packages feed

ribbit 0.4.0.0 → 0.4.1.0

raw patch · 3 files changed

+140/−33 lines, 3 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

+ Database.Ribbit: data Update table fields
+ Database.Ribbit: instance (GHC.TypeLits.KnownSymbol field, Database.Ribbit.RenderUpdates (m : ore)) => Database.Ribbit.RenderUpdates (field : m : ore)
+ Database.Ribbit: instance GHC.TypeLits.KnownSymbol field => Database.Ribbit.RenderUpdates '[field]
+ Database.Ribbit: instance forall k1 k2 (table :: k2) (updates :: k1). (GHC.TypeLits.KnownSymbol (Database.Ribbit.Name table), Database.Ribbit.RenderUpdates updates) => Database.Ribbit.Render (Database.Ribbit.Update table updates)

Files

README.md view
@@ -9,8 +9,8 @@             - [Limited `CREATE TABLE` support.](#limited-create-table-support)             - [`INSERT INTO` support.](#insert-into-support)             - [`DELETE FROM` support.](#delete-from-support)+            - [`UPDATE TABLE` support.](#update-table-support)     - [Roadmap](#roadmap)-        - [UPDATE support](#update-support)         - [Flesh out Haskell to PostgreSQL type mapping.](#flesh-out-haskell-to-postgresql-type-mapping)     - [How it compares with other libraries.](#how-it-compares-with-other-libraries)     - [The name: Ribbit](#the-name-ribbit)@@ -26,10 +26,13 @@ Using Ribbit, you might expect to see something like this:  ```haskell-type PeopleTable =-  Field "id" Int-  :> Field "name" Text-  :> Field "age" Int+data PeopleTable+instance Table PeopleTable where+  type Name PeopleTable = "people"+  type DBSchema PeopleTable =+    Field "id" Int+    :> Field "name" Text+    :> Field "age" Int     type MyQuery = Select '["id", "name"] `From` PeopleTable `Where` "age" `Equals` (?)@@ -148,13 +151,48 @@   (Only 1) ``` +#### `UPDATE TABLE` support.++Basic updates are supported:++```haskell+execute+  conn+  (Proxy :: Proxy (+    Update PeopleTable+      '[+        "name", -- each field adds a query parameter so you can+        "age"   -- provide the new value at run time.+      ]+    `Where` (+      "id" `Equals` (?)+    )+  ))+  (Only newName :> Only newAge :> Only targetEmployeeId)+```++ ## Roadmap  This is what I plan to work on next: -### UPDATE support+### Evaluate the usefulness of what I've got so far. -Support update operations.+I'm am half pleased with the way the query language turned out, because+someone with understanding of SQL can at least read and modify queries,+even if they would find it hard to construct them from scratch without+examples, but I still feel they could be more ergonomic in at least two ways:++1) Better error messages for compiler-guided development.+2) Fewer backticks.++I have tried playing around with defining the query language in terms+of value level constructs, which I think can help improve both of these+issues, but I haven't yet gotten that to work without requiring so much+type annotation that it defeats the purpose of ergonomics. I have some+vague idea about how I might solve that with generic instances, but I+am reluctant to do anything that smacks of "magic" without exhausting+all other possibilities first.  ### Flesh out Haskell to PostgreSQL type mapping. 
ribbit.cabal view
@@ -1,6 +1,6 @@  name:                ribbit-version:             0.4.0.0+version:             0.4.1.0 synopsis:            Type-level Relational DB combinators. description:         Ribbit is yet another type safe relational database                      library for Haskell, heavily inspired by the
src/Database/Ribbit.hs view
@@ -24,8 +24,8 @@      interpreted by third parties for their own purposes.    To that end, each schema is a new type, defined by you, using the-  combinators provided by this library. The same goes for queries. Each-  query is a separate type defined with combinators from this library.+  constructors provided by this library. The same goes for queries. Each+  query is a separate type defined with constructors from this library.    We provide a PostgreSQL backend so that real work can be accomplished,   but if the backend is missing something you need, then the idea is@@ -50,24 +50,31 @@   -- ** Deleting values   -- $delete +  -- ** Updating values+  -- $update+   -- * Schema Definition Types   (:>)(..),   Table(..),   Field, -  -- * Query Combinators+  -- * SQL Statement Constructors+  -- ** Query Constructors   Select,   From,   As,-  Where, -  -- * Insert Combinators+  -- ** Insert Constructors   InsertInto,   -  -- * Delete Combinators+  -- ** Delete Constructors   DeleteFrom, +  -- ** Update Constructors+  Update,+   -- ** Condition Conbinators+  Where,   And,   Or,   Equals,@@ -78,16 +85,16 @@   Lte,   Not, -  -- ** Query Parameters+  -- ** Statement Parameters   type (?), -  -- * Transformations on Query Types+  -- * Transformations on Statement Types   ArgsType,   ResultType,   ValidField,   ProjectionType, -  -- * Query Rendering+  -- * Statement Rendering   Render(..)  ) where@@ -156,7 +163,7 @@ -- >     :> Field "birth_date" Day  -- $query--- To write queries against these tables, use the query combinators+-- To write queries against these tables, use the query constructors -- defined in this module: --  -- > -- Given a company name as a query parameter, return all the@@ -295,74 +302,102 @@ -- >     (Proxy :: Proxy DeleteAllEmployees) -- >     () +-- $update+-- Updating values is almost the same as inserting values. Instead of+-- specifying the fields that get inserted, you specify the fields that get+-- updated, along with the conditions that match the rows to be updated.+--+-- > {- Update an employee's salary (hopefully a raise!). -}+-- > type UpdateSalary =+-- >   Update+-- >     '[+-- >       "salary"+-- >     ]+-- >   `Where` +-- >     "id" `Equals` (?)+-- > +-- > ...+-- > +-- > let+-- >   newSalary :: Int+-- >   newSalary = ...+-- > +-- >   targetEmployee :: Int+-- >   targetEmployee = ...+-- > in+-- >   execute+-- >     conn+-- >     (Proxy :: Proxy UpdateSalary)+-- >     (Only newSalary :> Only targetEmployee) -{- | "SELECT" combinator, used for starting a @SELECT@ statement. -}++{- | "SELECT" constructor, used for starting a @SELECT@ statement. -} data Select fields   {- |-  "FROM" combinator, used for attaching a SELECT projection to a relation+  "FROM" constructor, used for attaching a SELECT projection to a relation   in the database. -} data From proj relation infixl 6 `From`  -{- | "WHERE" combinator, used for attaching conditions to a query. -}+{- | "WHERE" constructor, used for attaching conditions to a query. -} data Where query conditions infixl 6 `Where`  -{- | "=" combinator for conditions. -}+{- | "=" constructor for conditions. -} data Equals l r infix 9 `Equals`  -{- | "!=" combinator for conditions. -}+{- | "!=" constructor for conditions. -} data NotEquals l r infix 9 `NotEquals`  -{- | "<" combinator for conditions. -}+{- | "<" constructor for conditions. -} data Lt l r infix 9 `Lt`  -{- | "<=" combinator for conditions. -}+{- | "<=" constructor for conditions. -} data Lte l r infix 9 `Lte`  -{- | ">" combinator for conditions. -}+{- | ">" constructor for conditions. -} data Gt l r infix 9 `Gt`  -{- | ">=" combinator for conditions. -}+{- | ">=" constructor for conditions. -} data Gte l r infix 9 `Gte`  -{- | "AND" combinator for conditions. -}+{- | "AND" constructor for conditions. -} data And l r infixr 8 `And`  -{- | "OR" combinator for conditions. -}+{- | "OR" constructor for conditions. -} data Or l r infixr 7 `Or`  -{- | "AS" combinator, used for attaching a name to a table in a FROM clause. -}+{- | "AS" constructor, used for attaching a name to a table in a FROM clause. -} data As relation name infix 8 `As`  -{- | NOT conditional combinator. -}+{- | NOT conditional constructor. -} data Not a  -{- | "?" combinator, used to indicate the presence of a query parameter. -}+{- | "?" constructor, used to indicate the presence of a query parameter. -} data (?)  @@ -527,7 +562,13 @@     TypeError ('Lit.Text "Insert statement must specify at least one column.")   ArgsType (InsertInto relation fields) =     ProjectionType fields (DBSchema relation)+  ArgsType (Update relation fields) =+    ProjectionType fields (DBSchema relation)+  ArgsType (Update relation fields `Where` conditions) =+    ProjectionType fields (DBSchema relation)+    :> ArgsType (DBSchema relation, conditions) +   ArgsType (schema, And a b) =     StripUnit (Flatten (ArgsType (schema, a) :> ArgsType (schema, b)))   ArgsType (schema, Or a b) =@@ -722,13 +763,37 @@         <> " (" <> T.intercalate ", " fields <> ")"         <> " values (" <> T.intercalate ", " (const "?" <$> fields) <> ");" - {- DELETE -} instance (KnownSymbol (Name table)) => Render (DeleteFrom table) where   render _proxy =     "delete from " <> symbolVal (Proxy @(Name table)) +{- UPDATE -}+instance (KnownSymbol (Name table), RenderUpdates updates)+  =>+    Render (Update table updates)+  where+    render _proxy =+      "UPDATE "+      <> symbolVal (Proxy @(Name table))+      <> " SET "+      <> renderUpdates (Proxy @updates) ++{- | Render the updates to a table. -}+class RenderUpdates a where+  renderUpdates :: proxy a -> Text+instance (KnownSymbol field) => RenderUpdates '[field] where+  renderUpdates _ = symbolVal (Proxy @field) <> " = ?"+instance (KnownSymbol field, RenderUpdates (m:ore))+  =>+    RenderUpdates (field : (m:ore))+  where+    renderUpdates _ =+      symbolVal (Proxy @field) <> " = ?, "+      <> renderUpdates (Proxy @(m:ore))++ {- | Insert statement. -} data InsertInto table fields @@ -744,5 +809,9 @@  {- | Delete statement. -} data DeleteFrom table+++{- | Update rows in a table. -}+data Update table fields