circuithub / rel8

Hey! Hey! Can u rel8?
https://rel8.readthedocs.io
Other
150 stars 38 forks source link

Statements overhaul (support for statement-level `WITH`) #247

Closed shane-circuithub closed 12 months ago

shane-circuithub commented 1 year ago

The motivation behind this PR was to add support for PostreSQL's WITH syntax at the statement level, which gives the ability to, e.g., delete some rows from a table and then re-insert those deleted rows into another table, without any round-trips between the application and the database.

However, this necessitated some changes to how Returning works, which also bled into our interface for running queries, so it's quite an invasive change. Returning previously bundled two different concepts together: whether or not to generate a RETURNING clause in the SQL for a manipulation statement, and how to decode the returned rows (if any). It was necessary to break these concepts apart because with WITH we need the ability to generate manipulation statements with RETURNING clauses that are never actually decoded at all (the results just get passed to the next statement without touching the application).

This resulted in the Rows datatype which this PR introduces, which has the following values:

type Rows :: Maybe Type -> Type -> Type
data Rows returning result where
  Void :: Rows returning ()
  RowsAffected :: Rows 'Nothing Int64
  Single :: Serializable exprs a => Rows ('Just exprs) a
  Maybe :: Serializable exprs a => Rows ('Just exprs) (Maybe a)
  List :: Serializable exprs a => Rows ('Just exprs) [a]
  Vector :: Serializable exprs a => Rows ('Just exprs) (Vector a)

select, insert, update and delete have all been replaced with a single (polymorphic) run function. run takes Rows as an argument (in addition to a statement such as a Query or an Insert) that specifies whether the result of the statement should be read as a single row or a list of rows (or a number of rows affected). This also gains us support for decoding the result of a query directly to a Vector which brings a performance improvement over lists for those who need it.

The Single, Maybe, List and Vector values for the Rows parameter can only be used with Querys or manipulation statements with a RETURNING clause — statements without a returning clause can only use Void or RowsAffected.

And finally, to actually generate compound statements using WITH, the Rel8.Statement.Do module exports a pseudo-monadic interface can be used with the QualifiedDo extension. This binds the result of a previous statement to a Query, which can then be used by a subsequent statement (e.g., as the rows parameter of an Insert statement).

shane-circuithub commented 1 year ago

@ocharles I figured out how to make Statement an actual Monad so that it doesn't need QualifiedDo. Let me make that change first before you put a bunch of time into reviewing this because it will move a bunch of things around

shane-circuithub commented 1 year ago

@ocharles Actually, no, I can't make it a full Monad. Maybe Bind from semigroupoids but not without other compromises and it's not clear that it's a win overall. Maybe you could familiarise yourself with this at first and then we could have a chat about the trade-offs and figure out how to move forward.

shane-circuithub commented 12 months ago

Closing in favour of #250.