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 +45/−7
- ribbit.cabal +1/−1
- src/Database/Ribbit.hs +94/−25
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