{-# OPTIONS_GHC -fwarn-incomplete-patterns #-} module Database.Beam.Query ( -- * Query type module Database.Beam.Query.Types -- ** Query expression contexts -- | A context is a type-level value that signifies where an expression can -- be used. For example, 'QExpr' corresponds to 'QGenExpr's that result in -- values. In reality, 'QExpr' is really 'QGenExpr' parameterized over the -- 'QValueContext'. Similarly, 'QAgg' represents expressions that contain -- aggregates, but it is just 'QGenExpr' parameterized over -- 'QAggregateContext' , QAggregateContext, QGroupingContext, QValueContext , QWindowingContext, QWindowFrameContext , QueryableSqlSyntax , QGenExprTable, QExprTable , module Database.Beam.Query.Combinators , module Database.Beam.Query.Extensions , module Database.Beam.Query.Relationships -- * Operators , module Database.Beam.Query.Operator -- ** Unquantified comparison operators , SqlEq(..), SqlOrd(..) -- ** Quantified Comparison Operators #quantified-comparison-operator# , SqlEqQuantified(..), SqlOrdQuantified(..) , QQuantified , anyOf_, allOf_, anyIn_, allIn_ , between_ , in_ , module Database.Beam.Query.Aggregate , module Database.Beam.Query.CustomSQL -- * SQL Command construction and execution -- ** @SELECT@ , SqlSelect(..) , select, lookup , runSelectReturningList , runSelectReturningOne , dumpSqlSelect -- ** @INSERT@ , SqlInsert(..) , insert , runInsert , SqlInsertValues(..) , insertExpressions , insertValues , insertFrom -- ** @UPDATE@ , SqlUpdate(..) , update, save , runUpdate -- ** @DELETE@ , SqlDelete(..) , delete , runDelete ) where import Prelude hiding (lookup) import Database.Beam.Query.Aggregate import Database.Beam.Query.Combinators import Database.Beam.Query.CustomSQL import Database.Beam.Query.Extensions import Database.Beam.Query.Internal import Database.Beam.Query.Operator import Database.Beam.Query.Ord import Database.Beam.Query.Relationships import Database.Beam.Query.Types (QGenExpr) -- hide QGenExpr constructor import Database.Beam.Query.Types hiding (QGenExpr) import Database.Beam.Backend.Types import Database.Beam.Backend.SQL import Database.Beam.Backend.SQL.Builder import Database.Beam.Schema.Tables import Control.Monad.Identity import Control.Monad.Writer -- * Query data QueryInaccessible -- | A version of the table where each field is a 'QGenExpr' type QGenExprTable ctxt syntax tbl = forall s. tbl (QGenExpr ctxt syntax s) type QExprTable syntax tbl = QGenExprTable QValueContext syntax tbl -- * SELECT -- | Represents a select statement over the syntax 'select' that will return -- rows of type 'a'. newtype SqlSelect select a = SqlSelect select type QueryableSqlSyntax cmd = ( IsSql92Syntax cmd , Sql92SanityCheck cmd , HasQBuilder (Sql92SelectSyntax cmd) ) -- | Build a 'SqlSelect' for the given 'Q'. select :: forall syntax db res. ( ProjectibleInSelectSyntax syntax res , IsSql92SelectSyntax syntax , HasQBuilder syntax ) => Q syntax db QueryInaccessible res -> SqlSelect syntax (QExprToIdentity res) select q = SqlSelect (buildSqlQuery "t" q) -- | Convenience function to generate a 'SqlSelect' that looks up a table row -- given a primary key. lookup :: ( HasQBuilder syntax , Sql92SelectSanityCheck syntax , SqlValableTable (PrimaryKey table) (Sql92SelectExpressionSyntax syntax) , HasSqlValueSyntax (Sql92ExpressionValueSyntax (Sql92SelectExpressionSyntax syntax)) Bool , Beamable table, Table table , Database db ) => DatabaseEntity be db (TableEntity table) -> PrimaryKey table Identity -> SqlSelect syntax (table Identity) lookup tbl tblKey = select $ filter_ (\t -> pk t ==. val_ tblKey) $ all_ tbl -- | Run a 'SqlSelect' in a 'MonadBeam' and get the results as a list runSelectReturningList :: (IsSql92Syntax cmd, MonadBeam cmd be hdl m, FromBackendRow be a) => SqlSelect (Sql92SelectSyntax cmd) a -> m [ a ] runSelectReturningList (SqlSelect s) = runReturningList (selectCmd s) -- | Run a 'SqlSelect' in a 'MonadBeam' and get the unique result, if there is -- one. Both no results as well as more than one result cause this to return -- 'Nothing'. runSelectReturningOne :: (IsSql92Syntax cmd, MonadBeam cmd be hdl m, FromBackendRow be a) => SqlSelect (Sql92SelectSyntax cmd) a -> m (Maybe a) runSelectReturningOne (SqlSelect s) = runReturningOne (selectCmd s) -- | Use a special debug syntax to print out an ANSI Standard @SELECT@ statement -- that may be generated for a given 'Q'. dumpSqlSelect :: ProjectibleInSelectSyntax SqlSyntaxBuilder res => Q SqlSyntaxBuilder db QueryInaccessible res -> IO () dumpSqlSelect q = let SqlSelect s = select q in putStrLn (renderSql s) -- * INSERT -- | Represents a SQL @INSERT@ command that has not yet been run newtype SqlInsert syntax = SqlInsert syntax -- | Generate a 'SqlInsert' given a table and a source of values. insert :: IsSql92InsertSyntax syntax => DatabaseEntity be db (TableEntity table) -- ^ Table to insert into -> SqlInsertValues (Sql92InsertValuesSyntax syntax) table -- ^ Values to insert. See 'insertValues', 'insertExpressions', and 'insertFrom' for possibilities. -> SqlInsert syntax insert (DatabaseEntity (DatabaseTable tblNm tblSettings)) (SqlInsertValues vs) = SqlInsert (insertStmt tblNm tblFields vs) where tblFields = allBeamValues (\(Columnar' f) -> _fieldName f) tblSettings -- | Run a 'SqlInsert' in a 'MonadBeam' runInsert :: (IsSql92Syntax cmd, MonadBeam cmd be hdl m) => SqlInsert (Sql92InsertSyntax cmd) -> m () runInsert (SqlInsert i) = runNoReturn (insertCmd i) -- | Represents a source of values that can be inserted into a table shaped like -- 'tbl'. newtype SqlInsertValues insertValues (tbl :: (* -> *) -> *) = SqlInsertValues insertValues -- | Build a 'SqlInsertValues' from series of expressions insertExpressions :: forall syntax table. ( Beamable table , IsSql92InsertValuesSyntax syntax ) => (forall s. [ table (QExpr (Sql92InsertValuesExpressionSyntax syntax) s) ]) -> SqlInsertValues syntax table insertExpressions tbls = SqlInsertValues $ insertSqlExpressions (map mkSqlExprs tbls) where mkSqlExprs :: forall s. table (QExpr (Sql92InsertValuesExpressionSyntax syntax) s) -> [Sql92InsertValuesExpressionSyntax syntax] mkSqlExprs = allBeamValues (\(Columnar' (QExpr x)) -> x "t") -- | Build a 'SqlInsertValues' from concrete table values insertValues :: forall table syntax. ( Beamable table , IsSql92InsertValuesSyntax syntax , FieldsFulfillConstraint (HasSqlValueSyntax (Sql92ExpressionValueSyntax (Sql92InsertValuesExpressionSyntax syntax))) table) => [ table Identity ] -> SqlInsertValues syntax table insertValues x = insertExpressions (map val_ x :: forall s. [table (QExpr (Sql92InsertValuesExpressionSyntax syntax) s) ]) -- | Build a 'SqlInsertValues' from a 'SqlSelect' that returns the same table insertFrom :: IsSql92InsertValuesSyntax syntax => SqlSelect (Sql92InsertValuesSelectSyntax syntax) (table Identity) -> SqlInsertValues syntax table insertFrom (SqlSelect s) = SqlInsertValues (insertFromSql s) -- * UPDATE -- | Represents a SQL @UPDATE@ statement for the given @table@. newtype SqlUpdate syntax (table :: (* -> *) -> *) = SqlUpdate syntax -- | Build a 'SqlUpdate' given a table, a list of assignments, and a way to -- build a @WHERE@ clause. -- -- See the '(<-.)' operator for ways to build assignments. The argument to the -- second argument is a the table parameterized over 'QField', which -- represents the left hand side of assignments. Sometimes, you'd like to also -- get the current value of a particular column. You can use the 'current_' -- function to convert a 'QField' to a 'QExpr'. update :: ( Beamable table , IsSql92UpdateSyntax syntax) => DatabaseEntity be db (TableEntity table) -- ^ The table to insert into -> (forall s. table (QField s) -> [ QAssignment (Sql92UpdateFieldNameSyntax syntax) (Sql92UpdateExpressionSyntax syntax) s ]) -- ^ A sequence of assignments to make. -> (forall s. table (QExpr (Sql92UpdateExpressionSyntax syntax) s) -> QExpr (Sql92UpdateExpressionSyntax syntax) s Bool) -- ^ Build a @WHERE@ clause given a table containing expressions -> SqlUpdate syntax table update (DatabaseEntity (DatabaseTable tblNm tblSettings)) mkAssignments mkWhere = SqlUpdate (updateStmt tblNm assignments (Just (where_ "t"))) where assignments = concatMap (\(QAssignment as) -> as) (mkAssignments tblFields) QExpr where_ = mkWhere tblFieldExprs tblFields = changeBeamRep (\(Columnar' (TableField name)) -> Columnar' (QField tblNm name)) tblSettings tblFieldExprs = changeBeamRep (\(Columnar' (QField _ nm)) -> Columnar' (QExpr (pure (fieldE (unqualifiedField nm))))) tblFields -- | Generate a 'SqlUpdate' that will update the given table with the given value. -- -- The SQL @UPDATE@ that is generated will set every non-primary key field for -- the row where each primary key field is exactly what is given. -- -- Note: This is a pure SQL @UPDATE@ command. This does not upsert or merge values. save :: forall table syntax be db. ( Table table , IsSql92UpdateSyntax syntax , SqlValableTable (PrimaryKey table) (Sql92UpdateExpressionSyntax syntax) , SqlValableTable table (Sql92UpdateExpressionSyntax syntax) , HasSqlValueSyntax (Sql92ExpressionValueSyntax (Sql92UpdateExpressionSyntax syntax)) Bool ) => DatabaseEntity be db (TableEntity table) -- ^ Table to update -> table Identity -- ^ Value to set to -> SqlUpdate syntax table save tbl@(DatabaseEntity (DatabaseTable _ tblSettings)) v = update tbl (\(tblField :: table (QField s)) -> execWriter $ zipBeamFieldsM (\(Columnar' field) c@(Columnar' value) -> do when (qFieldName field `notElem` primaryKeyFieldNames) $ tell [ field <-. value ] pure c) tblField (val_ v :: table (QExpr (Sql92UpdateExpressionSyntax syntax) s))) (\tblE -> primaryKey tblE ==. val_ (primaryKey v)) where primaryKeyFieldNames = allBeamValues (\(Columnar' (TableField fieldNm)) -> fieldNm) (primaryKey tblSettings) -- | Run a 'SqlUpdate' in a 'MonadBeam'. runUpdate :: (IsSql92Syntax cmd, MonadBeam cmd be hdl m) => SqlUpdate (Sql92UpdateSyntax cmd) tbl -> m () runUpdate (SqlUpdate u) = runNoReturn (updateCmd u) -- * DELETE -- | Represents a SQL @DELETE@ statement for the given @table@ newtype SqlDelete syntax (table :: (* -> *) -> *) = SqlDelete syntax -- | Build a 'SqlDelete' from a table and a way to build a @WHERE@ clause delete :: IsSql92DeleteSyntax delete => DatabaseEntity be db (TableEntity table) -- ^ Table to delete from -> (forall s. table (QExpr (Sql92DeleteExpressionSyntax delete) s) -> QExpr (Sql92DeleteExpressionSyntax delete) s Bool) -- ^ Build a @WHERE@ clause given a table containing expressions -> SqlDelete delete table delete (DatabaseEntity (DatabaseTable tblNm tblSettings)) mkWhere = SqlDelete (deleteStmt tblNm (Just (where_ "t"))) where QExpr where_ = mkWhere (changeBeamRep (\(Columnar' (TableField name)) -> Columnar' (QExpr (pure (fieldE (unqualifiedField name))))) tblSettings) -- | Run a 'SqlDelete' in a 'MonadBeam' runDelete :: (IsSql92Syntax cmd, MonadBeam cmd be hdl m) => SqlDelete (Sql92DeleteSyntax cmd) table -> m () runDelete (SqlDelete d) = runNoReturn (deleteCmd d)