hasqlator-mysql 0.0.8 → 0.0.9
raw patch · 4 files changed
+300/−143 lines, 4 filesdep +optics-corePVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependencies added: optics-core
API changes (from Hackage documentation)
- Database.MySQL.Hasqlator: values_ :: [QueryBuilder] -> Selector ()
- Database.MySQL.Hasqlator.Typed: Tbl :: (forall fieldNull exprNull a. CheckExprNullable (JoinNullable joinType fieldNull) exprNull => Field table database fieldNull a -> Expression exprNull a) -> Tbl table database (joinType :: JoinType)
- Database.MySQL.Hasqlator.Typed: instance Database.MySQL.Hasqlator.ToQueryBuilder (Database.MySQL.Hasqlator.Typed.Query database (Database.MySQL.Hasqlator.Selector a))
- Database.MySQL.Hasqlator.Typed: instance Database.MySQL.Hasqlator.Typed.Insertable fieldNull a b => Database.MySQL.Hasqlator.Typed.InsertGeneric tbl db (GHC.Generics.K1 r (Database.MySQL.Hasqlator.Typed.Field tbl db fieldNull a) ()) (GHC.Generics.K1 r b ())
- Database.MySQL.Hasqlator.Typed: newtype Tbl table database (joinType :: JoinType)
+ Database.MySQL.Hasqlator: exprInto :: (a -> QueryBuilder) -> Text -> Insertor a
+ Database.MySQL.Hasqlator: false_ :: QueryBuilder
+ Database.MySQL.Hasqlator: infix 4 /=.
+ Database.MySQL.Hasqlator: infixl 6 -.
+ Database.MySQL.Hasqlator: infixl 7 /.
+ Database.MySQL.Hasqlator: infixr 3 ||.
+ Database.MySQL.Hasqlator: infixr 5 ++.
+ Database.MySQL.Hasqlator: insertUpdateValues :: QueryBuilder -> Insertor a -> [(QueryBuilder, QueryBuilder)] -> [a] -> Command
+ Database.MySQL.Hasqlator: isNotNull :: QueryBuilder -> QueryBuilder
+ Database.MySQL.Hasqlator: isNull :: QueryBuilder -> QueryBuilder
+ Database.MySQL.Hasqlator: notIn_ :: QueryBuilder -> [QueryBuilder] -> QueryBuilder
+ Database.MySQL.Hasqlator: rawValues :: [QueryBuilder] -> Selector [MySQLValue]
+ Database.MySQL.Hasqlator: rawValues_ :: [QueryBuilder] -> Selector ()
+ Database.MySQL.Hasqlator: true_ :: QueryBuilder
+ Database.MySQL.Hasqlator: update :: [QueryBuilder] -> [(QueryBuilder, QueryBuilder)] -> QueryClauses -> Command
+ Database.MySQL.Hasqlator.Typed: (:=) :: Field table database nullable a -> Expression nullable a -> Updator table database
+ Database.MySQL.Hasqlator.Typed: Alias :: (forall fieldNull a. Field table database fieldNull a -> Expression (JoinNullable joinType fieldNull) a) -> Alias table database (joinType :: JoinType)
+ Database.MySQL.Hasqlator.Typed: data Updator table database
+ Database.MySQL.Hasqlator.Typed: executeCommand :: MySQLConn -> Command -> IO OK
+ Database.MySQL.Hasqlator.Typed: executeQuery :: MySQLConn -> Query database (Selector a) -> IO [a]
+ Database.MySQL.Hasqlator.Typed: in_ :: Expression nullable a -> [Expression nullable a] -> Expression nullable Bool
+ Database.MySQL.Hasqlator.Typed: infix 0 :=
+ Database.MySQL.Hasqlator.Typed: infix 4 <=.
+ Database.MySQL.Hasqlator.Typed: infixr 3 ||.
+ Database.MySQL.Hasqlator.Typed: insertUpdateValues :: Table table database -> Insertor table database a -> (Alias table database 'InnerJoined -> Alias table database 'InnerJoined -> [Updator table database]) -> [a] -> Command
+ Database.MySQL.Hasqlator.Typed: instance Database.MySQL.Hasqlator.ToSql b => Database.MySQL.Hasqlator.Typed.InsertGeneric tbl db (GHC.Generics.K1 r (Database.MySQL.Hasqlator.Typed.Field tbl db 'Database.MySQL.Hasqlator.Typed.NotNull a) ()) (GHC.Generics.K1 r b ())
+ Database.MySQL.Hasqlator.Typed: instance Database.MySQL.Hasqlator.ToSql b => Database.MySQL.Hasqlator.Typed.InsertGeneric tbl db (GHC.Generics.K1 r (Database.MySQL.Hasqlator.Typed.Field tbl db 'Database.MySQL.Hasqlator.Typed.Nullable a) ()) (GHC.Generics.K1 r (GHC.Maybe.Maybe b) ())
+ Database.MySQL.Hasqlator.Typed: isNotNull :: Expression 'Nullable a -> Expression 'NotNull Bool
+ Database.MySQL.Hasqlator.Typed: isNull :: Expression nullable a -> Expression 'NotNull Bool
+ Database.MySQL.Hasqlator.Typed: maybeLensInto :: ToSql b => Getter a (Maybe b) -> Field table database 'Nullable fieldType -> Insertor table database a
+ Database.MySQL.Hasqlator.Typed: maybeOpticInto :: (ToSql b, Is k A_Getter) => Optic' k is a (Maybe b) -> Field table database 'Nullable fieldType -> Insertor table database a
+ Database.MySQL.Hasqlator.Typed: newtype Alias table database (joinType :: JoinType)
+ Database.MySQL.Hasqlator.Typed: notIn_ :: Expression nullable a -> [Expression nullable a] -> Expression nullable Bool
+ Database.MySQL.Hasqlator.Typed: notNull :: Expression 'NotNull a -> Expression 'NotNull a
+ Database.MySQL.Hasqlator.Typed: opticInto :: (ToSql b, Is k A_Getter) => Optic' k is a b -> Field table database 'NotNull fieldType -> Insertor table database a
+ Database.MySQL.Hasqlator.Typed: orNull :: Expression nullable a -> (Expression 'NotNull a -> Expression 'NotNull Bool) -> Expression 'NotNull Bool
+ Database.MySQL.Hasqlator.Typed: unlessNull :: Expression nullable a -> (Expression 'NotNull a -> Expression 'NotNull Bool) -> Expression 'NotNull Bool
+ Database.MySQL.Hasqlator.Typed: untypeQuery :: Query database (Selector a) -> Query a
+ Database.MySQL.Hasqlator.Typed: update :: Table table database -> (Alias table database 'InnerJoined -> Query database [Updator table database]) -> Command
- Database.MySQL.Hasqlator: values :: [QueryBuilder] -> Selector [MySQLValue]
+ Database.MySQL.Hasqlator: values :: QueryBuilder -> QueryBuilder
- Database.MySQL.Hasqlator.Typed: (@@) :: CheckExprNullable (JoinNullable joinType fieldNull) exprNull => Tbl table database (joinType :: JoinType) -> Field table database fieldNull a -> Expression exprNull a
+ Database.MySQL.Hasqlator.Typed: (@@) :: Alias table database (joinType :: JoinType) -> Field table database fieldNull a -> Expression (JoinNullable joinType fieldNull) a
- Database.MySQL.Hasqlator.Typed: [getTableAlias] :: Tbl table database (joinType :: JoinType) -> forall fieldNull exprNull a. CheckExprNullable (JoinNullable joinType fieldNull) exprNull => Field table database fieldNull a -> Expression exprNull a
+ Database.MySQL.Hasqlator.Typed: [getTableAlias] :: Alias table database (joinType :: JoinType) -> forall fieldNull a. Field table database fieldNull a -> Expression (JoinNullable joinType fieldNull) a
- Database.MySQL.Hasqlator.Typed: exprInto :: CheckExprNullable exprNullable fieldNullable => Expression exprNullable a -> Field table database fieldNullable a -> Into database table
+ Database.MySQL.Hasqlator.Typed: exprInto :: Expression nullable a -> Field table database nullable a -> Into database table
- Database.MySQL.Hasqlator.Typed: from :: Table table database -> Query database (Tbl table database 'InnerJoined)
+ Database.MySQL.Hasqlator.Typed: from :: Table table database -> Query database (Alias table database 'InnerJoined)
- Database.MySQL.Hasqlator.Typed: innerJoin :: Table table database -> (Tbl table database 'InnerJoined -> Expression nullable Bool) -> Query database (Tbl table database 'InnerJoined)
+ Database.MySQL.Hasqlator.Typed: innerJoin :: Table table database -> (Alias table database 'InnerJoined -> Expression nullable Bool) -> Query database (Alias table database 'InnerJoined)
- Database.MySQL.Hasqlator.Typed: insertOne :: Insertable fieldNull fieldType a => Field table database fieldNull fieldType -> Insertor table database a
+ Database.MySQL.Hasqlator.Typed: insertOne :: ToSql a => Field table database 'NotNull fieldType -> Insertor table database a
- Database.MySQL.Hasqlator.Typed: into :: Insertable fieldNull fieldType b => (a -> b) -> Field table database fieldNull fieldType -> Insertor table database a
+ Database.MySQL.Hasqlator.Typed: into :: (a -> Expression nullable b) -> Field table database nullable fieldType -> Insertor table database a
- Database.MySQL.Hasqlator.Typed: leftJoin :: Table table database -> (Tbl table database 'LeftJoined -> Expression nullable Bool) -> Query database (Tbl table database 'LeftJoined)
+ Database.MySQL.Hasqlator.Typed: leftJoin :: Table table database -> (Alias table database 'LeftJoined -> Expression nullable Bool) -> Query database (Alias table database 'LeftJoined)
- Database.MySQL.Hasqlator.Typed: lensInto :: Insertable fieldNull fieldType b => Getter a b -> Field table database fieldNull fieldType -> Insertor table database a
+ Database.MySQL.Hasqlator.Typed: lensInto :: ToSql b => Getter a b -> Field table database 'NotNull fieldType -> Insertor table database a
- Database.MySQL.Hasqlator.Typed: where_ :: Expression nullable Bool -> Query database ()
+ Database.MySQL.Hasqlator.Typed: where_ :: Expression 'NotNull Bool -> Query database ()
Files
- hasqlator-mysql.cabal +3/−2
- src/Database/MySQL/Hasqlator.hs +82/−32
- src/Database/MySQL/Hasqlator/Typed.hs +205/−103
- src/Database/MySQL/Hasqlator/Typed/Schema.hs +10/−6
hasqlator-mysql.cabal view
@@ -1,5 +1,5 @@ Name: hasqlator-mysql-Version: 0.0.8+Version: 0.0.9 Synopsis: composable SQL generation Category: Database Copyright: Kristof Bastiaensen (2020)@@ -43,7 +43,8 @@ time, template-haskell, aeson,- pretty-simple+ pretty-simple,+ optics-core >= 0.4 && < 0.5 hs-source-dirs: src Exposed-Modules:
src/Database/MySQL/Hasqlator.hs view
@@ -44,16 +44,20 @@ localTimeSel, timeOfDaySel, diffTimeSel, daySel, byteStringSel, textSel, -- ** other selectors- values, values_,+ rawValues, rawValues_, -- * Expressions subQuery,- arg, fun, op, (>.), (<.), (>=.), (<=.), (+.), (-.), (*.), (/.), (=.), (++.),- (/=.), (&&.), (||.), abs_, negate_, signum_, sum_, rawSql, substr, in_,+ arg, fun, op, isNull, isNotNull, (>.), (<.), (>=.), (<=.), (+.), (-.), (*.),+ (/.), (=.), (++.), (/=.), (&&.), (||.), abs_, negate_, signum_, sum_,+ rawSql, substr, in_, false_, true_, notIn_, values, -- * Insertion- Insertor, insertValues, insertSelect, insertData, skipInsert, into, Getter,- lensInto, insertOne, ToSql,+ Insertor, insertValues, insertUpdateValues, insertSelect, insertData,+ skipInsert, into, exprInto, Getter, lensInto, insertOne, ToSql,++ -- * Updates+ update, -- * Rendering Queries renderStmt, renderPreparedStmt, SQLError(..), QueryBuilder,@@ -238,38 +242,45 @@ mempty = pure mempty data Query a = Query (Selector a) QueryBody-data Command = Update QueryBuilder [(QueryBuilder, QueryBuilder)] QueryBody+data Command = Update [QueryBuilder] [(QueryBuilder, QueryBuilder)] QueryBody | InsertSelect QueryBuilder [QueryBuilder] [QueryBuilder] QueryBody- | forall a.InsertValues QueryBuilder (Insertor a) [a]+ | forall a.InsertValues QueryBuilder (Insertor a)+ (Maybe [(QueryBuilder, QueryBuilder)]) [a] | forall a.Delete (Query a) -- | An @`Insertor` a@ provides a mapping of parts of values of type -- @a@ to columns in the database. Insertors can be combined using `<>`.-data Insertor a = Insertor [Text] (a -> [MySQLValue])+data Insertor a = Insertor [Text] (a -> [QueryBuilder]) data Join = Join JoinType [QueryBuilder] [QueryBuilder] data JoinType = InnerJoin | LeftJoin | RightJoin | OuterJoin +pairQuery :: (QueryBuilder, QueryBuilder) -> QueryBuilder+pairQuery (a, b) = a <> " = " <> b+ instance ToQueryBuilder Command where- toQueryBuilder (Update table setting body) =- let pairQuery (a, b) = a <> " = " <> b- in unwords- [ "UPDATE", table- , "SET", commaSep $ map pairQuery setting- , toQueryBuilder body- ] - - toQueryBuilder (InsertValues (QueryBuilder table _ _)- (Insertor cols convert) values__) =- let builder, valuesB :: Builder- valuesB = commaSep $- map (parentized . commaSep . map mysqlValueBuilder . convert)+ toQueryBuilder (Update tables setting body) =+ unwords+ [ "UPDATE", commaSep tables+ , "SET", commaSep $ map pairQuery setting+ , toQueryBuilder body+ ] ++ toQueryBuilder (InsertValues _ _ _ []) = "SELECT 'nothing to insert'"+ toQueryBuilder (InsertValues table (Insertor cols convert) updates values__) =+ let valuesB = commaSep $+ map (parentized . commaSep . convert) values__- builder = unwords [ "INSERT INTO", table- , parentized $ commaSep $- map (Builder.byteString . Text.encodeUtf8) cols- , "VALUES", valuesB]- in QueryBuilder builder builder DList.empty+ insertStmt = [ "INSERT INTO", table+ , parentized $ commaSep $+ map rawSql cols+ , "VALUES", valuesB]+ in unwords $ insertStmt +++ foldMap (\setting ->+ [ "ON DUPLICATE KEY UPDATE"+ , commaSep (map pairQuery setting)])+ updates+ toQueryBuilder (InsertSelect table cols rows queryBody) = unwords [ "INSERT INTO", table@@ -435,6 +446,12 @@ substr :: QueryBuilder -> QueryBuilder -> QueryBuilder -> QueryBuilder substr field start end = fun "substr" [field, start, end] +infixr 3 &&., ||.+infix 4 <., >., >=., <=., =., /=.+infixr 5 ++.+infixl 6 +., -.+infixl 7 *., /.+ (>.), (<.), (>=.), (<=.), (+.), (-.), (/.), (*.), (=.), (/=.), (++.), (&&.), (||.) :: QueryBuilder -> QueryBuilder -> QueryBuilder@@ -458,10 +475,26 @@ negate_ x = fun "-" [x] sum_ x = fun "sum" [x] +false_, true_ :: QueryBuilder+false_ = rawSql "false"+true_ = rawSql "true" +values :: QueryBuilder -> QueryBuilder+values x = fun "values" [x]++isNull :: QueryBuilder -> QueryBuilder+isNull e = parentized $ e <> " IS NULL"++isNotNull :: QueryBuilder -> QueryBuilder+isNotNull e = parentized $ e <> " IS NOT NULL"++-- | insert an expression+exprInto :: (a -> QueryBuilder) -> Text -> Insertor a+exprInto f s = Insertor [s] (\t -> [f t])+ -- | insert a single value directly insertOne :: ToSql a => Text -> Insertor a-insertOne s = Insertor [s] (\t -> [toSqlValue t])+insertOne s = arg `exprInto` s -- | insert a datastructure class InsertGeneric (fields :: *) (data_ :: *) where@@ -600,28 +633,45 @@ replaceSelect s (Query _ body) = Query s body insertValues :: QueryBuilder -> Insertor a -> [a] -> Command-insertValues = InsertValues+insertValues qb i = InsertValues qb i Nothing +insertUpdateValues :: QueryBuilder+ -> Insertor a+ -> [(QueryBuilder, QueryBuilder)]+ -> [a]+ -> Command+insertUpdateValues qb i u = InsertValues qb i (Just u)+ insertSelect :: QueryBuilder -> [QueryBuilder] -> [QueryBuilder] -> QueryClauses -> Command insertSelect table toColumns fromColumns (QueryClauses clauses) = InsertSelect table toColumns fromColumns $ appEndo clauses emptyQueryBody +update :: [QueryBuilder] -> [(QueryBuilder, QueryBuilder)] -> QueryClauses+ -> Command+update tables assignments (QueryClauses clauses) =+ Update tables assignments $ appEndo clauses emptyQueryBody+ -- | combinator for aliasing columns. as :: QueryBuilder -> QueryBuilder -> QueryBuilder as e1 e2 = e1 <> " AS " <> e2 in_ :: QueryBuilder -> [QueryBuilder] -> QueryBuilder+in_ _ [] = false_ in_ e l = e <> " IN " <> parentized (commaSep l) +notIn_ :: QueryBuilder -> [QueryBuilder] -> QueryBuilder+notIn_ _ [] = true_+notIn_ e l = e <> " NOT IN " <> parentized (commaSep l)+ -- | Read the columns directly as a `MySQLValue` type without conversion.-values :: [QueryBuilder] -> Selector [MySQLValue]-values cols = Selector (DList.fromList cols) $+rawValues :: [QueryBuilder] -> Selector [MySQLValue]+rawValues cols = Selector (DList.fromList cols) $ state $ splitAt (length cols) -- | Ignore the content of the given columns-values_ :: [QueryBuilder] -> Selector ()-values_ cols = () <$ values cols+rawValues_ :: [QueryBuilder] -> Selector ()+rawValues_ cols = () <$ rawValues cols -- selector for any bounded integer type intFromSql :: forall a.(Show a, Bounded a, Integral a)
src/Database/MySQL/Hasqlator/Typed.hs view
@@ -12,36 +12,39 @@ {-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE AllowAmbiguousTypes #-}-{-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE DeriveFunctor #-} module Database.MySQL.Hasqlator.Typed ( -- * Database Types- Table(..), Field(..), Tbl(..), (@@), Nullable (..), JoinType (..),- + Table(..), Field(..), Alias(..), (@@), Nullable (..), JoinType (..), -- * Querying- Query, + Query, untypeQuery, executeQuery, -- * Selectors Selector, sel, selMaybe, -- * Expressions Expression, SomeExpression, someExpr, Operator, - arg, argMaybe, nullable, cast, unsafeCast,- op, fun1, fun2, fun3, (=.), (/=.), (>.), (<.), (>=.), (<=.), (&&.), (||.),- substr, true_, false_,-+ arg, argMaybe, isNull, isNotNull, nullable, notNull, orNull, unlessNull,+ cast, unsafeCast, op, fun1, fun2, fun3, (=.), (/=.), (>.), (<.), (>=.),+ (<=.), (&&.), (||.), substr, true_, false_, in_, notIn_, + -- * Clauses from, fromSubQuery, innerJoin, leftJoin, joinSubQuery, leftJoinSubQuery, where_, groupBy_, having, orderBy, limit, limitOffset, -- * Insertion- Insertor, insertValues, insertSelect, insertData, skipInsert, into,- lensInto, insertOne, exprInto, Into,- + Insertor, insertValues, insertUpdateValues, insertSelect, insertData,+ skipInsert, into,+ lensInto, maybeLensInto, opticInto, maybeOpticInto, insertOne, exprInto,+ Into,++ -- * Update+ Updator(..), update,+ -- * imported from Database.MySQL.Hasqlator- H.Getter, H.ToSql, H.FromSql, subQueryExpr, H.Command+ H.Getter, H.ToSql, H.FromSql, subQueryExpr, H.executeCommand, H.Command ) where import Data.Text (Text)@@ -57,7 +60,6 @@ import qualified Data.Map.Strict as Map import Control.Monad.State import Control.Monad.Reader-import GHC.Exts (Constraint) import GHC.TypeLits as TL import Data.Functor.Contravariant import Control.Applicative@@ -65,36 +67,12 @@ import GHC.Generics hiding (from, Selector) import qualified Database.MySQL.Hasqlator as H import Data.Proxy+import qualified Database.MySQL.Base as MySQL+import Optics.Core hiding (lens) data Nullable = Nullable | NotNull data JoinType = LeftJoined | InnerJoined -type family CheckInsertable (fieldNullable :: Nullable) fieldType a- :: Constraint where- CheckInsertable 'Nullable a (Maybe a) = ()- CheckInsertable 'Nullable a a = ()- CheckInsertable 'NotNull a a = ()- CheckInsertable n t ft =- TypeError ('TL.Text "Cannot insert value of type " ':<>:- 'ShowType t ':<>:- 'TL.Text " into " ':<>:- 'ShowType n ':<>:- 'TL.Text " field of type " ':<>:- 'ShowType ft)--type Insertable nullable field a =- (CheckInsertable nullable field a, H.ToSql a)---- | check if field can be used in nullable context-type family CheckExprNullable (expr :: Nullable) (context :: Nullable)- :: Constraint- where- CheckExprNullable 'Nullable 'Nullable = ()- CheckExprNullable 'Nullable 'NotNull =- TypeError ('Text "A nullable expression can be only used in a nullable context")- -- a NotNull expressions can be used in both contexts- CheckExprNullable 'NotNull _ = ()- -- | check if a field is nullable after being joined type family JoinNullable (leftJoined :: JoinType) (field :: Nullable) :: Nullable@@ -156,14 +134,14 @@ -- | An table alias that can be used inside the Query. The function -- inside the newtype can also be applied directly to create an -- expression from a field.-newtype Tbl table database (joinType :: JoinType) =- Tbl { getTableAlias ::- forall fieldNull exprNull a .- CheckExprNullable (JoinNullable joinType fieldNull) exprNull =>+newtype Alias table database (joinType :: JoinType) =+ Alias { getTableAlias ::+ forall fieldNull a . Field table database fieldNull a ->- Expression exprNull a }+ Expression (JoinNullable joinType fieldNull) a } -newtype Insertor (table :: Symbol) database a = Insertor (H.Insertor a)+newtype Insertor (table :: Symbol) database a =+ Insertor (H.Insertor a) deriving (Monoid, Semigroup, Contravariant) data ClauseState = ClauseState@@ -179,12 +157,6 @@ newtype Query database a = Query (QueryInner a) deriving (Functor, Applicative, Monad) -instance H.ToQueryBuilder (Query database (H.Selector a)) where- toQueryBuilder (Query query) =- let (selector, clauseState) = runState query emptyClauseState- -- TODO: finalize query- in H.toQueryBuilder $ H.select selector $ clausesBuild clauseState- type Operator a b c = forall nullable . (Expression nullable a -> Expression nullable b ->@@ -192,17 +164,29 @@ infixl 9 @@ +untypeQuery :: Query database (Selector a) -> H.Query a+untypeQuery (Query query) =+ let (selector, clauseState) =+ runState (do (Selector sel) <- query; sel) emptyClauseState+ in H.select selector $ clausesBuild clauseState++executeQuery :: MySQL.MySQLConn -> Query database (Selector a) -> IO [a]+executeQuery conn query = H.executeQuery conn (untypeQuery query)+ -- | Create an expression from an aliased table and a field.-(@@) :: CheckExprNullable (JoinNullable joinType fieldNull) exprNull- => Tbl table database (joinType :: JoinType)+(@@) :: Alias table database (joinType :: JoinType) -> Field table database fieldNull a- -> Expression exprNull a+ -> Expression (JoinNullable joinType fieldNull) a (@@) = getTableAlias -mkTableAlias :: Text -> Tbl table database leftJoined-mkTableAlias tableName = Tbl $ \(Field _ fieldName) ->- Expression $ pure $ H.rawSql $ tableName <> "." <> fieldName+mkTableAlias :: Text -> Alias table database leftJoined+mkTableAlias tableName = Alias $ \field ->+ Expression $ pure $ H.rawSql $ tableName <> "." <> fieldName field +emptyAlias :: Alias table database leftJoined+emptyAlias = Alias $ \field ->+ Expression $ pure $ H.rawSql $ fieldName field+ data QueryOrdering = Asc SomeExpression | Desc SomeExpression -- | make a selector from a column@@ -254,6 +238,9 @@ -> Expression nullable Text substr = fun3 H.substr +infixr 3 &&., ||.+infix 4 <., >., >=., <=., =., /=.+ (=.), (/=.), (>.), (<.), (>=.), (<=.) :: H.ToSql a => Operator a a Bool (=.) = op (H.=.) (/=.) = op (H./=.)@@ -266,14 +253,48 @@ (||.) = op (H.||.) (&&.) = op (H.&&.) +isNull :: Expression nullable a -> Expression 'NotNull Bool+isNull (Expression e) = Expression $ H.isNull <$> e++isNotNull :: Expression 'Nullable a -> Expression 'NotNull Bool+isNotNull (Expression e) = Expression $ H.isNotNull <$> e+ true_, false_ :: Expression nullable Bool-true_ = Expression $ pure $ H.rawSql "true"-false_ = Expression $ pure $ H.rawSql "false"+true_ = Expression $ pure $ H.false_+false_ = Expression $ pure $ H.true_ +in_ :: Expression nullable a -> [Expression nullable a]+ -> Expression nullable Bool+in_ e es = Expression $+ liftA2 H.in_ (runExpression e) (traverse runExpression es)++notIn_ :: Expression nullable a -> [Expression nullable a]+ -> Expression nullable Bool+notIn_ e es = Expression $+ liftA2 H.notIn_ (runExpression e) (traverse runExpression es)+ -- | make expression nullable nullable :: Expression nullable a -> Expression 'Nullable a nullable = coerce +-- | ensure expression is not null+notNull :: Expression 'NotNull a -> Expression 'NotNull a+notNull = id++-- | Return a true expression if the given expression is NULL (using+-- the IS NULL sql test), or pass the expression (coerced to+-- 'NotNull) to the given test.+orNull :: Expression nullable a+ -> (Expression 'NotNull a -> Expression 'NotNull Bool)+ -> Expression 'NotNull Bool+orNull e f = isNull e ||. f (coerce e)++-- | Perform test if given expression is not NULL+unlessNull :: Expression nullable a+ -> (Expression 'NotNull a -> Expression 'NotNull Bool)+ -> Expression 'NotNull Bool+unlessNull e f = f (coerce e)+ class Castable a where -- | Safe cast. This uses the SQL CAST function to convert safely -- from one type to another.@@ -349,14 +370,19 @@ unsafeCast :: Expression nullable a -> Expression nullable b unsafeCast = coerce -fieldText :: Field table database nullable a -> Text-fieldText (Field table fieldName) = table <> "." <> fieldName+fieldName :: Field table database nullable a -> Text+fieldName (Field _ fn) = fn -insertOne :: Insertable fieldNull fieldType a- => Field table database fieldNull fieldType+insertOne :: H.ToSql a+ => Field table database 'NotNull fieldType -> Insertor table database a-insertOne = Insertor . H.insertOne. fieldText+insertOne = Insertor . H.insertOne . fieldName +insertOneMaybe :: H.ToSql a+ => Field table database 'Nullable fieldType+ -> Insertor table database (Maybe a)+insertOneMaybe = Insertor . H.insertOne . fieldName+ genFst :: (a :*: b) () -> a () genFst (a :*: _) = a @@ -377,10 +403,15 @@ InsertGeneric tbl db (M1 m1 m2 a ()) (M1 m3 m4 b ()) where insertDataGeneric = contramap unM1 . insertDataGeneric . unM1 -instance Insertable fieldNull a b =>- InsertGeneric tbl db (K1 r (Field tbl db fieldNull a) ()) (K1 r b ()) where- insertDataGeneric = contramap unK1 . insertOne . unK1+instance H.ToSql b =>+ InsertGeneric tbl db (K1 r (Field tbl db 'NotNull a) ()) (K1 r b ())+ where insertDataGeneric = contramap unK1 . insertOne . unK1 +instance H.ToSql b =>+ InsertGeneric tbl db (K1 r (Field tbl db 'Nullable a) ())+ (K1 r (Maybe b) ())+ where insertDataGeneric = contramap unK1 . insertOneMaybe . unK1+ instance InsertGeneric tbl db (K1 r (Insertor tbl db a) ()) (K1 r a ()) where insertDataGeneric = contramap unK1 . unK1 @@ -398,25 +429,76 @@ personInsertor = insertData (name, age) -} -into :: Insertable fieldNull fieldType b- => (a -> b)- -> Field table database fieldNull fieldType- -> Insertor table database a-into f = Insertor . H.into f . fieldText+-- (a -> Expression) -> QueryInner (a -> QueryBuilder)+-- (a -> QueryInner QueryBuilder)+-- a -> queryState -> (queryState, result)+-- queryState -> a -> (query, result)+-- (a -> Expression) -> queryState -> a -> QueryBuilder+-- -lensInto :: Insertable fieldNull fieldType b+into :: (a -> Expression nullable b)+ -> Field table database nullable fieldType+ -> Insertor table database a+into e f =+ Insertor $+ H.exprInto (\x -> evalState (runExpression $ e x) emptyClauseState)+ (fieldName f) ++lensInto :: H.ToSql b => H.Getter a b- -> Field table database fieldNull fieldType+ -> Field table database 'NotNull fieldType -> Insertor table database a-lensInto lens a = Insertor $ H.lensInto lens $ fieldText a+lensInto lens a = Insertor $ H.lensInto lens $ fieldName a +maybeLensInto :: H.ToSql b+ => H.Getter a (Maybe b)+ -> Field table database 'Nullable fieldType+ -> Insertor table database a+maybeLensInto lens a = Insertor $ H.lensInto lens $ fieldName a++opticInto :: (H.ToSql b , Is k A_Getter )+ => Optic' k is a b+ -> Field table database 'NotNull fieldType+ -> Insertor table database a+opticInto getter field = (arg . view getter) `into` field++maybeOpticInto :: (H.ToSql b , Is k A_Getter)+ => Optic' k is a (Maybe b)+ -> Field table database 'Nullable fieldType+ -> Insertor table database a+maybeOpticInto getter field = (argMaybe . view getter) `into` field++tableSql :: Table table database -> H.QueryBuilder+tableSql (Table mbSchema tableName) =+ H.rawSql $ foldMap (<> ".") mbSchema <> tableName+ insertValues :: Table table database -> Insertor table database a -> [a] -> H.Command-insertValues (Table _schema tableName) (Insertor i) =- H.insertValues (H.rawSql tableName) i+insertValues table (Insertor i) =+ H.insertValues (tableSql table) i +valuesAlias :: Alias table database leftJoined+valuesAlias = Alias $ \field ->+ Expression $ pure $ H.values $ H.rawSql $ fieldName field++insertUpdateValues :: Table table database+ -> Insertor table database a+ -> (Alias table database 'InnerJoined ->+ Alias table database 'InnerJoined ->+ [Updator table database])+ -> [a]+ -> H.Command+insertUpdateValues table (Insertor i) mkUpdators =+ H.insertUpdateValues (tableSql table) i updators+ where updators = flip evalState emptyClauseState $ + traverse runUpdator $ mkUpdators emptyAlias valuesAlias+ runUpdator :: Updator table database+ -> QueryInner (H.QueryBuilder, H.QueryBuilder) + runUpdator (field := Expression expr) = do+ (H.rawSql $ fieldName field, ) <$> expr+ newAlias :: Text -> QueryInner Text newAlias prefix = do clsState <- get@@ -429,38 +511,35 @@ clsState { clausesBuild = clausesBuild clsState <> c } from :: Table table database- -> Query database (Tbl table database 'InnerJoined)-from (Table schema tableName) = Query $+ -> Query database (Alias table database 'InnerJoined)+from table@(Table _ tableName) = Query $ do alias <- newAlias (Text.take 1 tableName)- addClauses $ H.from $- H.rawSql (maybe mempty (<> ".") schema <> tableName) `H.as` H.rawSql alias+ addClauses $ H.from $ tableSql table `H.as` H.rawSql alias pure $ mkTableAlias alias innerJoin :: Table table database- -> (Tbl table database 'InnerJoined ->+ -> (Alias table database 'InnerJoined -> Expression nullable Bool)- -> Query database (Tbl table database 'InnerJoined)-innerJoin (Table schema tableName) joinCondition = Query $ do- alias <- newAlias (Text.take 1 tableName)+ -> Query database (Alias table database 'InnerJoined)+innerJoin table@(Table _ tableName) joinCondition = Query $ do+ alias <- newAlias $ Text.take 1 tableName let tblAlias = mkTableAlias alias exprBuilder <- runExpression $ joinCondition tblAlias addClauses $- H.innerJoin [H.rawSql (maybe mempty (<> ".") schema <> tableName) `H.as`- H.rawSql alias]+ H.innerJoin [tableSql table `H.as` H.rawSql alias] [exprBuilder] pure tblAlias leftJoin :: Table table database- -> (Tbl table database 'LeftJoined ->+ -> (Alias table database 'LeftJoined -> Expression nullable Bool)- -> Query database (Tbl table database 'LeftJoined)-leftJoin (Table schema tableName) joinCondition = Query $ do- alias <- newAlias (Text.take 1 tableName)+ -> Query database (Alias table database 'LeftJoined)+leftJoin table@(Table _ tableName) joinCondition = Query $ do+ alias <- newAlias $ Text.take 1 tableName let tblAlias = mkTableAlias alias exprBuilder <- runExpression $ joinCondition tblAlias addClauses $- H.leftJoin [H.rawSql (maybe mempty (<> ".") schema <> tableName) `H.as`- H.rawSql alias]+ H.leftJoin [tableSql table `H.as` H.rawSql alias] [exprBuilder] pure tblAlias @@ -517,7 +596,7 @@ subQueryExpr sq = Expression $ do (subQueryBody, Expression subQuerySelect) <- runAsSubQuery sq selectBuilder <- subQuerySelect- pure $ H.subQuery $ H.select (H.values_ [selectBuilder]) subQueryBody+ pure $ H.subQuery $ H.select (H.rawValues_ [selectBuilder]) subQueryBody -- subJoinBody :: (Generic inExprs,@@ -539,7 +618,7 @@ from' sqExprs outExpr = to' outExprRep selectBuilder <- DList.toList <$> traverse runSomeExpression selectExprs- pure ( H.subQuery $ H.select (H.values_ selectBuilder) subQueryBody+ pure ( H.subQuery $ H.select (H.rawValues_ selectBuilder) subQueryBody , outExpr) joinSubQuery :: (Generic inExprs,@@ -576,7 +655,7 @@ addClauses $ H.from subQueryBody pure outExpr -where_ :: Expression nullable Bool -> Query database ()+where_ :: Expression 'NotNull Bool -> Query database () where_ expr = Query $ do exprBuilder <- runExpression expr addClauses $ H.where_ [exprBuilder]@@ -607,23 +686,46 @@ newtype Into database (table :: Symbol) = Into { runInto :: QueryInner (Text, H.QueryBuilder) } -exprInto :: CheckExprNullable exprNullable fieldNullable- => Expression exprNullable a ->- Field table database fieldNullable a ->+exprInto :: Expression nullable a ->+ Field table database nullable a -> Into database table-exprInto expr (Field _ fieldName) =- Into $ (fieldName,) <$> runExpression expr+exprInto expr field =+ Into $ (fieldName field,) <$> runExpression expr insertSelect :: Table table database -> Query database [Into database table] -> H.Command-insertSelect (Table schema tbl) (Query query) =- H.insertSelect (H.rawSql (maybe mempty (<> ".") schema <> tbl))+insertSelect table (Query query) =+ H.insertSelect (tableSql table) (map (H.rawSql . fst) intos) (map snd intos) clauses where (intos, ClauseState clauses _) = runState (query >>= traverse runInto) emptyClauseState +infix 0 :=++data Updator table database =+ forall nullable a.+ Field table database nullable a := Expression nullable a++update :: Table table database+ -> (Alias table database 'InnerJoined ->+ Query database [Updator table database])+ -> H.Command+update table query =+ H.update [tableSql table]+ updators clauses+ where Query runQuery = query emptyAlias+ (updators, ClauseState clauses _) =+ runState (runQuery >>= traverse runUpdator) emptyClauseState+ runUpdator :: Updator table database+ -> QueryInner (H.QueryBuilder, H.QueryBuilder) + runUpdator (field := Expression expr) = do+ (H.rawSql $ fieldName field, ) <$> expr+ + + + {- TODO:
src/Database/MySQL/Hasqlator/Typed/Schema.hs view
@@ -428,10 +428,14 @@ insertorName = mkName $ insertorNameModifier props ti insertorTypeName = mkName $ insertorTypeModifier props ti insertorField :: ColumnInfo -> Q Exp- insertorField ci = [e| $(sigE- (varE $ mkName $ insertorFieldModifier props ci)- [t| $(conT insertorTypeName) ->- $(columnTHType False ci) |])+ insertorField ci = [e| ($(if columnNullable ci+ then [e| T.argMaybe |]+ else [e| T.arg |])+ .+ $(sigE+ (varE $ mkName $ insertorFieldModifier props ci)+ [t| $(conT insertorTypeName) ->+ $(columnTHType False ci) |])) `T.into` $(varE $ mkName $ fieldsQualifier props <>@@ -441,7 +445,7 @@ makeSelector props dbName ti = sequence [ sigD selectorName- [t| T.Tbl+ [t| T.Alias $(litT $ strTyLit $ getTableName props ti) $(conT dbName) 'T.InnerJoined@@ -451,7 +455,7 @@ |] , funD selectorName [ do alias <- newName "alias"- clause [conP 'T.Tbl [varP alias]]+ clause [conP 'T.Alias [varP alias]] (normalB $ tableSelector alias) [] ]]