packages feed

ribbit 0.4.1.0 → 1.0.0.0

raw patch · 11 files changed

+1047/−639 lines, 11 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Database.Ribbit: class Render query
- Database.Ribbit: instance (GHC.Classes.Eq a, GHC.Classes.Eq b) => GHC.Classes.Eq (a Database.Ribbit.:> b)
- Database.Ribbit: instance (GHC.Classes.Ord a, GHC.Classes.Ord b) => GHC.Classes.Ord (a Database.Ribbit.:> b)
- Database.Ribbit: instance (GHC.Show.Show a, GHC.Show.Show b) => GHC.Show.Show (a Database.Ribbit.:> b)
- Database.Ribbit: instance (GHC.TypeLits.KnownSymbol a, Database.Ribbit.ReflectFields more) => Database.Ribbit.ReflectFields (a : more)
- Database.Ribbit: instance (GHC.TypeLits.KnownSymbol field, Database.Ribbit.ReflectFields (field : more)) => Database.Ribbit.Render (field : more)
- Database.Ribbit: instance (GHC.TypeLits.KnownSymbol field, Database.Ribbit.RenderUpdates (m : ore)) => Database.Ribbit.RenderUpdates (field : m : ore)
- Database.Ribbit: instance Database.Ribbit.ReflectFields '[]
- Database.Ribbit: instance Database.Ribbit.Render (Database.Ribbit.?)
- Database.Ribbit: instance Database.Ribbit.Render (Database.Ribbit.Expr (Database.Ribbit.?))
- Database.Ribbit: instance GHC.TypeLits.KnownSymbol a => Database.Ribbit.Render (Database.Ribbit.Expr a)
- Database.Ribbit: instance GHC.TypeLits.KnownSymbol field => Database.Ribbit.RenderUpdates '[field]
- Database.Ribbit: instance forall k (a :: k). Database.Ribbit.Render a => Database.Ribbit.Render (Database.Ribbit.Not a)
- Database.Ribbit: instance forall k (fields :: k). Database.Ribbit.Render fields => Database.Ribbit.Render (Database.Ribbit.Select fields)
- Database.Ribbit: instance forall k (table :: k) (name :: GHC.Types.Symbol) (moreTables :: [*]). (Database.Ribbit.Table table, GHC.TypeLits.KnownSymbol name) => Database.Ribbit.Table (Database.Ribbit.As table name : moreTables)
- Database.Ribbit: instance forall k (table :: k). GHC.TypeLits.KnownSymbol (Database.Ribbit.Name table) => Database.Ribbit.Render (Database.Ribbit.DeleteFrom table)
- Database.Ribbit: instance forall k1 k2 (fields :: k2) (table :: k1). (Database.Ribbit.ReflectFields fields, GHC.TypeLits.KnownSymbol (Database.Ribbit.Name table)) => Database.Ribbit.Render (Database.Ribbit.InsertInto table fields)
- Database.Ribbit: instance forall k1 k2 (l :: k2) (r :: k1). (Database.Ribbit.Render (Database.Ribbit.Expr l), Database.Ribbit.Render (Database.Ribbit.Expr r)) => Database.Ribbit.Render (Database.Ribbit.Equals l r)
- Database.Ribbit: instance forall k1 k2 (l :: k2) (r :: k1). (Database.Ribbit.Render (Database.Ribbit.Expr l), Database.Ribbit.Render (Database.Ribbit.Expr r)) => Database.Ribbit.Render (Database.Ribbit.Gt l r)
- Database.Ribbit: instance forall k1 k2 (l :: k2) (r :: k1). (Database.Ribbit.Render (Database.Ribbit.Expr l), Database.Ribbit.Render (Database.Ribbit.Expr r)) => Database.Ribbit.Render (Database.Ribbit.Gte l r)
- Database.Ribbit: instance forall k1 k2 (l :: k2) (r :: k1). (Database.Ribbit.Render (Database.Ribbit.Expr l), Database.Ribbit.Render (Database.Ribbit.Expr r)) => Database.Ribbit.Render (Database.Ribbit.Lt l r)
- Database.Ribbit: instance forall k1 k2 (l :: k2) (r :: k1). (Database.Ribbit.Render (Database.Ribbit.Expr l), Database.Ribbit.Render (Database.Ribbit.Expr r)) => Database.Ribbit.Render (Database.Ribbit.Lte l r)
- Database.Ribbit: instance forall k1 k2 (l :: k2) (r :: k1). (Database.Ribbit.Render (Database.Ribbit.Expr l), Database.Ribbit.Render (Database.Ribbit.Expr r)) => Database.Ribbit.Render (Database.Ribbit.NotEquals l r)
- Database.Ribbit: instance forall k1 k2 (l :: k2) (r :: k1). (Database.Ribbit.Render l, Database.Ribbit.Render r) => Database.Ribbit.Render (Database.Ribbit.And l r)
- Database.Ribbit: instance forall k1 k2 (l :: k2) (r :: k1). (Database.Ribbit.Render l, Database.Ribbit.Render r) => Database.Ribbit.Render (Database.Ribbit.Or l r)
- Database.Ribbit: instance forall k1 k2 (query :: k2) (conditions :: k1). (Database.Ribbit.Render query, Database.Ribbit.Render conditions) => Database.Ribbit.Render (Database.Ribbit.Where query conditions)
- Database.Ribbit: instance forall k1 k2 (relation :: k2) (proj :: k1). (GHC.TypeLits.KnownSymbol (Database.Ribbit.Name relation), Database.Ribbit.Render proj, Database.Ribbit.Table relation) => Database.Ribbit.Render (Database.Ribbit.From proj relation)
- 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)
- Database.Ribbit: render :: Render query => proxy query -> Text
- Database.Ribbit.PostgreSQL: instance (Database.Ribbit.PostgreSQL.FromRow a, Database.Ribbit.PostgreSQL.FromRow b) => Database.Ribbit.PostgreSQL.FromRow (a Database.Ribbit.:> b)
- Database.Ribbit.PostgreSQL: instance (Database.Ribbit.PostgreSQL.ToRow a, Database.Ribbit.PostgreSQL.ToRow b) => Database.Ribbit.PostgreSQL.ToRow (a Database.Ribbit.:> b)
- Database.Ribbit.PostgreSQL: instance forall k (name :: GHC.Types.Symbol) (typ :: k). GHC.TypeLits.KnownSymbol name => Database.Ribbit.PostgreSQL.HasFields (Database.Ribbit.Field name typ)
- Database.Ribbit.PostgreSQL: instance forall k (name :: GHC.Types.Symbol) more (typ :: k). (GHC.TypeLits.KnownSymbol name, Database.Ribbit.PostgreSQL.HasFields more) => Database.Ribbit.PostgreSQL.HasFields (Database.Ribbit.Field name typ Database.Ribbit.:> more)
- Database.Ribbit.PostgreSQL: instance forall k1 k2 (typ :: k2) (name :: k1). (Database.Ribbit.PostgreSQL.HasIsNullable typ, Database.Ribbit.PostgreSQL.HasPsqlType typ) => Database.Ribbit.PostgreSQL.HasPsqlTypes (Database.Ribbit.Field name typ)
- Database.Ribbit.PostgreSQL: instance forall k1 k2 (typ :: k2) more (name :: k1). (Database.Ribbit.PostgreSQL.HasIsNullable typ, Database.Ribbit.PostgreSQL.HasPsqlType typ, Database.Ribbit.PostgreSQL.HasPsqlTypes more) => Database.Ribbit.PostgreSQL.HasPsqlTypes (Database.Ribbit.Field name typ Database.Ribbit.:> more)
+ Database.Ribbit: data IsNull (field :: Symbol)
+ Database.Ribbit: data LeftJoin left right
+ Database.Ribbit: data NotNull (field :: Symbol)
+ Database.Ribbit: data On join (conditions :: *)
+ Database.Ribbit: infix 7 `On`
+ Database.Ribbit.PostgreSQL: instance (Database.Ribbit.PostgreSQL.FromRow a, Database.Ribbit.PostgreSQL.FromRow b) => Database.Ribbit.PostgreSQL.FromRow (a Database.Ribbit.Table.:> b)
+ Database.Ribbit.PostgreSQL: instance (Database.Ribbit.PostgreSQL.ToRow a, Database.Ribbit.PostgreSQL.ToRow b) => Database.Ribbit.PostgreSQL.ToRow (a Database.Ribbit.Table.:> b)
+ Database.Ribbit.PostgreSQL: instance forall k (name :: GHC.Types.Symbol) (typ :: k). GHC.TypeLits.KnownSymbol name => Database.Ribbit.PostgreSQL.HasFields (Database.Ribbit.Table.Field name typ)
+ Database.Ribbit.PostgreSQL: instance forall k (name :: GHC.Types.Symbol) more (typ :: k). (GHC.TypeLits.KnownSymbol name, Database.Ribbit.PostgreSQL.HasFields more) => Database.Ribbit.PostgreSQL.HasFields (Database.Ribbit.Table.Field name typ Database.Ribbit.Table.:> more)
+ Database.Ribbit.PostgreSQL: instance forall k1 k2 (typ :: k2) (name :: k1). (Database.Ribbit.PostgreSQL.HasIsNullable typ, Database.Ribbit.PostgreSQL.HasPsqlType typ) => Database.Ribbit.PostgreSQL.HasPsqlTypes (Database.Ribbit.Table.Field name typ)
+ Database.Ribbit.PostgreSQL: instance forall k1 k2 (typ :: k2) more (name :: k1). (Database.Ribbit.PostgreSQL.HasIsNullable typ, Database.Ribbit.PostgreSQL.HasPsqlType typ, Database.Ribbit.PostgreSQL.HasPsqlTypes more) => Database.Ribbit.PostgreSQL.HasPsqlTypes (Database.Ribbit.Table.Field name typ Database.Ribbit.Table.:> more)
- Database.Ribbit: data As relation name
+ Database.Ribbit: data As relation (name :: Symbol)
- Database.Ribbit: data Equals l r
+ Database.Ribbit: data Equals (l :: k1) (r :: k2)
- Database.Ribbit: data Or l r
+ Database.Ribbit: data Or (l :: k1) (r :: k2)
- Database.Ribbit: data Update table fields
+ Database.Ribbit: data Update table (fields :: [Symbol])
- Database.Ribbit: infix 8 `As`
+ Database.Ribbit: infix 8 `LeftJoin`
- Database.Ribbit: infix 9 `Lte`
+ Database.Ribbit: infix 9 `Gte`
- Database.Ribbit: type family ProjectionType proj schema
+ Database.Ribbit: type family Render a :: Symbol
- Database.Ribbit.PostgreSQL: execute :: (MonadIO m, ToRow (ArgsType query), Render query) => Connection -> Proxy query -> ArgsType query -> m Int64
+ Database.Ribbit.PostgreSQL: execute :: forall m query. (MonadIO m, ToRow (ArgsType query), KnownSymbol (Render query)) => Connection -> Proxy query -> ArgsType query -> m Int64
- Database.Ribbit.PostgreSQL: query :: (MonadIO m, Render query, ToRow (ArgsType query), FromRow (ResultType query)) => Connection -> Proxy query -> ArgsType query -> m [ResultType query]
+ Database.Ribbit.PostgreSQL: query :: forall m query. (MonadIO m, KnownSymbol (Render query), ToRow (ArgsType query), FromRow (ResultType query)) => Connection -> Proxy query -> ArgsType query -> m [ResultType query]

