{-# OPTIONS_GHC -fwarn-incomplete-patterns #-}
module Database.Beam.Query
(
module Database.Beam.Query.Types
, QAggregateContext, QGroupingContext, QValueContext
, QWindowingContext, QWindowFrameContext
, QGenExprTable, QExprTable
, QAssignment, QField, QFieldAssignment
, QBaseScope
, module Database.Beam.Query.Combinators
, module Database.Beam.Query.Extensions
, module Database.Beam.Query.Relationships
, module Database.Beam.Query.CTE
, module Database.Beam.Query.Extract
, module Database.Beam.Query.Operator
, isTrue_, isNotTrue_
, isFalse_, isNotFalse_
, isUnknown_, isNotUnknown_
, unknownAs_, sqlBool_
, possiblyNullBool_
, fromPossiblyNullBool_
, HasSqlEqualityCheck(..), HasSqlQuantifiedEqualityCheck(..)
, HasTableEquality
, SqlEq(..), SqlOrd(..), SqlIn(..)
, HasSqlInTable(..)
, SqlEqQuantified(..), SqlOrdQuantified(..)
, QQuantified
, anyOf_, allOf_, anyIn_, allIn_
, between_
, module Database.Beam.Query.Aggregate
, module Database.Beam.Query.CustomSQL
, module Database.Beam.Query.DataTypes
, SqlSelect(..)
, select, selectWith, lookup_
, runSelectReturningList
, runSelectReturningOne
, dumpSqlSelect
, SqlInsert(..)
, insert, insertOnly
, runInsert
, SqlInsertValues(..)
, insertExpressions
, insertValues
, insertFrom
, insertData
, SqlUpdate(..)
, update, save
, update', save'
, updateTable, updateTable'
, set, setFieldsTo
, toNewValue, toOldValue, toUpdatedValue
, toUpdatedValueMaybe
, updateRow, updateTableRow
, updateRow', updateTableRow'
, runUpdate
, SqlDelete(..)
, delete
, runDelete ) where
import Prelude hiding (lookup)
import Database.Beam.Query.Aggregate
import Database.Beam.Query.Combinators
import Database.Beam.Query.CTE ( With, ReusableQ, selecting, reuse )
import qualified Database.Beam.Query.CTE as CTE
import Database.Beam.Query.CustomSQL
import Database.Beam.Query.DataTypes
import Database.Beam.Query.Extensions
import Database.Beam.Query.Extract
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)
import Database.Beam.Query.Types hiding (QGenExpr)
import Database.Beam.Backend.SQL
import Database.Beam.Backend.SQL.Builder
import Database.Beam.Schema.Tables
import Control.Monad.Identity
import Control.Monad.Writer
import Control.Monad.State.Strict
import Data.Kind (Type)
import Data.Functor.Const (Const(..))
import Data.Text (Text)
import Data.Proxy
import Lens.Micro ((^.))
data QBaseScope
type QGenExprTable ctxt be s tbl = tbl (QGenExpr ctxt be s)
type QExprTable be s tbl = QGenExprTable QValueContext be s tbl
newtype SqlSelect be a
= SqlSelect (BeamSqlBackendSelectSyntax be)
select :: forall be db res
. ( BeamSqlBackend be, HasQBuilder be, Projectible be res )
=> Q be db QBaseScope res -> SqlSelect be (QExprToIdentity res)
select q =
SqlSelect (buildSqlQuery "t" q)
selectWith :: forall be db res
. ( BeamSqlBackend be, BeamSql99CommonTableExpressionBackend be
, HasQBuilder be, Projectible be res )
=> With be db (Q be db QBaseScope res) -> SqlSelect be (QExprToIdentity res)
selectWith (CTE.With mkQ) =
let (q, (recursiveness, ctes)) = evalState (runWriterT mkQ) 0
in case recursiveness of
CTE.Nonrecursive -> SqlSelect (withSyntax ctes
(buildSqlQuery "t" q))
CTE.Recursive -> SqlSelect (withRecursiveSyntax ctes
(buildSqlQuery "t" q))
lookup_ :: ( Database be db, Table table
, BeamSqlBackend be, HasQBuilder be
, SqlValableTable be (PrimaryKey table)
, HasTableEquality be (PrimaryKey table)
)
=> DatabaseEntity be db (TableEntity table)
-> PrimaryKey table Identity
-> SqlSelect be (table Identity)
lookup_ tbl tblKey =
select $
filter_ (\t -> pk t ==. val_ tblKey) $
all_ tbl
runSelectReturningList ::
(MonadBeam be m, BeamSqlBackend be, FromBackendRow be a) =>
SqlSelect be a -> m [ a ]
runSelectReturningList (SqlSelect s) =
runReturningList (selectCmd s)
runSelectReturningOne ::
(MonadBeam be m, BeamSqlBackend be, FromBackendRow be a) =>
SqlSelect be a -> m (Maybe a)
runSelectReturningOne (SqlSelect s) =
runReturningOne (selectCmd s)
dumpSqlSelect :: Projectible (MockSqlBackend SqlSyntaxBuilder) res
=> Q (MockSqlBackend SqlSyntaxBuilder) db QBaseScope res -> IO ()
dumpSqlSelect q =
let SqlSelect s = select q
in putStrLn (renderSql s)
data SqlInsert be (table :: (Type -> Type) -> Type)
= SqlInsert !(TableSettings table) !(BeamSqlBackendInsertSyntax be)
| SqlInsertNoRows
insertOnly :: ( BeamSqlBackend be, ProjectibleWithPredicate AnyType () Text (QExprToField r) )
=> DatabaseEntity be db (TableEntity table)
-> (table (QField s) -> QExprToField r)
-> SqlInsertValues be r
-> SqlInsert be table
insertOnly _ _ SqlInsertValuesEmpty = SqlInsertNoRows
insertOnly (DatabaseEntity dt@(DatabaseTable {})) mkProj (SqlInsertValues vs) =
SqlInsert (dbTableSettings dt) (insertStmt (tableNameFromEntity dt) proj vs)
where
tblFields = changeBeamRep (\(Columnar' fd) -> Columnar' (QField False (dbTableCurrentName dt) (fd ^. fieldName)))
(dbTableSettings dt)
proj = execWriter (project' (Proxy @AnyType) (Proxy @((), Text))
(\_ _ f -> tell [f] >> pure f)
(mkProj tblFields))
insert :: ( BeamSqlBackend be, ProjectibleWithPredicate AnyType () Text (table (QField s)) )
=> DatabaseEntity be db (TableEntity table)
-> SqlInsertValues be (table (QExpr be s))
-> SqlInsert be table
insert tbl values = insertOnly tbl id values
runInsert :: (BeamSqlBackend be, MonadBeam be m)
=> SqlInsert be table -> m ()
runInsert SqlInsertNoRows = pure ()
runInsert (SqlInsert _ i) = runNoReturn (insertCmd i)
data SqlInsertValues be proj
= SqlInsertValues (BeamSqlBackendInsertValuesSyntax be)
| SqlInsertValuesEmpty
insertExpressions :: forall be table s
. ( BeamSqlBackend be, Beamable table )
=> (forall s'. [ table (QExpr be s') ])
-> SqlInsertValues be (table (QExpr be s))
insertExpressions tbls =
case sqlExprs of
[] -> SqlInsertValuesEmpty
_ -> SqlInsertValues (insertSqlExpressions sqlExprs)
where
sqlExprs = map mkSqlExprs tbls
mkSqlExprs :: forall s'. table (QExpr be s') -> [ BeamSqlBackendExpressionSyntax be ]
mkSqlExprs = allBeamValues (\(Columnar' (QExpr x)) -> x "t")
insertValues :: forall be table s
. ( BeamSqlBackend be, Beamable table
, FieldsFulfillConstraint (BeamSqlBackendCanSerialize be) table )
=> [ table Identity ]
-> SqlInsertValues be (table (QExpr be s))
insertValues x = insertExpressions (map val_ x :: forall s'. [table (QExpr be s') ])
insertData :: forall be r
. ( Projectible be r, BeamSqlBackend be )
=> [ r ] -> SqlInsertValues be r
insertData rows =
case rows of
[] -> SqlInsertValuesEmpty
_ -> SqlInsertValues (insertSqlExpressions (map (\row -> project (Proxy @be) row "t") rows))
insertFrom :: ( BeamSqlBackend be, HasQBuilder be
, Projectible be r )
=> Q be db QBaseScope r
-> SqlInsertValues be r
insertFrom s = SqlInsertValues (insertFromSql (buildSqlQuery "t" s))
data SqlUpdate be (table :: (Type -> Type) -> Type)
= SqlUpdate !(TableSettings table) !(BeamSqlBackendUpdateSyntax be)
| SqlIdentityUpdate
updateImpl :: forall bool table db be
. ( BeamSqlBackend be, Beamable table )
=> DatabaseEntity be db (TableEntity table)
-> (forall s. table (QField s) -> QAssignment be s)
-> (forall s. table (QExpr be s) -> QExpr be s bool)
-> SqlUpdate be table
updateImpl (DatabaseEntity dt@(DatabaseTable {})) mkAssignments mkWhere =
case assignments of
[] -> SqlIdentityUpdate
_ -> SqlUpdate (dbTableSettings dt)
(updateStmt (tableNameFromEntity dt)
assignments (Just (where_ "t")))
where
QAssignment assignments = mkAssignments tblFields
QExpr where_ = mkWhere tblFieldExprs
tblFields = changeBeamRep (\(Columnar' fd) -> Columnar' (QField False (dbTableCurrentName dt) (fd ^. fieldName)))
(dbTableSettings dt)
tblFieldExprs = changeBeamRep (\(Columnar' (QField _ _ nm)) -> Columnar' (QExpr (pure (fieldE (unqualifiedField nm))))) tblFields
update :: ( BeamSqlBackend be, Beamable table )
=> DatabaseEntity be db (TableEntity table)
-> (forall s. table (QField s) -> QAssignment be s)
-> (forall s. table (QExpr be s) -> QExpr be s Bool)
-> SqlUpdate be table
update = updateImpl @Bool
update' :: ( BeamSqlBackend be, Beamable table )
=> DatabaseEntity be db (TableEntity table)
-> (forall s. table (QField s) -> QAssignment be s)
-> (forall s. table (QExpr be s) -> QExpr be s SqlBool)
-> SqlUpdate be table
update' = updateImpl @SqlBool
updateRow :: ( BeamSqlBackend be, Table table
, HasTableEquality be (PrimaryKey table)
, SqlValableTable be (PrimaryKey table) )
=> DatabaseEntity be db (TableEntity table)
-> table Identity
-> (forall s. table (QField s) -> QAssignment be s)
-> SqlUpdate be table
updateRow tbl row assignments =
update tbl assignments (references_ (val_ (pk row)))
updateRow' :: ( BeamSqlBackend be, Table table
, HasTableEquality be (PrimaryKey table)
, SqlValableTable be (PrimaryKey table) )
=> DatabaseEntity be db (TableEntity table)
-> table Identity
-> (forall s. table (QField s) -> QAssignment be s)
-> SqlUpdate be table
updateRow' tbl row assignments =
update' tbl assignments (references_' (val_ (pk row)))
updateTableImpl :: forall bool table db be
. ( BeamSqlBackend be, Beamable table )
=> DatabaseEntity be db (TableEntity table)
-> table (QFieldAssignment be table)
-> (forall s. table (QExpr be s) -> QExpr be s bool)
-> SqlUpdate be table
updateTableImpl tblEntity assignments mkWhere =
let mkAssignments :: forall s. table (QField s) -> QAssignment be s
mkAssignments tblFields =
let tblExprs = changeBeamRep (\(Columnar' fd) -> Columnar' (current_ fd)) tblFields
in execWriter $
zipBeamFieldsM
(\(Columnar' field :: Columnar' (QField s) a)
c@(Columnar' (QFieldAssignment mkAssignment)) ->
case mkAssignment tblExprs of
Nothing -> pure c
Just newValue -> do
tell (field <-. newValue)
pure c)
tblFields assignments
in updateImpl tblEntity mkAssignments mkWhere
updateTable :: forall table db be
. ( BeamSqlBackend be, Beamable table )
=> DatabaseEntity be db (TableEntity table)
-> table (QFieldAssignment be table)
-> (forall s. table (QExpr be s) -> QExpr be s Bool)
-> SqlUpdate be table
updateTable = updateTableImpl @Bool
updateTable' :: forall table db be
. ( BeamSqlBackend be, Beamable table )
=> DatabaseEntity be db (TableEntity table)
-> table (QFieldAssignment be table)
-> (forall s. table (QExpr be s) -> QExpr be s SqlBool)
-> SqlUpdate be table
updateTable' = updateTableImpl @SqlBool
updateTableRow :: ( BeamSqlBackend be, Table table
, HasTableEquality be (PrimaryKey table)
, SqlValableTable be (PrimaryKey table) )
=> DatabaseEntity be db (TableEntity table)
-> table Identity
-> table (QFieldAssignment be table)
-> SqlUpdate be table
updateTableRow tbl row assignments =
updateTable tbl assignments (references_ (val_ (pk row)))
updateTableRow' :: ( BeamSqlBackend be, Table table
, HasTableEquality be (PrimaryKey table)
, SqlValableTable be (PrimaryKey table) )
=> DatabaseEntity be db (TableEntity table)
-> table Identity
-> table (QFieldAssignment be table)
-> SqlUpdate be table
updateTableRow' tbl row assignments =
updateTable' tbl assignments (references_' (val_ (pk row)))
set :: forall table be table'. Beamable table => table (QFieldAssignment be table')
set = changeBeamRep (\_ -> Columnar' (QFieldAssignment (\_ -> Nothing))) (tblSkeleton :: TableSkeleton table)
setFieldsTo :: forall table be table'
. Table table => (forall s. table (QExpr be s)) -> table (QFieldAssignment be table')
setFieldsTo tbl =
runIdentity $
zipBeamFieldsM (\(Columnar' (Const columnIx))
(Columnar' (QExpr newValue)) ->
if columnIx `elem` primaryKeyIndices
then pure $ Columnar' toOldValue
else pure $ Columnar' (toNewValue (QExpr newValue)))
indexedTable tbl
where
indexedTable :: table (Const Int)
indexedTable =
flip evalState 0 $
zipBeamFieldsM (\_ _ -> do
n <- get
put (n + 1)
return (Columnar' (Const n)))
(tblSkeleton :: TableSkeleton table) (tblSkeleton :: TableSkeleton table)
primaryKeyIndices :: [ Int ]
primaryKeyIndices = allBeamValues (\(Columnar' (Const ix)) -> ix) (primaryKey indexedTable)
toNewValue :: (forall s. QExpr be s a) -> QFieldAssignment be table a
toNewValue newVal = toUpdatedValue (\_ -> newVal)
toOldValue :: QFieldAssignment be table a
toOldValue = toUpdatedValueMaybe (\_ -> Nothing)
toUpdatedValue :: (forall s. table (QExpr be s) -> QExpr be s a) -> QFieldAssignment be table a
toUpdatedValue mkNewVal = toUpdatedValueMaybe (Just <$> mkNewVal)
toUpdatedValueMaybe :: (forall s. table (QExpr be s) -> Maybe (QExpr be s a)) -> QFieldAssignment be table a
toUpdatedValueMaybe = QFieldAssignment
save :: forall table be db.
( Table table
, BeamSqlBackend be
, SqlValableTable be (PrimaryKey table)
, SqlValableTable be table
, HasTableEquality be (PrimaryKey table)
)
=> DatabaseEntity be db (TableEntity table)
-> table Identity
-> SqlUpdate be table
save tbl v =
updateTableRow tbl v
(setFieldsTo (val_ v))
save' :: forall table be db.
( Table table
, BeamSqlBackend be
, SqlValableTable be (PrimaryKey table)
, SqlValableTable be table
, HasTableEquality be (PrimaryKey table)
)
=> DatabaseEntity be db (TableEntity table)
-> table Identity
-> SqlUpdate be table
save' tbl v =
updateTableRow' tbl v
(setFieldsTo (val_ v))
runUpdate :: (BeamSqlBackend be, MonadBeam be m)
=> SqlUpdate be tbl -> m ()
runUpdate (SqlUpdate _ u) = runNoReturn (updateCmd u)
runUpdate SqlIdentityUpdate = pure ()
data SqlDelete be (table :: (Type -> Type) -> Type)
= SqlDelete !(TableSettings table) !(BeamSqlBackendDeleteSyntax be)
delete :: forall be db table
. BeamSqlBackend be
=> DatabaseEntity be db (TableEntity table)
-> (forall s. (forall s'. table (QExpr be s')) -> QExpr be s Bool)
-> SqlDelete be table
delete (DatabaseEntity dt@(DatabaseTable {})) mkWhere =
SqlDelete (dbTableSettings dt)
(deleteStmt (tableNameFromEntity dt) alias (Just (where_ "t")))
where
supportsAlias = deleteSupportsAlias (Proxy @(BeamSqlBackendDeleteSyntax be))
tgtName = "delete_target"
alias = if supportsAlias then Just tgtName else Nothing
mkField = if supportsAlias then qualifiedField tgtName else unqualifiedField
QExpr where_ = mkWhere (changeBeamRep (\(Columnar' fd) -> Columnar' (QExpr (pure (fieldE (mkField (fd ^. fieldName))))))
(dbTableSettings dt))
runDelete :: (BeamSqlBackend be, MonadBeam be m)
=> SqlDelete be table -> m ()
runDelete (SqlDelete _ d) = runNoReturn (deleteCmd d)