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

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.

Related

Remove underscore from fields with generated lenses in Persistent

Let's suppose that I have a persistent type and want to project some value from this type:
share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
User
name Text
email Text
|]
...
getName :: Entity User -> Text
getName (Entity uid vals) = userName vals
The problem is, if I generate lenses for said type, using mkPersist sqlSettings {mpsGenerateLenses = True}, I'll need to add a underscore in the beginning of each projection function or use the lenses getter:
getName :: Entity User -> Text
getName (Entity uid vals) = _userName vals
getName' :: Entity User -> Text
getName (Entity uid vals) = vals ^. userName
Firstly, how can I revert that to the default, userName vals, and add the underscore to use the lenses getter, vals ^. _userName?
Secondly, why is this this way and not the other way around?
Firstly, how can I revert that to the default, userName vals, and add the underscore to use the lenses getter, vals ^. _userName?
Database.Persist.TH does not offer that option (to see what it might look like if it existed, cf. Control.Lens.TH), so, assuming that you won't fork the library over this, there doesn't seem to be a way. (By the way, looking for mpsGenerateLenses in the source will show exactly where the underscores are added.)
Secondly, why is this this way and not the other way around?
Presumably because the library assumes that if you generate the lenses you will use them everywhere instead of the record accessors/labels, including for getting the value of the field. The only cosmetic suggestion I have is that, if the change of writing order from _userName vals to vals ^. userName bothers you, you might prefer using view rather than (^.), as in view userName vals.

How to omit fields that will be filled in by the database

I've just started off with Persistent from Yesod and have already hit my first roadblock.
share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
User
email String
createdAt UTCTime Maybe default=CURRENT_TIME
updatedAt UTCTime Maybe default=CURRENT_TIME
deriving Show
|]
u <- insert $ User "saurabhnanda#gmail.com" Nothing Nothing
I'm coming from a Rails background and like the schema design conventions advocated by them. In this particular case, having every table have a created_at and updated_at timestamp. However, is there any way to NOT specify the createdAt and updatedAt fields for every object that will be created?
I don't know much about Persistent, but the usual technique for this kind of thing is to define some parameterized types. For example, you might write
data Dated a = Dated
{ created :: UTCTime
, updated :: UTCTime
, value :: a
}
Then your bare values need not worry about their metadata, and metadata can be handled uniformly across all different types. e.g. you might expose an API like this:
new :: a -> IO (Dated a)
new a = do
now <- getUTCTime
return (Dated now now a)
update :: Dated a -> a -> IO (Dated a)
update old new = do
now <- getUTCTime
return (old { updated = now, value = new })
Then I would suggest exporting the Dated type constructor and all its fields, together with new and update, but not exporting the Dated data constructor. This will ensure that the created and updated fields are maintained correctly.

Define and use a Text column as id in persistent mongodb

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.

Can template-haskell be used to generate quasi-quotes?

A project that I'm currently working on makes extensive use of persistent. Instead of persistent's quasi-quoted syntax to specify models, I would like to use json. Right now, I use a script to generate the quasiquote which persistent expects using simple-templates. That adds a rather awkward step in the workflow. Can this be avoided using template-haskell?
This is currently generated by the script :
-- File : ProjSpecific.Models
share [mkPersist sqlSettings, mkMigrate "migrateAll"]
[persistLowerCase|
Person
name String
age Int Maybe
deriving Show
BlogPost
title String
authorId PersonId
deriving Show
|]
This is how I would ideally like to do it :
-- File : ProjSpecific.Config
import Data.Aeson.QQ
import Data.Aeson (Value)
models :: Value
models = [aesonQQ| {some json encoding of above models} |]
And
-- File : ProjSpecific.Models
complie time logic to generate the persistent models
Any ideas on how this can be done or is there a better way to accompilsh what I'm trying to do?
Yes, it should be relatively painless. You'd essentially want to use the quoteExp field from persistLowerCase, which will give you a function of type String -> Q Exp. Use your preprocessor to convert JSON into the expected syntax, and then pass it to the function.

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