Define and use a Text column as id in persistent mongodb - haskell

I'm trying to define and use a table (AKA collection) with a Text column as the unique id. I'm using the 2.0.2 versions of persistent, persistent-mongodb, persistent-template.
This is my table definition:
let mongoSettings = (mkPersistSettings (ConT ''MongoContext)) { mpsGeneric = False }
in share [mkPersist mongoSettings, mkMigrate "migrateAll"][persistUpperCase|
User id=myid
myid Text
count Int
|]
I can't figure out how to create a Key User from a Text value.
I attempted:
mkUser :: Text -> Key User
mkUser x = UserKey . MongoKey $ x
with compiler error:
Couldn't match type ‘Text’ with ‘ObjectId’
Expected type: Text -> Key User
Actual type: ObjectId -> Key User
Are mongo keys in persistent only allowed to be of type ObjectId? From my examination of the relevant source code, this seems to be the case, but I would love to be wrong. Mongodb certainly allows textual keys outside of persistent. In fact, it allows for any unique thing.
I also tried inserting a row like so:
insert $ User "bob" 0
but the resulting database row has an auto-generated ObjectId in the _id column, instead of "bob".
If I define the schema:
User sql=profiles
_id Text
count Int
then I still have the issue of attempting to define a Key User with a Text value.

This is a new feature in persistent-mongoDB (that was not available when I asked the question), so the answer used to be "No, that's not possible" but is now "Yes, and here is how". It's available as of the 2.0.3 series: persistent-2.0.3, persistent-template-2.0.3, and persistent-mongoDB-2.0.3.
Your schema should have a line: Id <type of _id field> For example:
let mongoSettings = (mkPersistSettings (ConT ''MongoContext)) { mpsGeneric = False }
in share [mkPersist mongoSettings][persistUpperCase|
User
Id Text
age Int
|]
and you insert documents with a specific _id like this:
insertKey (UserKey "alice") $ User 1
Also see the wiki.

Related

default values for PersistList on SQLite - Yesod

