yesodweb / persistent

Persistence interface for Haskell allowing multiple storage methods.
MIT License
467 stars 297 forks source link

Persistent with pre-defined data types? #476

Open gibiansky opened 9 years ago

gibiansky commented 9 years ago

I have a bunch of already-defined data types using standard Haskell syntax. Can I use persistent without converting them into quasiquoters? It seems like it should be possible, but I can't find any documentation on how to do so. I think I'd like a version of mkPersist that does not generate the actual data statements but rather just the EntityDefs, but I'm not sure.

Any suggestions?

gregwebs commented 9 years ago

Not supported, although I would like to. You can write a function to translate from your data type into a persistent data type.

gibiansky commented 9 years ago

Ok. Thanks.

Function to go back and forth is doable but somewhat of a pain for a large complex object.

gibiansky commented 9 years ago

Can I use derivePersistField for this?

erikd commented 9 years ago

Yes, derivePersistField should do the trick.

gibiansky commented 9 years ago

Reading a bit more, it looks like derivePersistField uses Show and Read marshalling. There's no way that could support migrations, right? It seems like it shouldn't support migrations (e.g. adding a field breaks Read) so it's not quite the same, and it's really intended for simple fields that won't change their structure.

erikd commented 9 years ago

If the data structures don't change then the migrations should be fine.

For things that could not be migrated automatically, I have written Haskell code to do the migration.

gibiansky commented 9 years ago

If my entire application uses derivePersistField, I will have migrations that will need to be written. The data structures are likely to change. How would you write migrations for something that uses derivePersistField?

erikd commented 9 years ago

Say you have a table T with a field of type CustomData and you want to update that field.

You now need two versions of that data type, the old CustomDataOld and the new CustomData and you need a function convertCustomData which converts old to new.

Now you can then create a new table Tnew, with the same fields as T except with CustomDataOld replaced with the new CustomData. You can then read rows from T, writing them the Tnew converting CustomDataOld to CustomData along them way.

Finally you delete T and rename Tnew to T.

I hope I don't need to say that you test the code you write for the above on a cloned version of the DB :-).

gibiansky commented 9 years ago

I guess that's possible, that that's going to be a large proliferation of data types, with every change resulting in a new data type, no matter how small the change. I don't think that is a reasonable approach, although I guess it would work. It's certainly error-prone and not scalable.

I think I will avoid using derivePersistField. For the time being I can use something other than persistent, as I don't like the idea of committing to persistent and putting it at the forefront of my application. (In general I think the lack of this feature is very anti-modular: it means I cannot separate my data types and the way they are stored easily.) Hopefully this will be implemented soon!

erikd commented 9 years ago

If this is a problem, I suspect that you are designing you database schema incorrectly. Most database fields should be simple things like Text, Int, Bool etc. Other enumerations like data Wibble = A | B | C should also be fine. I would advise against using complicated data types in a database field.

gibiansky commented 9 years ago

That is my point: derivePersistField is meant for fields only. I would like to have my database schema represented by records which are not defined through persistent's quasiquoters. Instead of

share [...] [persistLowerCase|
Person
  name String
  age Int
|]

I would like to write

data Person = Person { name :: String, age :: Int }
derivePersistent 'Person

Or something like that.

erikd commented 9 years ago

Ok, looks like you are little confused.

If you define:

share [...] [persistLowerCase|
    Person
        name String
        age Int
|]

then persistent will automatically create accessors named personName and personAge.

gibiansky commented 9 years ago

I realize that. I don't want that behaviour, because I already have a bunch of defined records, and I don't want to have to rewrite those records in persistent's quasiquoter.

In general, I don't want persistent generating my data types. I want to use my existing data types with persistent. This may just be the wrong way to use persistent, but the majority of the tutorials don't seem to imply so.

jerbaroo commented 7 years ago

This is also my main issue with persistent, the use of quasi-quotes. It's something I consider as a more complex Haskell feature and it may confuse less-advanced people reading my code. I really think persistent is brilliant but I don't want to have to sacrifice readability and thus maintainability.

googleson78 commented 4 years ago

Is there some "right" solution for this problem?

It seems to me like it is implied to be having a type that you only use to store in your db and convert to and from what you actually use, but (as a newb) I don't understand what the issue is with trying to store the ("complex") datatypes I will be working with in my program "directly" in my db?

parsonsmatt commented 4 years ago

The "right" solution is to separate your serialization types and domain types, because what is Good and easy for your domain is probably not good and easy for serialization and vice versa.

If you want to use persistent with pre-made datatypes, then you can write a PersistEntity instance for that type. This isn't well documented and it's a good bit of work, but you can totally do it.

We could provide a Template Haskell function mkPersistEntity :: Name -> PersistOptions -> DecsQ that would generate the PersistEntity instance for you. There are some complications here - the persistent quasiquoter has many features for describing database specific details that Haskell data syntax doesn't cover.

This should be made easier by the #995 issue which will make Entity less special.