Files

ribbit.cabal view
@@ -1,6 +1,6 @@  name:                ribbit-version:             0.4.1.0+version:             1.0.0.0 synopsis:            Type-level Relational DB combinators. description:         Ribbit is yet another type safe relational database                      library for Haskell, heavily inspired by the@@ -24,7 +24,15 @@   exposed-modules:          Database.Ribbit     Database.Ribbit.PostgreSQL-  -- other-modules:       +  other-modules:       +    Database.Ribbit.Args+    Database.Ribbit.Conditions+    Database.Ribbit.Delete+    Database.Ribbit.Insert+    Database.Ribbit.Render+    Database.Ribbit.Select+    Database.Ribbit.Table+    Database.Ribbit.Update   -- other-extensions:       build-depends:     Only              >= 0.1     && < 0.2,
src/Database/Ribbit.hs view
@@ -54,62 +54,162 @@   -- $update    -- * Schema Definition Types-  (:>)(..),   Table(..),   Field,+  (:>)(..),    -- * SQL Statement Constructors   -- ** Query Constructors+  {- |+    Types in the section are used to construct SELECT queries.++    e.g.++    List all employees who have quit, for all companies. (Left Joined+    version, which shows companies that have had no employees quit.)++    > Select '["c.name", "e.name", "e.quit_date"]+    > `From`+    >   Company `As` "c"+    >   `LeftJoin`  Employee `As` "e" `On` "c.id" `Equals` "e.company_id"+    > `Where`+    >   NotNull "e.quit_date"++    List all employees who have quit, for all companies. (Inner join+    version, which omits companies from which no employee has quit.)++    > Select '["c.name", "e.name", "e.quit_date"]+    > `From` '[Company `As` "c", Employee `As` "e"]+    > `Where`+    >   "c.id" `Equals` "e.company_id"+    >   `And` NotNull "e.quit_date"+  -}   Select,   From,   As,+  On,+  LeftJoin,    -- ** Insert Constructors+  {- |+    Construct insert statements.++    e.g.++    Insert one row into the Employee Table.++    > InsertInto Employee '["id", "company_id", "name"]++    The values which are inserted into the specified fields are provided+    as query parameters.+  -}   InsertInto,-  +   -- ** Delete Constructors+  {- |+    Construct delete statements.++    e.g.++    Delete all rows from the Company table:+    +    > type Statement = DeleteFrom Company++    Delete a specific row from the Company table:++    > type Statement = DeleteFrom Company `Where` "id" `Equals` (?)+  -}   DeleteFrom,    -- ** Update Constructors+  {- |+    Construct update statements++    e.g.++    Update a specific employee's salary:++    > type Statement =+    >   Update Employee '["salary"]+    >   `Where`+    >     "company_id" `Equals` (?)+    >     `And` "id" `Equals` (?)++    The values which are inserted into the specified fields are provided+    as query parameters.++    e.g.++    > execute+    >   conn+    >   Statement+    >   (Only newSalary :> Only companyId :> Only employeeId)+  -}   Update, -  -- ** Condition Conbinators+  -- ** Condition Constructors+  {- | Use these types to construct a @WHERE@ clause. -}+   Where,-  And,-  Or,   Equals,   NotEquals,-  Gt,-  Gte,   Lt,   Lte,+  Gt,+  Gte,+  And,+  Or,   Not,--  -- ** Statement Parameters+  IsNull,+  NotNull,   type (?),    -- * Transformations on Statement Types+  {- |+    These type families are useful for transforming the query types in various+    ways, or extracting certain information from them.++    e.g.++    Given the query:++    > type Query = Select '["name"] `From` Company `Where` "id" `Equals` (?)++    Render the query as a 'String' value (using 'GHC.TypeLits.symbolVal'):++    > symbolVal (Proxy :: Proxy (Render Query)) == "SELECT name FROM companies WHERE id = ?"++    Produce the Haskell type corresponding to the query parameters for a select+    statement:++    > ArgsType Query ~ Only Int -- Our statement has only one parameter, which is an int.++    Produce the Haskell type corresponding to the rows produced by the query:++    > ResultType Query ~ Only Text -- Our query procudes only one column, a text.++  -}   ArgsType,   ResultType,-  ValidField,-  ProjectionType,+  Render,+  -- ValidField,+  -- ProjectionType, -  -- * Statement Rendering-  Render(..)+  -- -- * Statement Rendering+  -- Render,  ) where  -import Data.Proxy (Proxy(Proxy))-import Data.String (IsString, fromString)-import Data.Text (Text)-import Data.Tuple.Only (Only)-import Data.Type.Bool (If, type (||))-import GHC.TypeLits (KnownSymbol, TypeError, ErrorMessage((:<>:),-  (:$$:), ShowType), AppendSymbol, Symbol)-import qualified Data.Text as T-import qualified GHC.TypeLits as Lit-+import Database.Ribbit.Args (ArgsType, ResultType)+import Database.Ribbit.Conditions (Where, Equals, NotEquals, Lt, Lte,+  Gt, Gte, And, Or, Not, IsNull, NotNull, type (?))+import Database.Ribbit.Delete (DeleteFrom)+import Database.Ribbit.Insert (InsertInto)+import Database.Ribbit.Render (Render)+import Database.Ribbit.Select (Select, From, As, On, LeftJoin)+import Database.Ribbit.Table (Table(Name, DBSchema), Field, (:>)((:>)))+import Database.Ribbit.Update (Update)  -- $definetable -- To define a table you need a type:@@ -125,9 +225,7 @@ -- And you need a type class instance 'Table': --  -- > instance Table Company where--- > -- >   type Name Company = "companies"--- > -- >   type DBSchema Company = -- >     Field "id" Int -- >     :> Field "name" Text@@ -150,16 +248,14 @@ -- in the following examples: -- -- > data Employee--- > -- > instance Table Employee where--- > -- >   type Name Employee = "employees"--- > -- >   type DBSchema Employee =--- >     Field "id" Int--- >     :> Field "company_id" Int+-- >     Field "company_id" Int+-- >     :> Field "id" Int -- >     :> Field "name" Text -- >     :> Field "salary" (Maybe Int)+-- >     :> Field "quit_date" (Maybe Day) -- >     :> Field "birth_date" Day  -- $query@@ -203,12 +299,13 @@ -- The "Database.Ribbit.PostgreSQL" module provides a -- 'Database.Ribbit.PostgreSQL.query' function: -- --- > query :: (--- >     MonadIO m,--- >     Render query,--- >     ToRow (ArgsType query),--- >     FromRow (ResultType query)--- >   )+-- > query ::+-- >      forall m query.+-- >      ( MonadIO m+-- >      , KnownSymbol (Render query)+-- >      , ToRow (ArgsType query)+-- >      , FromRow (ResultType query)+-- >      ) -- >   => Connection -- >   -> Proxy query -- >   -> ArgsType query@@ -227,7 +324,7 @@ --  -- > sequence_ -- >   [--- >     putStrLn (show name <> " - " <> show sallary)+-- >     putStrLn (show name <> " - " <> show salary) -- >     | (Only name :> Only salary) <- results -- >   ] @@ -283,7 +380,9 @@ -- > type DeleteAllEmployees = DeleteFrom Employee -- > type DeleteEmployeeById = -- >   DeleteFrom Employee--- >   `Where` "id" `Equals` (?)+-- >   `Where`+-- >     "company_id" `Equals` (?)+-- >     `And` "id" `Equals` (?) --  -- Then just execute the query, providing the appropriate query params. -- @@ -309,509 +408,27 @@ -- -- > {- Update an employee's salary (hopefully a raise!). -} -- > type UpdateSalary =--- >   Update--- >     '[--- >       "salary"--- >     ]+-- >   Update '[ "salary" ] -- >   `Where` +-- >     "company_id" `Equals` (?) -- >     "id" `Equals` (?) -- >  -- > ... -- >  -- > let -- >   newSalary :: Int--- >   newSalary = ...+-- >   newSalary = 2 -- > +-- >   targetCompany :: Int+-- >   targetCompany = 1+-- >  -- >   targetEmployee :: Int--- >   targetEmployee = ...+-- >   targetEmployee = 1+-- >  -- > in -- >   execute -- >     conn -- >     (Proxy :: Proxy UpdateSalary)--- >     (Only newSalary :> Only targetEmployee)---{- | "SELECT" constructor, used for starting a @SELECT@ statement. -}-data Select fields---{- |-  "FROM" constructor, used for attaching a SELECT projection to a relation-  in the database.--}-data From proj relation-infixl 6 `From`---{- | "WHERE" constructor, used for attaching conditions to a query. -}-data Where query conditions-infixl 6 `Where`---{- | "=" constructor for conditions. -}-data Equals l r-infix 9 `Equals`---{- | "!=" constructor for conditions. -}-data NotEquals l r-infix 9 `NotEquals`---{- | "<" constructor for conditions. -}-data Lt l r-infix 9 `Lt`---{- | "<=" constructor for conditions. -}-data Lte l r-infix 9 `Lte`---{- | ">" constructor for conditions. -}-data Gt l r-infix 9 `Gt`---{- | ">=" constructor for conditions. -}-data Gte l r-infix 9 `Gte`---{- | "AND" constructor for conditions. -}-data And l r-infixr 8 `And`---{- | "OR" constructor for conditions. -}-data Or l r-infixr 7 `Or`---{- | "AS" constructor, used for attaching a name to a table in a FROM clause. -}-data As relation name-infix 8 `As`---{- | NOT conditional constructor. -}-data Not a---{- | "?" constructor, used to indicate the presence of a query parameter. -}-data (?)---{- |-  Define a field in a database schema, where:--  - @name@: is the name of the database column, expressed as a type-level-    string literal, and--  - @typ@: is the Haskell type whose values get stored in the column.--  E.g:--  - @'Field' "company_name" 'Text'@-  - @'Field' "expiration_date" ('Maybe' 'Data.Time.Day')@---}-data Field name typ---{- |-  String two types together. 'Int' ':>' 'Int' ':>' 'Int' is similar in-  principal to the nested tuple ('Int', ('Int', 'Int')), but looks a-  whole lot nicer when the number of elements becomes large.--  This is how you build up a schema from a collection of 'Field' types.--  E.g.:--  > Field "foo" Int-  > :> Field "bar" Text-  > :> Field "baz" (Maybe Text)--  It also the mechanism by which this library builds up the Haskell-  types for query parameters and resulting rows that get returned. So-  if you have a query that accepts three text query parameters, that-  type represented in Haskell is going to be @('Only' 'Text' ':>' 'Only'-  'Text' ':>' 'Only' 'Text')@.--  If that query returns rows that contain a Text, an Int, and a Text,-  then the type of the rows will be @('Only' 'Text' ':>' 'Only' 'Int'-  ':>' 'Only' 'Text')@.---}-data a :> b = a :> b-  deriving (Eq, Ord, Show)-infixr 5 :>---data Expr a---type family ProjectionType proj schema where-  ProjectionType '[name] schema =-    LookupType name schema schema-  ProjectionType (name:more) schema =-    LookupType name schema schema-    :> ProjectionType more schema--type family LookupType name schema context where-  LookupType name (Field name typ) _ = Only typ-  LookupType name (Field name typ :> _) _ = Only typ-  LookupType name (Field _ typ) context = NotInSchema name context-  LookupType name (_ :> more) context = LookupType name more context-  LookupType name a context = NotInSchema name context---{- |-  Type class for defining your own tables. The primary way for you to-  introduce a new schema is to instantiate this type class for one of-  your types.--  E.g.:--  > data MyTable-  > instance Table MyTable where-  >   type Name MyTable = "my_table"-  >   type DBSchema MyTable =-  >     Field "id" Int-  >     :> Field "my_non_nullable_text_field" Text-  >     :> Field "my_nullable_int_field" (Maybe Int)-    --}-class Table relation where-  type Name relation :: Symbol-  type DBSchema relation--{- | Cross product -}-instance-    (Table table, KnownSymbol name)-  =>-    Table ((table `As` name) : moreTables)-  where-    type DBSchema ((table `As` name) : moreTables) =-      CrossProductSchema ((table `As` name) : moreTables)-    type Name ((table `As` name) : moreTables) =-      CrossProductName ((table `As` name) : moreTables)---{- | Produce the schema of a cross product. -}-type family CrossProductSchema cp where-  CrossProductSchema '[table `as` name] =-    Flatten (-      AliasAs name (DBSchema table)-    )-  CrossProductSchema ((table `As` name) : moreTables) =-    Flatten (-      AliasAs name (DBSchema table)-      :> CrossProductSchema moreTables-    )---{- | Product the renderable "name" of a cross product. -}-type family CrossProductName cp where-  CrossProductName '[table `As` name] = -    Name table-    `AppendSymbol` " as "-    `AppendSymbol` name-  CrossProductName ((table `As` name) : moreTables) = -    Name table-    `AppendSymbol` " as "-    `AppendSymbol` name-    `AppendSymbol` ", "-    `AppendSymbol` CrossProductName moreTables---{- |-  Rename the fields in a given schema to reflect an applied table-  alias. For instance, data Foo--}-type family AliasAs prefix schema where-  AliasAs prefix (Field name typ) =-    Field-      (prefix `AppendSymbol` "." `AppendSymbol` name)-      typ-  AliasAs prefix (Field name typ :> more) =-    Field-      (prefix `AppendSymbol` "." `AppendSymbol` name)-      typ-    :> AliasAs prefix more---{- | Produce the type of rows return by a query. -}-type family ResultType query where-  ResultType (Select fields `From` relation) =-    ProjectionType fields (DBSchema relation)-  ResultType (query `Where` conditions) = ResultType query-  ResultType query =-    TypeError ('Lit.Text "Malformed Query" ':$$: 'ShowType query)---{- |-  Produce the type represeting the placeholder ("?") values in a-  paramaterized query.--}-type family ArgsType query where-  ArgsType (_ `From` relation `Where` conditions) =-    ArgsType (DBSchema relation, conditions)-  ArgsType (DeleteFrom relation `Where` conditions) =-    ArgsType (DBSchema relation, conditions)-  ArgsType (InsertInto relation '[]) =-    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) =-    StripUnit (Flatten (ArgsType (schema, a) :> ArgsType (schema, b)))-  ArgsType (schema, Condition field (?)) =-    ProjectionType '[field] schema-  ArgsType (schema, Condition (?) field) =-    ProjectionType '[field] schema-  ArgsType (schema, Condition l r) =-    If-      (ValidField r schema)-      (If (ValidField l schema) () (NotInSchema l schema))-      (NotInSchema r schema)-  ArgsType (schema, Equals l r) = ArgsType (schema, Condition l r)-  ArgsType (schema, NotEquals l r) = ArgsType (schema, Condition l r)-  ArgsType (schema, Lt l r) = ArgsType (schema, Condition l r)-  ArgsType (schema, Lte l r) = ArgsType (schema, Condition l r)-  ArgsType (schema, Gt l r) = ArgsType (schema, Condition l r)-  ArgsType (schema, Gte l r) = ArgsType (schema, Condition l r)-  ArgsType (schema, Not a) = ArgsType (schema, a)-  ArgsType _ = ()---{- |-  Helper for 'ArgsType'. Reduces the number of equations required, because-  'ArgsType' doesn't actually care about which conditionl operator it-  is inspecting.--}-data Condition l r---type family NotInSchema field schema where-  NotInSchema field schema =-    TypeError (-      'Lit.Text "name ("-      ':<>: 'ShowType field-      ':<>: 'Lit.Text ") not found in schema: "-      ':<>: 'ShowType schema-    )---{- | Type level check to see if the field is actually contained in the schema -}-type family ValidField field schema where-  ValidField name (Field name typ) = 'True-  ValidField name (Field _ typ) = 'False-  ValidField name (a :> b) = ValidField name a || ValidField name b---{- |-  Normalize nested type strings to be right associative. Mainly used to-  help simplify the implementation of other type families.--}-type family Flatten a where-  Flatten ((a :> b) :> c) = Flatten (a :> b :> c)-  Flatten (a :> b) = a :> Flatten b-  Flatten a = a---{- |-  Strip redundant unit types out of a string of types. This is used-  mainly to help simplify the implementation of 'ArgsType'.--}-type family StripUnit a where-  StripUnit (() :> a) = StripUnit a-  StripUnit (a :> ()) = StripUnit a-  StripUnit (a :> b) = a :> StripUnit b-  StripUnit a = a---{- | Like 'Lit.symbolVal', but produce any kind of string-like thing. -}-symbolVal :: (KnownSymbol n, IsString a) => proxy n -> a-symbolVal = fromString . Lit.symbolVal---{- | Render a type-level query as text. -}-class Render query where-  render :: proxy query -> Text--{- SELECT -}-instance (Render fields) => Render (Select fields) where-  render _proxy =-    "SELECT "-    <> render (Proxy @fields)--{- Field list -}-instance (KnownSymbol field, ReflectFields (field:more)) => Render (field:more) where-  render _proxy = T.intercalate "," (reflectFields (Proxy @(field:more)))--{- FROM -}-instance (KnownSymbol (Name relation), Render proj, Table relation) => Render (From proj relation) where-  render _proxy =-    render (Proxy @proj)-    <> " FROM "-    <> symbolVal (Proxy @(Name relation))--{- WHERE -}-instance (Render query, Render conditions) => Render (Where query conditions) where-  render _proxy =-    render (Proxy @query)-    <> " WHERE "-    <> render (Proxy @conditions)--{- Equals -}-instance (Render (Expr l), Render (Expr r)) => Render (Equals l r) where-  render _proxy =-    render (Proxy @(Expr l))-    <> " = "-    <> render (Proxy @(Expr r))--{- Not Equals -}-instance (Render (Expr l), Render (Expr r)) => Render (NotEquals l r) where-  render _proxy =-    render (Proxy @(Expr l))-    <> " != "-    <> render (Proxy @(Expr r))--{- Not -}-instance (Render a) => Render (Not a) where-  render _proxy =-    "not ("-    <> render (Proxy @a)-    <> ")"--{- Gt -}-instance (Render (Expr l), Render (Expr r)) => Render (Gt l r) where-  render _proxy =-    render (Proxy @(Expr l))-    <> " > "-    <> render (Proxy @(Expr r))--{- Gte -}-instance (Render (Expr l), Render (Expr r)) => Render (Gte l r) where-  render _proxy =-    render (Proxy @(Expr l))-    <> " >= "-    <> render (Proxy @(Expr r))--{- Lt -}-instance (Render (Expr l), Render (Expr r)) => Render (Lt l r) where-  render _proxy =-    render (Proxy @(Expr l))-    <> " < "-    <> render (Proxy @(Expr r))--{- Lte -}-instance (Render (Expr l), Render (Expr r)) => Render (Lte l r) where-  render _proxy =-    render (Proxy @(Expr l))-    <> " <= "-    <> render (Proxy @(Expr r))--{- AND -}-instance (Render l, Render r) => Render (And l r) where-  render _proxy =-    "( "-    <> render (Proxy @l)-    <> " AND "-    <> render (Proxy @r)-    <> " )"--{- OR -}-instance (Render l, Render r) => Render (Or l r) where-  render _proxy =-    "( "-    <> render (Proxy @l)-    <> " AND "-    <> render (Proxy @r)-    <> " )"--{- Expr -}-instance Render (Expr (?)) where-  render _proxy = "?"-instance (KnownSymbol a) => Render (Expr a) where-  render _proxy = symbolVal (Proxy @a)--{- (?) -}-instance Render (?) where-  render _proxy = "?"--{- INSERT -}-instance-    (ReflectFields fields, KnownSymbol (Name table))-  =>-    Render (InsertInto table fields)-  where-    render _proxy =-      let-        fields :: [Text]-        fields = reflectFields (Proxy @fields)-      in-        "insert into " <> symbolVal (Proxy @(Name table))-        <> " (" <> 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---{- | Convert a type-level list of strings into a value. -}-class ReflectFields a where-  reflectFields :: proxy a -> [Text]-instance ReflectFields '[] where-  reflectFields _proxy = []-instance (KnownSymbol a, ReflectFields more) => ReflectFields (a:more) where-  reflectFields _proxy = symbolVal (Proxy @a) : reflectFields (Proxy @more)---{- | Delete statement. -}-data DeleteFrom table---{- | Update rows in a table. -}-data Update table fields+-- >     (Only newSalary :> Only targetCompany :> Only targetEmployee)  
+ src/Database/Ribbit/Args.hs view
@@ -0,0 +1,114 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE UndecidableInstances #-}++{- | Translate statement parameters into Haskell types. -}+module Database.Ribbit.Args (+  ArgsType,+  ResultType,+) where+++import Data.Tuple.Only (Only)+import Data.Type.Bool (If)+import Database.Ribbit.Conditions (Where, Equals, NotEquals, Lt, Lte,+  Gt, Gte, Not, And, Or, type (?))+import Database.Ribbit.Delete (DeleteFrom)+import Database.Ribbit.Insert (InsertInto)+import Database.Ribbit.Select (From, Select)+import Database.Ribbit.Table (DBSchema, Flatten, (:>), Field, ValidField,+  NotInSchema)+import Database.Ribbit.Update (Update)+import GHC.TypeLits (TypeError, ErrorMessage(ShowType, (:$$:)))+import qualified GHC.TypeLits as Lit+++{- |+  Produce the type represeting the placeholder ("?") values in a+  paramaterized query.+-}+type family ArgsType query where+  ArgsType (_ `From` relation `Where` conditions) =+    ArgsType (DBSchema relation, conditions)++  ArgsType (InsertInto relation fields) =+    ProjectionType fields (DBSchema relation)++  ArgsType (DeleteFrom relation `Where` conditions) =+    ArgsType (DBSchema relation, conditions)++  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) =+    StripUnit (Flatten (ArgsType (schema, a) :> ArgsType (schema, b)))+  ArgsType (schema, Condition field (?)) =+    ProjectionType '[field] schema+  ArgsType (schema, Condition (?) field) =+    ProjectionType '[field] schema+  ArgsType (schema, Condition l r) =+    If+      (ValidField r schema)+      (If (ValidField l schema) () (NotInSchema l schema))+      (NotInSchema r schema)+  ArgsType (schema, Equals l r) = ArgsType (schema, Condition l r)+  ArgsType (schema, NotEquals l r) = ArgsType (schema, Condition l r)+  ArgsType (schema, Lt l r) = ArgsType (schema, Condition l r)+  ArgsType (schema, Lte l r) = ArgsType (schema, Condition l r)+  ArgsType (schema, Gt l r) = ArgsType (schema, Condition l r)+  ArgsType (schema, Gte l r) = ArgsType (schema, Condition l r)+  ArgsType (schema, Not a) = ArgsType (schema, a)+  ArgsType _ = ()+++{- |+  Helper for 'ArgsType'. Reduces the number of equations required, because+  'ArgsType' doesn't actually care about which conditionl operator it+  is inspecting.+-}+data Condition l r+++{- |+  Strip redundant unit types out of a string of types. This is used+  mainly to help simplify the implementation of 'ArgsType'.+-}+type family StripUnit a where+  StripUnit (() :> a) = StripUnit a+  StripUnit (a :> ()) = StripUnit a+  StripUnit (a :> b) = a :> StripUnit b+  StripUnit a = a+++type family ProjectionType proj schema where+  ProjectionType '[name] schema =+    LookupType name schema schema+  ProjectionType (name:more) schema =+    LookupType name schema schema+    :> ProjectionType more schema+++{- | Produce the type of rows return by a query. -}+type family ResultType query where+  ResultType (Select fields `From` relation) =+    ProjectionType fields (DBSchema relation)+  ResultType (query `Where` conditions) = ResultType query+  ResultType query =+    TypeError ('Lit.Text "Malformed Query" ':$$: 'ShowType query)+++type family LookupType name schema context where+  LookupType name (Field name typ) _ = Only typ+  LookupType name (Field name typ :> _) _ = Only typ+  LookupType name (Field _ typ) context = NotInSchema name context+  LookupType name (_ :> more) context = LookupType name more context+  LookupType name a context = NotInSchema name context++
+ src/Database/Ribbit/Conditions.hs view
@@ -0,0 +1,206 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE UndecidableInstances #-}++{- | SQL statement conditions. -}+module Database.Ribbit.Conditions (+  Where,+  Equals,+  NotEquals,+  Lt,+  Lte,+  Gt,+  Gte,+  And,+  Or,+  Not,+  IsNull,+  NotNull,+  type (?),++  RenderConditions,+  RenderJoinConditions,+) where+++import Database.Ribbit.Table (Validate)+import GHC.TypeLits (Symbol, AppendSymbol, TypeError, ErrorMessage((:<>:),+  ShowType))+import qualified GHC.TypeLits as Lit+++{- | "WHERE" constructor, used for attaching conditions to a query. -}+data Where query conditions+infixl 6 `Where`+++{- | "=" constructor for conditions. -}+data Equals (l :: k1) (r :: k2)+infix 9 `Equals`+++{- | "!=" constructor for conditions. -}+data NotEquals l r+infix 9 `NotEquals`+++{- | "<" constructor for conditions. -}+data Lt l r+infix 9 `Lt`+++{- | "<=" constructor for conditions. -}+data Lte l r+infix 9 `Lte`+++{- | ">" constructor for conditions. -}+data Gt l r+infix 9 `Gt`+++{- | ">=" constructor for conditions. -}+data Gte l r+infix 9 `Gte`+++{- | "AND" constructor for conditions. -}+data And l r+infixr 8 `And`+++{- | "OR" constructor for conditions. -}+data Or (l :: k1) (r :: k2)+infixr 7 `Or`+++{- | NOT conditional constructor. -}+data Not a+++{- | Is a field null? -}+data IsNull (field :: Symbol)+++{- | Is a field not null? -}+data NotNull (field :: Symbol)+++type family RenderConditions a schema where+  RenderConditions (Or l r) schema =+    "( "+    `AppendSymbol` RenderConditions l schema+    `AppendSymbol` " ) OR ("+    `AppendSymbol` RenderConditions r schema+    `AppendSymbol` " )"++  RenderConditions (And l r) schema =+    "( "+    `AppendSymbol` RenderConditions l schema+    `AppendSymbol` " ) AND ( "+    `AppendSymbol` RenderConditions r schema+    `AppendSymbol` " )"++  RenderConditions condition schema = RenderCondition condition schema+++type family RenderCondition condition schema where+  RenderCondition (Equals l r) schema = SimpleCondition schema (Expr l) "=" (Expr r)+  RenderCondition (NotEquals l r) schema = SimpleCondition schema (Expr l) "!=" (Expr r)+  RenderCondition (Lt l r) schema = SimpleCondition schema (Expr l) "<" (Expr r)+  RenderCondition (Lte l r) schema = SimpleCondition schema (Expr l) "<=" (Expr r)+  RenderCondition (Gt l r) schema = SimpleCondition schema (Expr l) ">" (Expr r)+  RenderCondition (Gte l r) schema = SimpleCondition schema (Expr l) ">=" (Expr r)+  RenderCondition (IsNull field) schema = Validate field schema (+      field `AppendSymbol` " IS NULL"+    )+  RenderCondition (NotNull field) schema = Validate field schema (+      field `AppendSymbol` " IS NOT NULL"+    )+  RenderCondition a _ = TypeError ('Lit.Text "Invalid condition: " ':<>: 'ShowType a)+++type family SimpleCondition schema l op r where+  SimpleCondition schema (Expr (?)) op (Expr r) =+    Validate r schema (+      "? "+      `AppendSymbol` op+      `AppendSymbol` " "+      `AppendSymbol` r+    )+  SimpleCondition schema (Expr l) op (Expr (?)) =+    Validate l schema (+      l+      `AppendSymbol` " "+      `AppendSymbol` op+      `AppendSymbol` " ?"+    )+  SimpleCondition schema (Expr l) op (Expr r) =+    Validate l schema (+      Validate r schema (+        l+        `AppendSymbol` " "+        `AppendSymbol` op+        `AppendSymbol` " "+        `AppendSymbol` r+      )+    )+++data Expr (a :: k)+++type family RenderJoinConditions a schema where+  RenderJoinConditions (Or l r) schema =+    "( "+    `AppendSymbol` RenderJoinConditions l schema+    `AppendSymbol` " ) OR ("+    `AppendSymbol` RenderJoinConditions r schema+    `AppendSymbol` " )"++  RenderJoinConditions (And l r) schema =+    "( "+    `AppendSymbol` RenderJoinConditions l schema+    `AppendSymbol` " ) AND ( "+    `AppendSymbol` RenderJoinConditions r schema+    `AppendSymbol` " )"++  RenderJoinConditions condition schema = RenderJoinCondition condition schema+++type family RenderJoinCondition condition schema where+  RenderJoinCondition (Equals l r) schema = ClosedCondition schema (Expr l) "=" (Expr r)+  RenderJoinCondition (NotEquals l r) schema = ClosedCondition schema (Expr l) "!=" (Expr r)+  RenderJoinCondition (Lt l r) schema = ClosedCondition schema (Expr l) "<" (Expr r)+  RenderJoinCondition (Lte l r) schema = ClosedCondition schema (Expr l) "<=" (Expr r)+  RenderJoinCondition (Gt l r) schema = ClosedCondition schema (Expr l) ">" (Expr r)+  RenderJoinCondition (Gte l r) schema = ClosedCondition schema (Expr l) ">=" (Expr r)+  RenderJoinCondition (IsNull field) schema = Validate field schema (+      field `AppendSymbol` " IS NULL"+    )+  RenderJoinCondition (NotNull field) schema = Validate field schema (+      field `AppendSymbol` " IS NOT NULL"+    )+  RenderJoinCondition a _ = TypeError ('Lit.Text "Invalid condition: " ':<>: 'ShowType a)+++{- | A closed condition is one that does not allow query parameters. -}+type family ClosedCondition schema l op r where+  ClosedCondition schema (Expr l) op (Expr r) =+    Validate l schema (+      Validate r schema (+        l+        `AppendSymbol` " "+        `AppendSymbol` op+        `AppendSymbol` " "+        `AppendSymbol` r+      )+    )+++{- | "?" constructor, used to indicate the presence of a query parameter. -}+data (?)++
+ src/Database/Ribbit/Delete.hs view
@@ -0,0 +1,32 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE UndecidableInstances #-}+++{- | Delete statements. -}+module Database.Ribbit.Delete (+  DeleteFrom,+) where+++import Database.Ribbit.Conditions (RenderConditions, Where)+import Database.Ribbit.Render (Render)+import Database.Ribbit.Table (Name, DBSchema)+import GHC.TypeLits (AppendSymbol)+++{- | Delete statement. -}+data DeleteFrom table+++type instance Render (DeleteFrom table) =+  "DELETE FROM "+  `AppendSymbol` Name table++type instance Render (DeleteFrom table `Where` conditions) =+  Render (DeleteFrom table)+  `AppendSymbol` " WHERE "+  `AppendSymbol` RenderConditions conditions (DBSchema table)++
+ src/Database/Ribbit/Insert.hs view
@@ -0,0 +1,47 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE UndecidableInstances #-}++module Database.Ribbit.Insert (+  InsertInto,+) where+++import Database.Ribbit.Render (Render)+import Database.Ribbit.Table (Name)+import GHC.TypeLits (AppendSymbol)+++{- | Insert statement. -}+data InsertInto table fields+++type instance Render (InsertInto table fields) =+  "insert into "+  `AppendSymbol` Name table+  `AppendSymbol` " ("+  `AppendSymbol` RendFieldList fields+  `AppendSymbol`") values ("+  `AppendSymbol` RendPlaceholderList fields+  `AppendSymbol` ");"+++type family RendFieldList a where+  RendFieldList '[field] = field+  RendFieldList (f1:f2:more) =+    f1+    `AppendSymbol` ", "+    `AppendSymbol` RendFieldList (f2:more)++type family RendPlaceholderList a where+  RendPlaceholderList '[field] = "?"+  RendPlaceholderList (f1:f2:more) =+    "?"+    `AppendSymbol` ", "+    `AppendSymbol` RendPlaceholderList (f2:more)++
src/Database/Ribbit/PostgreSQL.hs view
@@ -1,15 +1,15 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE DerivingStrategies #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE DataKinds                  #-}+{-# LANGUAGE DerivingStrategies         #-}+{-# LANGUAGE FlexibleContexts           #-}+{-# LANGUAGE FlexibleInstances          #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE PolyKinds #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeApplications #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE TypeOperators #-}-{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE OverloadedStrings          #-}+{-# LANGUAGE PolyKinds                  #-}+{-# LANGUAGE ScopedTypeVariables        #-}+{-# LANGUAGE TypeApplications           #-}+{-# LANGUAGE TypeFamilies               #-}+{-# LANGUAGE TypeOperators              #-}+{-# LANGUAGE UndecidableInstances       #-}  {- | "postgresql-simple"-backed query ribbit implementation. -} module Database.Ribbit.PostgreSQL (@@ -18,8 +18,8 @@   PsqlType(..),    -- * Performing queries.-  query,   execute,+  query,    -- * Creating tables.   createTable,@@ -42,17 +42,19 @@ import Control.Monad (void) import Control.Monad.IO.Class (MonadIO, liftIO) import Data.Int (Int64)-import Data.Proxy (Proxy(Proxy))+import Data.Proxy (Proxy (Proxy)) import Data.String (fromString, IsString) import Data.Text (Text) import Data.Time (Day)-import Data.Tuple.Only (Only(Only))+import Data.Tuple.Only (Only (Only)) import Data.Type.Bool (If) import Database.PostgreSQL.Simple (Connection) import Database.PostgreSQL.Simple.FromField (FromField) import Database.PostgreSQL.Simple.ToField (Action, ToField)-import Database.Ribbit (Render, render, ArgsType, ResultType, (:>)((:>)),-  Name, Field, DBSchema, ValidField)+import Database.Ribbit.Args (ArgsType, ResultType)+import Database.Ribbit.Render (Render)+import Database.Ribbit.Table ((:>)((:>)), Name, DBSchema, Field,+  ValidField) import GHC.TypeLits (KnownSymbol, TypeError, ErrorMessage((:<>:),   ShowType)) import qualified Data.Text as T@@ -62,43 +64,56 @@ import qualified GHC.TypeLits as Lit  -{- | Execute a query against a PostgreSQL database connection. -}-query :: (-    MonadIO m,-    Render query,-    ToRow (ArgsType query),-    FromRow (ResultType query)-  )-  => Connection-  -> Proxy query-  -> ArgsType query-  -> m [ResultType query]-query conn theQuery args =-  liftIO . (fmap . fmap) unWrap $-    PG.query-      conn -      ((fromString . T.unpack . render) theQuery)-      (Wrap args)-- {- | Execute a statement. -}-execute :: (-    MonadIO m,-    ToRow (ArgsType query),-    Render query-  )+execute ::+     forall m query.+     (MonadIO m, ToRow (ArgsType query), KnownSymbol (Render query))   => Connection   -> Proxy query   -> ArgsType query   -> m Int64-execute conn theQuery args =+execute conn _theQuery args =   liftIO $     PG.execute       conn-      ((fromString . T.unpack . render) theQuery)+      (fromString (symbolVal (Proxy @(Render query))))       (Wrap args)  ++{- | Like 'PGT.ToRow', but defined here to avoid orphan instances. -}+class ToRow a where+  toRow :: a -> [Action]+instance (ToRow a, ToRow b) => ToRow (a :> b) where+  toRow (a :> b) = toRow a ++ toRow b+instance (ToField a) => ToRow (Only a) where+  toRow = PGT.toRow+instance ToRow () where+  toRow = PGT.toRow+  ++{- | Like 'PGF.FromRow', but defined here so we can avoid orphaned instances. -}+class FromRow a where+  fromRow :: PGF.RowParser a+instance (FromRow a, FromRow b) => FromRow (a :> b) where+  fromRow = +    (:>)+      <$> fromRow+      <*> fromRow+instance (FromField a) => FromRow (Only a) where+  fromRow = Only <$> PGF.field+++{- | Wrapper that helps us avoid orphan instances. -}+newtype Wrap a = Wrap {+    unWrap :: a+  }+instance (FromRow a) => PGF.FromRow (Wrap a) where+  fromRow = Wrap <$> fromRow+instance (ToRow a) => PGT.ToRow (Wrap a) where+  toRow = toRow . unWrap++ {- |   Create the indicated table in the database. @@ -188,20 +203,6 @@     tableName = Proxy  -class HasFields a where-  fields :: proxy a -> [Text]-instance (KnownSymbol name) => HasFields (Field name typ) where-  fields _proxy = [symbolVal (Proxy @name)]-instance (KnownSymbol name, HasFields more) =>-    HasFields (Field name typ :> more)-  where-    fields _proxy = symbolVal (Proxy @name) : fields (Proxy @more)-instance HasFields '[] where-  fields _proxy = []-instance (KnownSymbol name, HasFields more) => HasFields (name:more) where-  fields _proxy = symbolVal (Proxy @name) : fields (Proxy @more)-- class HasPsqlTypes a where   psqlTypes :: proxy a -> [Text] instance (HasIsNullable typ, HasPsqlType typ) => HasPsqlTypes (Field name typ) where@@ -233,6 +234,39 @@   psqlType _proxy = "date"  +{- | Make sure the fields in the list are actually part of the schema. -}+type family IsSubset fields schema where+  IsSubset '[] schema = 'True+  IsSubset (field:more) schema =+    If+      (ValidField field schema)+      (IsSubset more schema)+      (+        TypeError (+          'Lit.Text "field "+          ':<>: 'ShowType field+          ':<>: 'Lit.Text " is not part of the schema, so it cannot be\+                          \ used as a component of the primary key."+        )+      )+++{- | Produce a list of field names from a schema. -}+class HasFields a where+  fields :: proxy a -> [Text]+instance (KnownSymbol name) => HasFields (Field name typ) where+  fields _proxy = [symbolVal (Proxy @name)]+instance (KnownSymbol name, HasFields more) =>+    HasFields (Field name typ :> more)+  where+    fields _proxy = symbolVal (Proxy @name) : fields (Proxy @more)+instance HasFields '[] where+  fields _proxy = []+instance (KnownSymbol name, HasFields more) => HasFields (name:more) where+  fields _proxy = symbolVal (Proxy @name) : fields (Proxy @more)+++{- | Figure out if a Haskell type is "nullable" in sql. -} class HasIsNullable a where   isNullable :: proxy a -> Bool instance HasIsNullable (Maybe a) where@@ -256,60 +290,26 @@   deriving newtype (IsString)  -{- | Like 'PGF.FromRow', but defined here so we can avoid orphaned instances. -}-class FromRow a where-  fromRow :: PGF.RowParser a-instance (FromRow a, FromRow b) => FromRow (a :> b) where-  fromRow = -    (:>)-      <$> fromRow-      <*> fromRow-instance (FromField a) => FromRow (Only a) where-  fromRow = Only <$> PGF.field---{- | Like 'PGT.ToRow', but defined here to avoid orphan instances. -}-class ToRow a where-  toRow :: a -> [Action]-instance (ToRow a, ToRow b) => ToRow (a :> b) where-  toRow (a :> b) = toRow a ++ toRow b-instance (ToField a) => ToRow (Only a) where-  toRow = PGT.toRow-instance ToRow () where-  toRow = PGT.toRow-  ---{- | Wrapper that helps us avoid orphan instances. -}-newtype Wrap a = Wrap {-    unWrap :: a-  }-instance (FromRow a) => PGF.FromRow (Wrap a) where-  fromRow = Wrap <$> fromRow-instance (ToRow a) => PGT.ToRow (Wrap a) where-  toRow = toRow . unWrap-- {- | Like 'Lit.symbolVal', but produce any kind of string-like thing. -} symbolVal :: (KnownSymbol n, IsString a) => proxy n -> a symbolVal = fromString . Lit.symbolVal  -{- | Make sure the fields in the list are actually part of the schema. -}-type family IsSubset fields schema where-  IsSubset '[] schema = 'True-  IsSubset (field:more) schema =-    If-      (ValidField field schema)-      (IsSubset more schema)-      (-        TypeError (-          'Lit.Text "field "-          ':<>: 'ShowType field-          ':<>: 'Lit.Text " is not part of the schema, so it cannot be\-                          \ used as a component of the primary key."-        )-      )---+{- | Execute a query against a PostgreSQL database connection. -}+query ::+     forall m query.+     ( MonadIO m+     , KnownSymbol (Render query)+     , ToRow (ArgsType query)+     , FromRow (ResultType query)+     )+  => Connection+  -> Proxy query+  -> ArgsType query+  -> m [ResultType query]+query conn _theQuery args =+  liftIO . (fmap . fmap) unWrap $+    PG.query+      conn +      (fromString (symbolVal (Proxy @(Render query))))+      (Wrap args)
+ src/Database/Ribbit/Render.hs view
@@ -0,0 +1,17 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TypeFamilies #-}+++{- | Render queries. -}+module Database.Ribbit.Render (+  Render+) where+++import GHC.TypeLits (Symbol)+++{- | Render a query. -}+type family Render a :: Symbol++
+ src/Database/Ribbit/Select.hs view
@@ -0,0 +1,177 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE UndecidableInstances #-}++module Database.Ribbit.Select (+  -- * Query Structure+  Select,+  From,+  As,+  On,+  LeftJoin,++  -- * Type families+  RenderFieldList,+  RenderField,+  Expr,+) where+++import Database.Ribbit.Conditions (RenderJoinConditions, Where, RenderConditions)+import Database.Ribbit.Render (Render)+import Database.Ribbit.Table (Name, DBSchema, Field, (:>), Table, Flatten,+  Validate)+import GHC.TypeLits (AppendSymbol, Symbol)+++{- | "SELECT" constructor, used for starting a @SELECT@ statement. -}+data Select fields+++{- |+  "FROM" constructor, used for attaching a SELECT projection to a relation+  in the database.+-}+data From proj relation+infixl 6 `From`+++{- | "AS" constructor, used for attaching a name to a table in a FROM clause. -}+data As relation (name :: Symbol)+infix 9 `As`++{- | Cross Product -}+instance (Table table) => Table ((table `As` alias) : more) where+  type Name ((table `As` alias):more) =+    CrossProductName ((table `As` alias) : more)++  type DBSchema ((table `As` alias) : more) =+    CrossProductSchema ((table `As` alias) : more)+++{- | Product the renderable "name" of a cross product. -}+type family CrossProductName cp where+  CrossProductName '[table `As` name] = +    Name table+    `AppendSymbol` " AS "+    `AppendSymbol` name+  CrossProductName ((table `As` name) : moreTables) = +    Name table+    `AppendSymbol` " AS "+    `AppendSymbol` name+    `AppendSymbol` ", "+    `AppendSymbol` CrossProductName moreTables++{- | Produce the schema of a cross product. -}+type family CrossProductSchema cp where+  CrossProductSchema '[table `as` name] =+    Flatten (+      AliasAs name (DBSchema table)+    )+  CrossProductSchema ((table `As` name) : moreTables) =+    Flatten (+      AliasAs name (DBSchema table)+      :> CrossProductSchema moreTables+    )++{- |+  Rename the fields in a given schema to reflect an applied table+  alias. For instance, data Foo+-}+type family AliasAs prefix schema where+  AliasAs prefix (Field name typ) =+    Field+      (prefix `AppendSymbol` "." `AppendSymbol` name)+      typ+  AliasAs prefix (Field name typ :> more) =+    Field+      (prefix `AppendSymbol` "." `AppendSymbol` name)+      typ+    :> AliasAs prefix more+++{- | "ON" keyword, for joins.  -}+data On join (conditions :: *)+infix 7 `On`+++{- | Left Joins. -}+data LeftJoin left right+infix 8 `LeftJoin`+++type instance Render (From (Select proj) table) =+  "SELECT "+  `AppendSymbol` RenderFieldList proj (DBSchema table)+  `AppendSymbol` " FROM "+  `AppendSymbol` Name table++type family RenderFieldList fields schema where+  RenderFieldList '[field] schema =+    RenderField field schema++  RenderFieldList (f1:f2:more) schema =+    RenderField f1 schema+    `AppendSymbol` ", "+    `AppendSymbol` RenderFieldList (f2:more) schema++type family RenderField field schema where+  RenderField field schema =+    Validate field schema field+++data Expr (a :: k)++instance Table (((l `As` lname) `LeftJoin` (r `As` rname)) `On` conditions) where+  type Name (((l `As` lname) `LeftJoin` (r `As` rname)) `On` conditions) =+    Name l+    `AppendSymbol` " AS "+    `AppendSymbol` lname+    `AppendSymbol` " LEFT JOIN "+    `AppendSymbol` Name r+    `AppendSymbol` " AS "+    `AppendSymbol` rname+    `AppendSymbol` " ON "+    `AppendSymbol`+      RenderJoinConditions+        conditions+        (+          LeftJoinSchema+            (AliasAs lname (DBSchema l))+            (AliasAs rname (DBSchema r))+        )++  type DBSchema (((l `As` lname) `LeftJoin` (r `As` rname)) `On` conditions) =+    LeftJoinSchema+      (AliasAs lname (DBSchema l))+      (AliasAs rname (DBSchema r))+++{- | Produce the schema for a left join. -}+type family LeftJoinSchema l r where+  LeftJoinSchema l r =+    Flatten (l :> Nullable r)+++{- | Make all the fields of a schema nullable. -}+type family Nullable schema where+  Nullable (Field name (Maybe typ)) =+    Field name (Maybe typ)++  Nullable (Field name typ) =+    Field name (Maybe typ)++  Nullable (a :> b) =+    Flatten (Nullable a :> Nullable b)+++type instance Render (proj `From` table `Where` conditions) =+  Render (proj `From` table)+  `AppendSymbol` " WHERE "+  `AppendSymbol` RenderConditions conditions (DBSchema table)++
+ src/Database/Ribbit/Table.hs view
@@ -0,0 +1,123 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE UndecidableInstances #-}++module Database.Ribbit.Table (+  Table(..),+  Field,+  (:>)(..),++  Flatten,+  ValidField,+  Validate,+  NotInSchema,+) where+++import Data.Type.Bool (type (||), If)+import GHC.TypeLits (Symbol, TypeError, ErrorMessage(ShowType, (:<>:)))+import qualified GHC.TypeLits as Lit+++{- |+  Type class for defining your own tables. The primary way for you to+  introduce a new schema is to instantiate this type class for one of+  your types.++  E.g.:++  > data MyTable+  > instance Table MyTable where+  >   type Name MyTable = "my_table"+  >   type DBSchema MyTable =+  >     Field "id" Int+  >     :> Field "my_non_nullable_text_field" Text+  >     :> Field "my_nullable_int_field" (Maybe Int)+    +-}+class Table relation where+  type Name relation :: Symbol+  type DBSchema relation+++{- |+  Define a field in a database schema, where:++  - @name@: is the name of the database column, expressed as a type-level+    string literal, and++  - @typ@: is the Haskell type whose values get stored in the column.++  E.g:++  - @'Field' "company_name" 'Text'@+  - @'Field' "expiration_date" ('Maybe' 'Data.Time.Day')@++-}+data Field name typ+++{- |+  String two types together. 'Int' ':>' 'Int' ':>' 'Int' is similar in+  principal to the nested tuple ('Int', ('Int', 'Int')), but looks a+  whole lot nicer when the number of elements becomes large.++  This is how you build up a schema from a collection of 'Field' types.++  E.g.:++  > Field "foo" Int+  > :> Field "bar" Text+  > :> Field "baz" (Maybe Text)++  It also the mechanism by which this library builds up the Haskell+  types for query parameters and resulting rows that get returned. So+  if you have a query that accepts three text query parameters, that+  type represented in Haskell is going to be @('Only' 'Text' ':>' 'Only'+  'Text' ':>' 'Only' 'Text')@.++  If that query returns rows that contain a Text, an Int, and a Text,+  then the type of the rows will be @('Only' 'Text' ':>' 'Only' 'Int'+  ':>' 'Only' 'Text')@.++-}+data a :> b = a :> b+  deriving (Eq, Ord, Show)+infixr 5 :>++{- |+  Normalize nested type strings to be right associative. Mainly used to+  help simplify the implementation of other type families.+-}+type family Flatten a where+  Flatten ((a :> b) :> c) = Flatten (a :> b :> c)+  Flatten (a :> b) = a :> Flatten b+  Flatten a = a+++{- | Type level check to see if the field is actually contained in the schema -}+type family ValidField field schema where+  ValidField name (Field name typ) = 'True+  ValidField name (Field _ typ) = 'False+  ValidField name (a :> b) = ValidField name a || ValidField name b+++type Validate field schema result =+  If (ValidField field schema)+    result+    (NotInSchema field schema)+++type family NotInSchema field schema where+  NotInSchema field schema =+    TypeError (+      'Lit.Text "name ("+      ':<>: 'ShowType field+      ':<>: 'Lit.Text ") not found in schema: "+      ':<>: 'ShowType schema+    )+
+ src/Database/Ribbit/Update.hs view
@@ -0,0 +1,67 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE UndecidableInstances #-}+++{- | Update statements. -}+module Database.Ribbit.Update (+  Update,+) where+++import Database.Ribbit.Conditions (RenderConditions, Where)+import Database.Ribbit.Render (Render)+import Database.Ribbit.Table (Name, DBSchema)+import GHC.TypeLits (AppendSymbol, Symbol)+++{- | Update statement. -}+data Update table (fields :: [Symbol])+++type instance Render (Update table fields) =+  "UPDATE "+  `AppendSymbol` Name table+  `AppendSymbol` " SET "+  `AppendSymbol` RenderUpdateFields fields++type instance Render (Update table fields `Where` conditions) =+  Render (Update table fields)+  `AppendSymbol` " WHERE "+  `AppendSymbol` RenderConditions conditions (DBSchema table)+++type family RenderUpdateFields fields where+  RenderUpdateFields '[field] =+    field `AppendSymbol` " = ?"+  RenderUpdateFields (field:more) =+    field+    `AppendSymbol` " = ?, "+    `AppendSymbol` RenderUpdateFields more++-- {- 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))