Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A way to define Definition divorced from initial schema #151

Open
ilyakooo0 opened this issue Oct 4, 2019 · 17 comments
Open

A way to define Definition divorced from initial schema #151

ilyakooo0 opened this issue Oct 4, 2019 · 17 comments
Labels

Comments

@ilyakooo0
Copy link
Contributor

Currently the types of definition functions like createTable constrain the resulting schema, rather than deriving it from the initial schema and the definition itself, which kind of hinders the ability to decompose a schema definition into independent Definitions.

@echatav
Copy link
Contributor

echatav commented Oct 7, 2019

Can you give an example of what you mean by hinder? I guess you're talking about the constraint

schemas1 ~ Alter sch (Create tab ('Table (constraints :=> columns)) schema0) schemas0

in createTable. It's an equality constraint so it doesn't act much differently from inlining, but schemas1 is used in 2 places so it makes the type a little clearer I think.

@ilyakooo0
Copy link
Contributor Author

ilyakooo0 commented Oct 7, 2019

I mean that there isn't a way (at least I couldn't think of it) to describe the Definition of two different tables separately and then compose them into one Definition.

If the two tables are independent, then they will both come from an empty schema.
in this case the composition would have to be roughly:

cat a b -> cat a c -> cat a (b + c) 

Which is more akin to a special case of &&& from Arrow.

Furthermore, a table definition could depend on a different specific table being defined.

@echatav
Copy link
Contributor

echatav commented Oct 7, 2019

You can use the DDL type families to be polymorphic in the initial schema.

createUser
  :: Has "public" db schema
  => Definition db (Alter "public" (Create "user" UserTable schema) db)

@ilyakooo0
Copy link
Contributor Author

I think providing something akin to this might be useful (of course not hard-coded to the "public" schema):

type SchemumCreation (s :: Symbol) (table :: SchemumType) =
  forall db schema.
  Has "public" db schema =>
  Definition db (Alter "public" (Create s table schema) db)

@echatav
Copy link
Contributor

echatav commented Oct 15, 2019

I think that's less clear. It wouldn't be as obvious createTable is a Definition. And it's not a very general tool, whereas the DDL type families are very useful and general.

@ilyakooo0
Copy link
Contributor Author

ilyakooo0 commented Oct 15, 2019

Related: I am getting Could not deduce: AllNotNull with the SchemumCreation from the previous comment.

I think what I have written might not be strict enough somehow.

(I have a primaryKey constraint)

@echatav
Copy link
Contributor

echatav commented Oct 15, 2019

If you have a foreign key in your definition then the column(s) it references must be unique and not null. You should specify that your primary key in the other table is not null.

@ilyakooo0
Copy link
Contributor Author

ilyakooo0 commented Oct 15, 2019

type TasksTable =
  'Table
    ( '[ "pk_task_payload" :=> PrimaryKey '["task", "payload"]
       ]
        :=> '[ "task" ::: 'NoDef :=> 'NotNull (PG Text),
               "payload" ::: 'NoDef :=> 'NotNull (PG (Jsonb Value)),
               "start_time" ::: 'NoDef :=> 'NotNull (PG UTCTime),
               "initial_start_time" ::: 'NoDef :=> 'NotNull (PG UTCTime)
             ]
    )

createTasksTable ::
  Has "public" db schema =>
  Definition db (Alter "public" (Create "tasks" TasksTable schema) db)
createTasksTable =
  createTable
    #tasks
    ( notNullable text `as` #task
        :* notNullable jsonb `as` #payload
        :* notNullable timestampWithTimeZone `as` #start_time
        :* notNullable timestampWithTimeZone `as` #initial_start_time
    )
    ( primaryKey (#task :* #payload) `as` #pk_task_payload
    )

This gives me:

    • Could not deduce: AllNotNull
                          '["task" ::: field1, "payload" ::: field]

@ilyakooo0
Copy link
Contributor Author

The exact same code works when it is placed in a constant of type

Definition (Public '[]) (Public '[ "tasks" ::: TasksTable ])

@echatav
Copy link
Contributor

echatav commented Oct 15, 2019

interesting...clearly GHC's having trouble inferring field1 and field2. It should be able to infer them from the HasAll constraint but it can't because it's not fully applying the type families yet. Is there a big reason to want the definitions split up and polymorphic?

@ilyakooo0
Copy link
Contributor Author

Just decomposition.

Having one multi-thousand line file with every table and view definition and migration doesn't feel like a terribly good practice and feels like it will hinder maintainability and composability. (Compared to every definition/migration defining its own dependancies and separated into module).

For example define two executables with their own sets of tables, which overlap. Can't think of a good way except for copy-pasting.

@ilyakooo0
Copy link
Contributor Author

In an ideal scenario every function could explicitly specify which tables it relies on and not depend on the whole schema.

@echatav
Copy link
Contributor

echatav commented Oct 15, 2019

You can still decompose monomorphically though. In my project I have modules like V1.hs, V2.hs, etc. Each has their own Schemas type and a migration from the previous one.

@ilyakooo0
Copy link
Contributor Author

You mean explicitly defining some midpoint schema and splitting the definition along that schema?

@echatav
Copy link
Contributor

echatav commented Oct 15, 2019

basically, yes. Although I don't do one for each table, but just introduce a new Vn.hs each time I need to change the schema. So the first schema I defined was relatively large

@ilyakooo0
Copy link
Contributor Author

Not ideal, but seems reasonable.

Thanks

@echatav
Copy link
Contributor

echatav commented Oct 15, 2019

You can of course break apart that first one however you like. As for maintainability though, I never change the old Vn.hss, just introduce a new one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants