-
Notifications
You must be signed in to change notification settings - Fork 21
Haskell Relational Records (HRR) - initial sprint #50
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
base: master
Are you sure you want to change the base?
Conversation
|
@saurabhnanda The domain API is now up and running as described in the PR, minus the topics still open (enums, arrays, jsonb). This would be a good time for you to review the code, comment some design choices, look at the overall resulting workflow etc. etc. so that I can adjust the code to your feedback, which will be highly appreciated. |
|
@mgmeier ran into the very first (expected) issue. The HRR TemplateHaskell needs to connect to PG in the compilation step itself. How do I configure the PG credentials? |
|
@mgmeier never mind. Figured it out. Btw, hitting a new compile error. Let's discuss it on Gitter. |
saurabhnanda
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Does the PG
textorvarcharalways map to Haskell'sString? What's the easiest way to make it map toData.Text? - What does
Relation x yreally mean? You can userelationalQueryto convertRelation x y -> Query x ywhereQuery x y = Query {untypeQuery :: String}. The type parametersx yin the entire chain seem to be phantom types. So, isRelation x ybasically some form of SQL represented in a Haskell-friendly AST (or DSL)? - Consequently, what's the difference between
SimpleQuery x yandRelation x y? - Has audit logging (transaction logging) been completed?
| , uEmail :: Maybe String | ||
| , uBOD :: Maybe String | ||
| , uStatus :: Maybe Int32 | ||
| , uOwnerId :: Maybe (Maybe Int32) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it a good idea to conflate a no-op with an update to NULL? eg. say this column were initially non-nullable, and thus, had the type Maybe Int32. And we had some call-sites where this field was being set as Nothing, assuming that it would map to an sql NULL. Then we made this column nullable, thus changing its type to Maybe (Maybe Int32). The call sites would still type-check, but they would result in completely unexpected behaviour.
What are your thoughts on the following: Nullable (Maybe Int32)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe a is the HRR derivation of a nullable type a on the db.
The conflation problem you adressed can actually not occur: The outer Maybe signals the presence vs. absence of a value for the update. So, no value assignment whatsoever takes place in the update query for a value of Nothing. The inner Maybe can then be set to Just Nothing to update with sql NULL. Thus, the call-sites that never update some field (Nothing) stay correct, the ones updating a column that has been made nullable fail to type-check, as it should be.
You're right however that the mechanism is not quite clear at first glance. I've changed that by replacing the outer Maybe with a different, more expressive type.
| } | ||
| $(makeRecordPersistableDefault ''UserInsert) | ||
|
|
||
| piUser :: Pi Users UserInsert |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just wondering if it is possible to avoid this boilerplate if we're fine with having the same data-structure/type for, both, reading from the DB and writing to the DB?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
HRR derived types have all strict fields; an "identity projection" of such a type would require us to assign a (non-existent) primary key value where present to construct the record -- which will then clash on the DB when we run the insert. For tables without PK, this is absolutely possible; I've done so for the join-through table users_roles, it looks like this:
assignRole :: Insert UsersRoles
assignRole = typedInsert (tableOf usersRoles) (defineDirectPi [0, 1])
|
|
||
| userUpdate :: UserUpdate | ||
| userUpdate = UserUpdate | ||
| Nothing Nothing Nothing Nothing Nothing Nothing |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can be replaced with Data.Default?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes. done.
|
|
||
| -- UPDATES | ||
|
|
||
| data UserUpdate = UserUpdate |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how do we handle the case where certain columns need to be protected from "blind" updates? eg. password column? While it can be inserted initially, it should not be updatable later via a variadic update? One should be forced to call a special function for changing the password, which would also trigger a side-effect of notifying the user by email?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the solution would be to remove that field from the variadic update type completely.
for those updates which require special attention / a special code-path (you mentioned side effects), a specialized, explicit Update relation and corresponding domain API function would be in order.
| User.lastName' <-#? uLastName | ||
| User.status' <-#? uStatus | ||
|
|
||
| (phTStamp, _) <- placeholder (\tStamp -> User.updatedAt' <-# tStamp) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need some comments on what exactly is happening here? How is the current timestamp actually getting assigned?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, how is createdAt being handled?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The helper type for this update is type TimestampedUpdate = Update (DBTime, PKey) . This means, the generated query will have two placeholders, one for the timestamp, one for the primary key to update. The other updated values are rendered in the query as literals. So when rendering the query DSL, we get something like:
UPDATE users SET first_name='Foo', last_name='Bar', updatedAt=? WHERE id=?, where substitution of these placeholders happens one step later.
In HRR, these type parameters for e.g. an Update a have to be realized via the placeholder syntax. It's also possible to apply some datatype argument first, and use the literal inside the query, e.g. DBTime -> PKey -> Update (). One can decide on the context which is preferable.
Either way, getting a current timestamp from the system clock is done in the DBInterface, which in turn then runs the update query.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
createdAt timestamps are always left to the DB, where they have default value current_timestamp in the schema.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Possible to discuss this on gitter chat?
| return a | ||
|
|
||
| -- given a user, get all his/her roles (inner join) | ||
| getRoles :: Relation Users Roles |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please give a usage example of this function? Is this just a DSL/AST representation of an inner-join between roles and users via the user_roles join-through table, with an unapplied user argument? What is the type of the unapplied user argument? Is it the PKey of the User table or the actual User record? How can you tell which arguments are unapplied from the function signature?
When this query is executed, what does it evaluate to? A list of Roles?
How would one write the following -- get all users along with each user's role(s), where the user has been created after a given timestamp AND the user's email ID belongs to a given domain (say, gmail.com or vacationlabs.com)?
Can I compose this Relation with another wheres clause specified outside this function? Can I compose this with another JOIN, say with tenants table?
|
I've changed HRR default mapping of PostgreSQL type |
JSONB and ENUM: investigation resultsThis is the relation used by the HRR driver to get postgres type info; those will be the types considered for generating a corresponding attribute in the derived Haskell type (in pseudo-code, taken from module We can see, JSONB is not amongst them as it is of category 'U' (user-defined). Conclusion: The HRR library would have to be patched accordingly for HRR to even consider |
|
Do you think this is easy to patch in HRR (JSONB and enums)? On 18 Nov 2016 9:50 pm, "M. G. Meier" notifications@github.com wrote:
|
|
Audit logging works fine for now; the logic for creating the JSON diff is in the application layer (Haskell), not on the DB. |
|
Don't forget to create DB from scratch (schema.sql) and possibly completely eliminate .stack-work/, since I've replaced the source repo for HRR with the fork where my patch is applied. |
This sprint's focus is the evaluation of HRR as a DB abstraction. The result should implement the domain API as per specs for Tenant, User and Role data types, encompassing the following:
Furthermore, the result should serve as an example how to implement and/or deal with the following topics in HRR:
Time estimate: 20 hours