-
Notifications
You must be signed in to change notification settings - Fork 21
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
Haskell Relational Records (HRR) - initial sprint #50
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. |
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
text
orvarchar
always map to Haskell'sString
? What's the easiest way to make it map toData.Text
? - What does
Relation x y
really mean? You can userelationalQuery
to convertRelation x y -> Query x y
whereQuery x y = Query {untypeQuery :: String}
. The type parametersx y
in the entire chain seem to be phantom types. So, isRelation x y
basically some form of SQL represented in a Haskell-friendly AST (or DSL)? - Consequently, what's the difference between
SimpleQuery x y
andRelation 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