I would like to add a PersistList value into a user entity with a default value. My model file looks like this. And Models.hs file:
User
ident Text
password Text Maybe
UniqueUser ident
perms [Privileges] default=[PrvDemoOne]
deriving Typeable
data Privileges =
PrvDemoOne -- ^ what can be demo one...
| PrvDemoTwo -- ^ what can be demo two...
deriving (Show,Read,Eq)
derivePersistField "Privileges"
the code compiles but when a new user is added into the table save an empty array instead of an array with the default value.
1|google-uid:223344555661778819911||[]
The question is how I could save the column with the default value?
Have you read this? The value of the default field doesn't really have anything to do with the Haskell side per-se, it's being passed to set the "default value" description your DBMS. In this case [PrvDemoOne] is being passed directly to SQLite, which will interpret it as gibberish (because it's not a valid SQL expression) so this is either ignored or (what seems to be the case here) treated as if you hadn't set a default at all.
If you want a "Haskell side" default value you should just create a function for that, i.e. something like
defaultUser :: Text -> Maybe Text -> User
defaultUser i maybePw = User { ident = i, password = maybePw, perms = [PrvDemoOne] }
If you want a SQL side default you need to write the corresponding SQL expression for the value you're trying to represent.
On a non-Haskell related note: the 'normal' way to represent lists (or sets in this case, rather!) in SQL is via relations, so you'd normally have a many-to-many relationship mapping users to their privileges instead of a list field.

How do I store Either (Key a) (Key b)?

I have the following model:
User
...
Group
...
Sharing
objectId (Either UserId GroupId)
In Sharing entity I want to store either UserId or GroupId and differentiate between them. Simply using Either doesn't work:
Not in scope: type constructor or class `UserId'
Not in scope: type constructor or class `GroupId'
Adding a new sum-type also doesn't work:
data SharingIdType = SharingUserId UserId | SharingGroupId GroupId
Not in scope: type constructor or class `SharingIdType'
Moving SharingIdType into another module isn't possible, because it uses UserId and GroupId types. The only way I see is to create an entity for each sharing type, like UserSharing/GroupSharing.
Other than that, how to approach this problem?
After searching for some time and thinking about it I concluded there are two possible solutions:
1.
If number of SharingIdTypes is static or rarely changes (means, it is OK to recompile the source to change it or alter the DB schema), the proper way to handle the problem is to have to entities for each sharing type:
User
...
Group
...
UserSharing
userId UserId
GroupSharing
groupId GroupId
Here the "sumness" of the problem is moved to DB queries. Whenever I need to find out with what something shared, I make two selectLists and query two tables instead of one.
2.
If number of SharingIdTypes needs to be altered dynamically, the SharingType entity is needed:
User
...
Group
...
SharingType
description String
Sharing
objectId SharingTypeId
This table is filled up with values corresponding to SharingIdTypes constructors:
do
insert $ SharingType "user"
insert $ SharingType "group"
Now whenever we share something, we refer SharingTypeId.

Yesod: querying `persist` database with a custom primary key

Supposing that I have a SQL table with persist, and that I have a custom Text as primary key instead of the auto-incrementing Int64 key.
For example, my database definition is thus:
share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
TorrentD
infoHash Text
ipAddr [Text]
Primary infoHash
deriving Show
|]
Supposing that I then have a plain Text value, what is the idiomatic way to query the database for a row with a primary key matching my Text value?
runDB $ get $ toSqlKey ("test" :: Text) doesn't work as toSqlKey doesn't support custom primary keys and thus expects expects an Int64.
Creating the key manually and running runDB $ get $ Key $ PersistText ("test" :: Text) doesn't work as it is giving me an error about Key not being in scope (although I do have Database.Persist.Class in my imports).
I've found (the?) (an?) answer. It's not very pretty, but:
get (TorrentDKey {unTorrentDKey = torrentPInfoHash torrent})
works.
The unTorrentDKey is something generated inside the template haskell.
It looks like i'll have to pepper
let primaryKey = TorrentDKey {unTorrentDKey = torrentPInfoHash torrent}
around in my code.

Database-backed REST API with servant?

I am running into a problem setting up a simple proof of concept servant API. This is my User datatype and my API type:
data User = User { id :: Int, first_name :: String, last_name :: String } deriving (Eq, Show, Generic)
instance FromRow User
instance ToRow User
$(deriveJSON defaultOptions ''User)
type API = "users" :> ReqBody '[JSON] User :> Post '[JSON] User
The handler method for this uses postgresql-simple like this:
create u = liftIO $ head <$> returning connection "insert into users (first_name, last_name) values (?,?) returning id" [(first_name u, last_name u)]
Boilerplate code such as connecting to the db and the routing method has been elided.
The problem is that if I make a POST request, what I want to be doing is creating a new user, so I would supply the JSON:
{ "first_name": "jeff", "last_name": "lebowski" }
but then my program fails at runtime with
Error in $: When parsing the record User of type Lib.User the key id was not present.
This makes sense because the API specified a User which has an id field. But I do not want to have to pass a bogus id in the request (since they are assigned by postgres sequentially) because that is gross. I also can't move the id field out of the User datatype because then postgres-simple fails because of a model-database mismatch when making a GET request to a different endpoint (which does the obvious thing: get by id. Not included above).
What do I do here? Write a custom FromJson instance? I already tried setting the Data.Aeson.TH options flag omitNothingFields to True and setting the id field to be a Maybe Int, but that didn't work either.
Any advice would be appreciated.
First you need to understand that a User and a row in a table corresponding to this User are two different things.
A row has an id, a user does not. For example, you can imagine to compare two users without dealing with ids, or the fact that they are saved or not.
Once convinced, you will have to explain this to the type system, or you will have to deal with Maybe fields, which I think is not the solution here.
Some people talked about Template Haskell, I think it would be overkill here, and you need to solve the problem first.
What you can do is use a data type to represent a saved row in your database. Let's call it an Entity.
newtype PrimaryKey = PrimaryKey Int
data Entity b = Entity PrimaryKey b
Then the function saving a User row in your database could take a user as parameter and return a PrimaryKey (In you database monad, of course).
Other functions reading from the database would return something using Entity User
Your field declarations are not duplicated, since you are reusing the User type as a parameter.
You will have to adapt FromRow/ToRow and FromJSON/ToJSON accordingly.

How do I define a composite key in Persistent

How do I declare to Persistent that I have a table the primary key of which is a combination of two fields?
For example, assume I have a table containing first_name and last_name, then in SQL syntax I'll need something like:
CONSTRAINT pk_PersonID PRIMARY KEY (first_name,last_name)
Thanks,
You can use the Primary <field1> <field2> syntax as per code below.
PrimaryCompositeWithOtherNullableFields
foo String maxlen=20
bar String maxlen=20
baz String Maybe
Primary foo bar -- THIS LINE --
deriving Eq Show
enter code here
The above code is taken from one of the tests at https://github.com/yesodweb/persistent/blob/master/persistent-test/src/CompositeTest.hs#L74
This wiki page explains the different syntax for defining the model in persistent. It really should have been part of the Yesod book.
See http://www.yesodweb.com/book/persistent, section Uniqueness
Person
firstName String
lastName String
age Int
PersonName firstName lastName
deriving Show
This define a unique key made of both firstName and lastName.

Resources