{-# OPTIONS_GHC -fno-warn-unused-binds -fno-warn-name-shadowing #-} {-# LANGUAGE PolyKinds #-} {-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE CPP #-} -- | Data types for Postgres syntax. Access is given mainly for extension -- modules. The types and definitions here are likely to change. module Database.Beam.Postgres.Syntax ( PgSyntaxF(..), PgSyntaxM , PgSyntax(..) , emit, emitBuilder, escapeString , escapeBytea, escapeIdentifier , pgParens , nextSyntaxStep , PgCommandSyntax(..), PgCommandType(..) , PgSelectSyntax(..), PgSelectSetQuantifierSyntax(..) , PgInsertSyntax(..) , PgDeleteSyntax(..) , PgUpdateSyntax(..) , PgExpressionSyntax(..), PgFromSyntax(..), PgTableNameSyntax(..) , PgComparisonQuantifierSyntax(..) , PgExtractFieldSyntax(..) , PgProjectionSyntax(..), PgGroupingSyntax(..) , PgOrderingSyntax(..), PgValueSyntax(..) , PgTableSourceSyntax(..), PgFieldNameSyntax(..) , PgAggregationSetQuantifierSyntax(..) , PgInsertValuesSyntax(..), PgInsertOnConflictSyntax(..) , PgInsertOnConflictTargetSyntax(..), PgConflictActionSyntax(..) , PgCreateTableSyntax(..), PgTableOptionsSyntax(..), PgColumnSchemaSyntax(..) , PgDataTypeSyntax(..), PgColumnConstraintDefinitionSyntax(..), PgColumnConstraintSyntax(..) , PgTableConstraintSyntax(..), PgMatchTypeSyntax(..), PgReferentialActionSyntax(..) , PgAlterTableSyntax(..), PgAlterTableActionSyntax(..), PgAlterColumnActionSyntax(..) , PgWindowFrameSyntax(..), PgWindowFrameBoundsSyntax(..), PgWindowFrameBoundSyntax(..) , PgSelectLockingClauseSyntax(..) , PgSelectLockingStrength(..) , PgSelectLockingOptions(..) , fromPgSelectLockingClause , pgSelectStmt , defaultPgValueSyntax , PgDataTypeDescr(..) , PgHasEnum(..) , pgCreateExtensionSyntax, pgDropExtensionSyntax , pgCreateEnumSyntax, pgDropTypeSyntax , pgSimpleMatchSyntax , pgSelectSetQuantifierDistinctOn , pgDataTypeJSON , pgTsQueryType, pgTsVectorType , pgJsonType, pgJsonbType, pgUuidType , pgMoneyType , pgTsQueryTypeInfo, pgTsVectorTypeInfo , pgByteaType, pgTextType, pgUnboundedArrayType , pgSerialType, pgSmallSerialType, pgBigSerialType , pgPointType, pgLineType, pgLineSegmentType, pgBoxType , pgQuotedIdentifier, pgSepBy, pgDebugRenderSyntax , pgRenderSyntaxScript, pgBuildAction , pgBinOp, pgCompOp, pgUnOp, pgPostFix , pgTestSyntax , PostgresInaccessible ) where import Database.Beam hiding (insert) import Database.Beam.Backend.SQL import Database.Beam.Migrate import Database.Beam.Migrate.Checks (HasDataTypeCreatedCheck(..)) import Database.Beam.Migrate.SQL.Builder hiding (fromSqlConstraintAttributes) import Database.Beam.Migrate.Serialization import Control.Monad (guard) import Control.Monad.Free import Control.Monad.Free.Church import Data.Aeson (Value, object, (.=)) import Data.Bits import Data.ByteString (ByteString) import Data.ByteString.Builder (Builder, byteString, char8, toLazyByteString) import qualified Data.ByteString.Char8 as B import Data.ByteString.Lazy.Char8 (toStrict) import qualified Data.ByteString.Lazy.Char8 as BL import Data.CaseInsensitive (CI) import qualified Data.CaseInsensitive as CI import Data.Coerce import Data.Functor.Classes import Data.Hashable import Data.Int import Data.Maybe #if !MIN_VERSION_base(4, 11, 0) import Data.Semigroup #endif import Data.Scientific (Scientific) import Data.String (IsString(..), fromString) import qualified Data.Text as T import qualified Data.Text.Encoding as TE import qualified Data.Text.Lazy as TL import Data.Time (LocalTime, UTCTime, TimeOfDay, NominalDiffTime, Day) import Data.UUID.Types (UUID) import Data.Word import qualified Data.Vector as V import qualified Database.PostgreSQL.Simple.ToField as Pg import qualified Database.PostgreSQL.Simple.TypeInfo.Static as Pg import qualified Database.PostgreSQL.Simple.Types as Pg (Oid(..), Binary(..), Null(..)) import qualified Database.PostgreSQL.Simple.Time as Pg (Date, LocalTimestamp, UTCTimestamp) import qualified Database.PostgreSQL.Simple.HStore as Pg (HStoreList, HStoreMap, HStoreBuilder) data PostgresInaccessible -- TODO This probably shouldn't be a free monad... oh well. data PgSyntaxF f where EmitByteString :: ByteString -> f -> PgSyntaxF f EmitBuilder :: Builder -> f -> PgSyntaxF f EscapeString :: ByteString -> f -> PgSyntaxF f EscapeBytea :: ByteString -> f -> PgSyntaxF f EscapeIdentifier :: ByteString -> f -> PgSyntaxF f deriving instance Functor PgSyntaxF instance Eq1 PgSyntaxF where liftEq eq (EmitByteString b1 next1) (EmitByteString b2 next2) = b1 == b2 && next1 `eq` next2 liftEq eq (EmitBuilder b1 next1) (EmitBuilder b2 next2) = toLazyByteString b1 == toLazyByteString b2 && next1 `eq` next2 liftEq eq (EscapeString b1 next1) (EscapeString b2 next2) = b1 == b2 && next1 `eq` next2 liftEq eq (EscapeBytea b1 next1) (EscapeBytea b2 next2) = b1 == b2 && next1 `eq` next2 liftEq eq (EscapeIdentifier b1 next1) (EscapeIdentifier b2 next2) = b1 == b2 && next1 `eq` next2 liftEq _ _ _ = False instance Eq f => Eq (PgSyntaxF f) where (==) = eq1 instance Hashable PgSyntax where hashWithSalt salt (PgSyntax s) = runF s finish step salt where finish _ salt = hashWithSalt salt () step (EmitByteString b hashRest) salt = hashRest (hashWithSalt salt (0 :: Int, b)) step (EmitBuilder b hashRest) salt = hashRest (hashWithSalt salt (1 :: Int, toLazyByteString b)) step (EscapeString b hashRest) salt = hashRest (hashWithSalt salt (2 :: Int, b)) step (EscapeBytea b hashRest) salt = hashRest (hashWithSalt salt (3 :: Int, b)) step (EscapeIdentifier b hashRest) salt = hashRest (hashWithSalt salt (4 :: Int, b)) instance Sql92DisplaySyntax PgSyntax where displaySyntax = BL.unpack . pgRenderSyntaxScript type PgSyntaxM = F PgSyntaxF -- | A piece of Postgres SQL syntax, which may contain embedded escaped byte and -- text sequences. 'PgSyntax' composes monoidally, and may be created with -- 'emit', 'emitBuilder', 'escapeString', 'escapBytea', and 'escapeIdentifier'. newtype PgSyntax = PgSyntax { buildPgSyntax :: PgSyntaxM () } instance Semigroup PgSyntax where a <> b = PgSyntax (buildPgSyntax a >> buildPgSyntax b) instance Monoid PgSyntax where mempty = PgSyntax (pure ()) mappend = (<>) instance Eq PgSyntax where PgSyntax x == PgSyntax y = (fromF x :: Free PgSyntaxF ()) == fromF y instance Show PgSyntax where showsPrec prec s = showParen (prec > 10) $ showString "PgSyntax <" . shows (pgTestSyntax s) . showString ">" emit :: ByteString -> PgSyntax emit bs = PgSyntax (liftF (EmitByteString bs ())) emitBuilder :: Builder -> PgSyntax emitBuilder b = PgSyntax (liftF (EmitBuilder b ())) escapeString, escapeBytea, escapeIdentifier :: ByteString -> PgSyntax escapeString bs = PgSyntax (liftF (EscapeString bs ())) escapeBytea bin = PgSyntax (liftF (EscapeBytea bin ())) escapeIdentifier id = PgSyntax (liftF (EscapeIdentifier id ())) nextSyntaxStep :: PgSyntaxF f -> f nextSyntaxStep (EmitByteString _ next) = next nextSyntaxStep (EmitBuilder _ next) = next nextSyntaxStep (EscapeString _ next) = next nextSyntaxStep (EscapeBytea _ next) = next nextSyntaxStep (EscapeIdentifier _ next) = next -- * Syntax types data PgCommandType = PgCommandTypeQuery | PgCommandTypeDdl | PgCommandTypeDataUpdate | PgCommandTypeDataUpdateReturning deriving Show -- | Representation of an arbitrary Postgres command. This is the combination of -- the command syntax (repesented by 'PgSyntax'), as well as the type of command -- (represented by 'PgCommandType'). The command type is necessary for us to -- know how to retrieve results from the database. data PgCommandSyntax = PgCommandSyntax { pgCommandType :: PgCommandType , fromPgCommand :: PgSyntax } -- | 'IsSql92SelectSyntax' for Postgres newtype PgSelectSyntax = PgSelectSyntax { fromPgSelect :: PgSyntax } newtype PgSelectTableSyntax = PgSelectTableSyntax { fromPgSelectTable :: PgSyntax } -- | 'IsSql92InsertSyntax' for Postgres newtype PgInsertSyntax = PgInsertSyntax { fromPgInsert :: PgSyntax } -- | 'IsSql92DeleteSyntax' for Postgres newtype PgDeleteSyntax = PgDeleteSyntax { fromPgDelete :: PgSyntax } -- | 'IsSql92UpdateSyntax' for Postgres newtype PgUpdateSyntax = PgUpdateSyntax { fromPgUpdate :: PgSyntax } newtype PgExpressionSyntax = PgExpressionSyntax { fromPgExpression :: PgSyntax } deriving Eq newtype PgAggregationSetQuantifierSyntax = PgAggregationSetQuantifierSyntax { fromPgAggregationSetQuantifier :: PgSyntax } newtype PgSelectSetQuantifierSyntax = PgSelectSetQuantifierSyntax { fromPgSelectSetQuantifier :: PgSyntax } newtype PgFromSyntax = PgFromSyntax { fromPgFrom :: PgSyntax } newtype PgTableNameSyntax = PgTableNameSyntax { fromPgTableName :: PgSyntax } newtype PgComparisonQuantifierSyntax = PgComparisonQuantifierSyntax { fromPgComparisonQuantifier :: PgSyntax } newtype PgExtractFieldSyntax = PgExtractFieldSyntax { fromPgExtractField :: PgSyntax } newtype PgProjectionSyntax = PgProjectionSyntax { fromPgProjection :: PgSyntax } newtype PgGroupingSyntax = PgGroupingSyntax { fromPgGrouping :: PgSyntax } newtype PgValueSyntax = PgValueSyntax { fromPgValue :: PgSyntax } newtype PgTableSourceSyntax = PgTableSourceSyntax { fromPgTableSource :: PgSyntax } newtype PgFieldNameSyntax = PgFieldNameSyntax { fromPgFieldName :: PgSyntax } newtype PgInsertValuesSyntax = PgInsertValuesSyntax { fromPgInsertValues :: PgSyntax } newtype PgInsertOnConflictSyntax = PgInsertOnConflictSyntax { fromPgInsertOnConflict :: PgSyntax } newtype PgInsertOnConflictTargetSyntax = PgInsertOnConflictTargetSyntax { fromPgInsertOnConflictTarget :: PgSyntax } newtype PgInsertOnConflictUpdateSyntax = PgInsertOnConflictUpdateSyntax { fromPgInsertOnConflictUpdate :: PgSyntax } newtype PgConflictActionSyntax = PgConflictActionSyntax { fromPgConflictAction :: PgSyntax } data PgOrderingSyntax = PgOrderingSyntax { pgOrderingSyntax :: PgSyntax, pgOrderingNullOrdering :: Maybe PgNullOrdering } data PgSelectLockingClauseSyntax = PgSelectLockingClauseSyntax { pgSelectLockingClauseStrength :: PgSelectLockingStrength , pgSelectLockingTables :: [T.Text] , pgSelectLockingClauseOptions :: Maybe PgSelectLockingOptions } newtype PgCommonTableExpressionSyntax = PgCommonTableExpressionSyntax { fromPgCommonTableExpression :: PgSyntax } fromPgOrdering :: PgOrderingSyntax -> PgSyntax fromPgOrdering (PgOrderingSyntax s Nothing) = s fromPgOrdering (PgOrderingSyntax s (Just PgNullOrderingNullsFirst)) = s <> emit " NULLS FIRST" fromPgOrdering (PgOrderingSyntax s (Just PgNullOrderingNullsLast)) = s <> emit " NULLS LAST" data PgNullOrdering = PgNullOrderingNullsFirst | PgNullOrderingNullsLast deriving (Show, Eq, Generic) fromPgSelectLockingClause :: PgSelectLockingClauseSyntax -> PgSyntax fromPgSelectLockingClause s = emit " FOR " <> (case pgSelectLockingClauseStrength s of PgSelectLockingStrengthUpdate -> emit "UPDATE" PgSelectLockingStrengthNoKeyUpdate -> emit "NO KEY UPDATE" PgSelectLockingStrengthShare -> emit "SHARE" PgSelectLockingStrengthKeyShare -> emit "KEY SHARE") <> emitTables <> (maybe mempty emitOptions $ pgSelectLockingClauseOptions s) where emitTables = case pgSelectLockingTables s of [] -> mempty tableNames -> emit " OF " <> (pgSepBy (emit ", ") (map pgQuotedIdentifier tableNames)) emitOptions PgSelectLockingOptionsNoWait = emit " NOWAIT" emitOptions PgSelectLockingOptionsSkipLocked = emit " SKIP LOCKED" -- | Specifies the level of lock that will be taken against a row. See -- -- for more information. data PgSelectLockingStrength = PgSelectLockingStrengthUpdate -- ^ @UPDATE@ | PgSelectLockingStrengthNoKeyUpdate -- ^ @NO KEY UPDATE@ | PgSelectLockingStrengthShare -- ^ @SHARE@ | PgSelectLockingStrengthKeyShare -- ^ @KEY SHARE@ deriving (Show, Eq, Generic) -- | Specifies how we should handle lock conflicts. -- -- See -- -- for more information data PgSelectLockingOptions = PgSelectLockingOptionsNoWait -- ^ @NOWAIT@. Report an error rather than waiting for the lock | PgSelectLockingOptionsSkipLocked -- ^ @SKIP LOCKED@. Rather than wait for a lock, skip the row instead deriving (Show, Eq, Generic) data PgDataTypeDescr = PgDataTypeDescrOid Pg.Oid (Maybe Int32) | PgDataTypeDescrDomain T.Text deriving (Show, Eq, Generic) instance Hashable PgDataTypeDescr where hashWithSalt salt (PgDataTypeDescrOid (Pg.Oid oid) dim) = hashWithSalt salt (0 :: Int, fromIntegral oid :: Word32, dim) hashWithSalt salt (PgDataTypeDescrDomain t) = hashWithSalt salt (1 :: Int, t) newtype PgCreateTableSyntax = PgCreateTableSyntax { fromPgCreateTable :: PgSyntax } data PgTableOptionsSyntax = PgTableOptionsSyntax PgSyntax PgSyntax newtype PgColumnSchemaSyntax = PgColumnSchemaSyntax { fromPgColumnSchema :: PgSyntax } deriving (Show, Eq) instance Sql92DisplaySyntax PgColumnSchemaSyntax where displaySyntax = displaySyntax . fromPgColumnSchema data PgDataTypeSyntax = PgDataTypeSyntax { pgDataTypeDescr :: PgDataTypeDescr , fromPgDataType :: PgSyntax , pgDataTypeSerialized :: BeamSerializedDataType } deriving Show instance Sql92DisplaySyntax PgDataTypeSyntax where displaySyntax = displaySyntax . fromPgDataType data PgColumnConstraintDefinitionSyntax = PgColumnConstraintDefinitionSyntax { fromPgColumnConstraintDefinition :: PgSyntax , pgColumnConstraintDefinitionSerialized :: BeamSerializedConstraintDefinition } deriving Show instance Sql92DisplaySyntax PgColumnConstraintDefinitionSyntax where displaySyntax = displaySyntax . fromPgColumnConstraintDefinition data PgColumnConstraintSyntax = PgColumnConstraintSyntax { fromPgColumnConstraint :: PgSyntax , pgColumnConstraintSerialized :: BeamSerializedConstraint } newtype PgTableConstraintSyntax = PgTableConstraintSyntax { fromPgTableConstraint :: PgSyntax } data PgMatchTypeSyntax = PgMatchTypeSyntax { fromPgMatchType :: PgSyntax , pgMatchTypeSerialized :: BeamSerializedMatchType } data PgReferentialActionSyntax = PgReferentialActionSyntax { fromPgReferentialAction :: PgSyntax , pgReferentialActionSerialized :: BeamSerializedReferentialAction } newtype PgDropTableSyntax = PgDropTableSyntax { fromPgDropTable :: PgSyntax } newtype PgAlterTableSyntax = PgAlterTableSyntax { fromPgAlterTable :: PgSyntax } newtype PgAlterTableActionSyntax = PgAlterTableActionSyntax { fromPgAlterTableAction :: PgSyntax } newtype PgAlterColumnActionSyntax = PgAlterColumnActionSyntax { fromPgAlterColumnAction :: PgSyntax } newtype PgWindowFrameSyntax = PgWindowFrameSyntax { fromPgWindowFrame :: PgSyntax } newtype PgWindowFrameBoundsSyntax = PgWindowFrameBoundsSyntax { fromPgWindowFrameBounds :: PgSyntax } newtype PgWindowFrameBoundSyntax = PgWindowFrameBoundSyntax { fromPgWindowFrameBound :: ByteString -> PgSyntax } instance Hashable PgDataTypeSyntax where hashWithSalt salt (PgDataTypeSyntax a _ _) = hashWithSalt salt a instance Eq PgDataTypeSyntax where PgDataTypeSyntax a _ _ == PgDataTypeSyntax b _ _ = a == b instance HasDataTypeCreatedCheck PgDataTypeSyntax where dataTypeHasBeenCreated (PgDataTypeSyntax (PgDataTypeDescrOid {}) _ _) _ = True dataTypeHasBeenCreated (PgDataTypeSyntax (PgDataTypeDescrDomain d) _ _) pre = not . null $ do PgHasEnum nm _ <- pre guard (nm == d) instance Eq PgColumnConstraintDefinitionSyntax where PgColumnConstraintDefinitionSyntax a _ == PgColumnConstraintDefinitionSyntax b _ = a == b instance IsSql92Syntax PgCommandSyntax where type Sql92SelectSyntax PgCommandSyntax = PgSelectSyntax type Sql92InsertSyntax PgCommandSyntax = PgInsertSyntax type Sql92UpdateSyntax PgCommandSyntax = PgUpdateSyntax type Sql92DeleteSyntax PgCommandSyntax = PgDeleteSyntax selectCmd = PgCommandSyntax PgCommandTypeQuery . coerce insertCmd = PgCommandSyntax PgCommandTypeDataUpdate . coerce deleteCmd = PgCommandSyntax PgCommandTypeDataUpdate . coerce updateCmd = PgCommandSyntax PgCommandTypeDataUpdate . coerce instance IsSql92DdlCommandSyntax PgCommandSyntax where type Sql92DdlCommandCreateTableSyntax PgCommandSyntax = PgCreateTableSyntax type Sql92DdlCommandDropTableSyntax PgCommandSyntax = PgDropTableSyntax type Sql92DdlCommandAlterTableSyntax PgCommandSyntax = PgAlterTableSyntax createTableCmd = PgCommandSyntax PgCommandTypeDdl . coerce dropTableCmd = PgCommandSyntax PgCommandTypeDdl . coerce alterTableCmd = PgCommandSyntax PgCommandTypeDdl . coerce instance IsSql92TableNameSyntax PgTableNameSyntax where tableName Nothing t = PgTableNameSyntax (pgQuotedIdentifier t) tableName (Just s) t = PgTableNameSyntax (pgQuotedIdentifier s <> emit "." <> pgQuotedIdentifier t) instance IsSql92UpdateSyntax PgUpdateSyntax where type Sql92UpdateFieldNameSyntax PgUpdateSyntax = PgFieldNameSyntax type Sql92UpdateExpressionSyntax PgUpdateSyntax = PgExpressionSyntax type Sql92UpdateTableNameSyntax PgUpdateSyntax = PgTableNameSyntax updateStmt tbl fields where_ = PgUpdateSyntax $ emit "UPDATE " <> fromPgTableName tbl <> (case fields of [] -> mempty fields -> emit " SET " <> pgSepBy (emit ", ") (map (\(field, val) -> fromPgFieldName field <> emit "=" <> fromPgExpression val) fields)) <> maybe mempty (\where_ -> emit " WHERE " <> fromPgExpression where_) where_ instance IsSql92DeleteSyntax PgDeleteSyntax where type Sql92DeleteExpressionSyntax PgDeleteSyntax = PgExpressionSyntax type Sql92DeleteTableNameSyntax PgDeleteSyntax = PgTableNameSyntax deleteStmt tbl alias where_ = PgDeleteSyntax $ emit "DELETE FROM " <> fromPgTableName tbl <> maybe mempty (\alias_ -> emit " AS " <> pgQuotedIdentifier alias_) alias <> maybe mempty (\where_ -> emit " WHERE " <> fromPgExpression where_) where_ deleteSupportsAlias _ = True instance IsSql92SelectSyntax PgSelectSyntax where type Sql92SelectSelectTableSyntax PgSelectSyntax = PgSelectTableSyntax type Sql92SelectOrderingSyntax PgSelectSyntax = PgOrderingSyntax selectStmt tbl ordering limit offset = pgSelectStmt tbl ordering limit offset Nothing instance IsSql92SelectTableSyntax PgSelectTableSyntax where type Sql92SelectTableSelectSyntax PgSelectTableSyntax = PgSelectSyntax type Sql92SelectTableExpressionSyntax PgSelectTableSyntax = PgExpressionSyntax type Sql92SelectTableProjectionSyntax PgSelectTableSyntax = PgProjectionSyntax type Sql92SelectTableFromSyntax PgSelectTableSyntax = PgFromSyntax type Sql92SelectTableGroupingSyntax PgSelectTableSyntax = PgGroupingSyntax type Sql92SelectTableSetQuantifierSyntax PgSelectTableSyntax = PgSelectSetQuantifierSyntax selectTableStmt setQuantifier proj from where_ grouping having = PgSelectTableSyntax $ emit "SELECT " <> maybe mempty (\setQuantifier' -> fromPgSelectSetQuantifier setQuantifier' <> emit " ") setQuantifier <> fromPgProjection proj <> (maybe mempty (emit " FROM " <> ) (coerce from)) <> (maybe mempty (emit " WHERE " <>) (coerce where_)) <> (maybe mempty (emit " GROUP BY " <>) (coerce grouping)) <> (maybe mempty (emit " HAVING " <>) (coerce having)) unionTables all = pgTableOp (if all then "UNION ALL" else "UNION") intersectTables all = pgTableOp (if all then "INTERSECT ALL" else "INTERSECT") exceptTable all = pgTableOp (if all then "EXCEPT ALL" else "EXCEPT") instance IsSql92GroupingSyntax PgGroupingSyntax where type Sql92GroupingExpressionSyntax PgGroupingSyntax = PgExpressionSyntax groupByExpressions es = PgGroupingSyntax $ pgSepBy (emit ", ") (map fromPgExpression es) instance IsSql92FromSyntax PgFromSyntax where type Sql92FromExpressionSyntax PgFromSyntax = PgExpressionSyntax type Sql92FromTableSourceSyntax PgFromSyntax = PgTableSourceSyntax fromTable tableSrc Nothing = coerce tableSrc fromTable tableSrc (Just (nm, colNms)) = PgFromSyntax $ coerce tableSrc <> emit " AS " <> pgQuotedIdentifier nm <> maybe mempty (\colNms' -> pgParens (pgSepBy (emit ",") (map pgQuotedIdentifier colNms'))) colNms innerJoin a b Nothing = PgFromSyntax (fromPgFrom a <> emit " CROSS JOIN " <> fromPgFrom b) innerJoin a b (Just e) = pgJoin "INNER JOIN" a b (Just e) leftJoin = pgJoin "LEFT JOIN" rightJoin = pgJoin "RIGHT JOIN" instance IsSql92FromOuterJoinSyntax PgFromSyntax where outerJoin = pgJoin "FULL OUTER JOIN" instance IsSql92OrderingSyntax PgOrderingSyntax where type Sql92OrderingExpressionSyntax PgOrderingSyntax = PgExpressionSyntax ascOrdering e = PgOrderingSyntax (fromPgExpression e <> emit " ASC") Nothing descOrdering e = PgOrderingSyntax (fromPgExpression e <> emit " DESC") Nothing instance IsSql2003OrderingElementaryOLAPOperationsSyntax PgOrderingSyntax where nullsFirstOrdering o = o { pgOrderingNullOrdering = Just PgNullOrderingNullsFirst } nullsLastOrdering o = o { pgOrderingNullOrdering = Just PgNullOrderingNullsLast } instance IsSql92DataTypeSyntax PgDataTypeSyntax where domainType nm = PgDataTypeSyntax (PgDataTypeDescrDomain nm) (pgQuotedIdentifier nm) (domainType nm) charType prec charSet = PgDataTypeSyntax (PgDataTypeDescrOid (Pg.typoid Pg.bpchar) (Just (fromIntegral (fromMaybe 1 prec)))) (emit "CHAR" <> pgOptPrec prec <> pgOptCharSet charSet) (charType prec charSet) varCharType prec charSet = PgDataTypeSyntax (PgDataTypeDescrOid (Pg.typoid Pg.varchar) (fmap fromIntegral prec)) (emit "VARCHAR" <> pgOptPrec prec <> pgOptCharSet charSet) (varCharType prec charSet) nationalCharType prec = PgDataTypeSyntax (PgDataTypeDescrOid (Pg.typoid Pg.bpchar) (fmap fromIntegral prec)) (emit "NATIONAL CHAR" <> pgOptPrec prec) (nationalCharType prec) nationalVarCharType prec = PgDataTypeSyntax (PgDataTypeDescrOid (Pg.typoid Pg.varchar) (fmap fromIntegral prec)) (emit "NATIONAL CHARACTER VARYING" <> pgOptPrec prec) (nationalVarCharType prec) bitType prec = PgDataTypeSyntax (PgDataTypeDescrOid (Pg.typoid Pg.bit) (fmap fromIntegral prec)) (emit "BIT" <> pgOptPrec prec) (bitType prec) varBitType prec = PgDataTypeSyntax (PgDataTypeDescrOid (Pg.typoid Pg.varbit) (fmap fromIntegral prec)) (emit "BIT VARYING" <> pgOptPrec prec) (varBitType prec) numericType prec = PgDataTypeSyntax (PgDataTypeDescrOid (Pg.typoid Pg.numeric) (mkNumericPrec prec)) (emit "NUMERIC" <> pgOptNumericPrec prec) (numericType prec) decimalType prec = PgDataTypeSyntax (PgDataTypeDescrOid (Pg.typoid Pg.numeric) (mkNumericPrec prec)) (emit "DOUBLE" <> pgOptNumericPrec prec) (decimalType prec) intType = PgDataTypeSyntax (PgDataTypeDescrOid (Pg.typoid Pg.int4) Nothing) (emit "INT") intType smallIntType = PgDataTypeSyntax (PgDataTypeDescrOid (Pg.typoid Pg.int2) Nothing) (emit "SMALLINT") smallIntType floatType prec = PgDataTypeSyntax (PgDataTypeDescrOid (Pg.typoid Pg.float4) Nothing) (emit "FLOAT" <> pgOptPrec prec) (floatType prec) doubleType = PgDataTypeSyntax (PgDataTypeDescrOid (Pg.typoid Pg.float8) Nothing) (emit "DOUBLE PRECISION") doubleType realType = PgDataTypeSyntax (PgDataTypeDescrOid (Pg.typoid Pg.float4) Nothing) (emit "REAL") realType dateType = PgDataTypeSyntax (PgDataTypeDescrOid (Pg.typoid Pg.date) Nothing) (emit "DATE") dateType timeType prec withTz = PgDataTypeSyntax (PgDataTypeDescrOid (Pg.typoid Pg.time) Nothing) (emit "TIME" <> pgOptPrec prec <> if withTz then emit " WITH TIME ZONE" else mempty) (timeType prec withTz) timestampType prec withTz = PgDataTypeSyntax (PgDataTypeDescrOid (Pg.typoid (if withTz then Pg.timestamptz else Pg.timestamp)) Nothing) (emit "TIMESTAMP" <> pgOptPrec prec <> if withTz then emit " WITH TIME ZONE" else mempty) (timestampType prec withTz) instance IsSql99DataTypeSyntax PgDataTypeSyntax where characterLargeObjectType = pgTextType { pgDataTypeSerialized = characterLargeObjectType } binaryLargeObjectType = pgByteaType { pgDataTypeSerialized = binaryLargeObjectType } booleanType = PgDataTypeSyntax (PgDataTypeDescrOid (Pg.typoid Pg.bool) Nothing) (emit "BOOLEAN") booleanType arrayType (PgDataTypeSyntax _ syntax serialized) sz = PgDataTypeSyntax (error "TODO: array migrations") (syntax <> emit "[" <> emit (fromString (show sz)) <> emit "]") (arrayType serialized sz) rowType = error "rowType" instance IsSql99CommonTableExpressionSelectSyntax PgSelectSyntax where type Sql99SelectCTESyntax PgSelectSyntax = PgCommonTableExpressionSyntax withSyntax ctes (PgSelectSyntax select) = PgSelectSyntax $ emit "WITH " <> pgSepBy (emit ", ") (map fromPgCommonTableExpression ctes) <> select instance IsSql99RecursiveCommonTableExpressionSelectSyntax PgSelectSyntax where withRecursiveSyntax ctes (PgSelectSyntax select) = PgSelectSyntax $ emit "WITH RECURSIVE " <> pgSepBy (emit ", ") (map fromPgCommonTableExpression ctes) <> select instance IsSql99CommonTableExpressionSyntax PgCommonTableExpressionSyntax where type Sql99CTESelectSyntax PgCommonTableExpressionSyntax = PgSelectSyntax cteSubquerySyntax tbl fields (PgSelectSyntax select) = PgCommonTableExpressionSyntax $ pgQuotedIdentifier tbl <> pgParens (pgSepBy (emit ",") (map pgQuotedIdentifier fields)) <> emit " AS " <> pgParens select instance IsSql2008BigIntDataTypeSyntax PgDataTypeSyntax where bigIntType = PgDataTypeSyntax (PgDataTypeDescrOid (Pg.typoid Pg.int8) Nothing) (emit "BIGINT") bigIntType instance Sql92SerializableDataTypeSyntax PgDataTypeSyntax where serializeDataType = fromBeamSerializedDataType . pgDataTypeSerialized pgOptPrec :: Maybe Word -> PgSyntax pgOptPrec Nothing = mempty pgOptPrec (Just x) = emit "(" <> emit (fromString (show x)) <> emit ")" pgOptCharSet :: Maybe T.Text -> PgSyntax pgOptCharSet Nothing = mempty pgOptCharSet (Just cs) = emit " CHARACTER SET " <> emit (TE.encodeUtf8 cs) pgOptNumericPrec :: Maybe (Word, Maybe Word) -> PgSyntax pgOptNumericPrec Nothing = mempty pgOptNumericPrec (Just (prec, Nothing)) = pgOptPrec (Just prec) pgOptNumericPrec (Just (prec, Just dec)) = emit "(" <> emit (fromString (show prec)) <> emit ", " <> emit (fromString (show dec)) <> emit ")" pgDataTypeJSON :: Value -> BeamSerializedDataType pgDataTypeJSON v = BeamSerializedDataType (beamSerializeJSON "postgres" v) pgByteaType :: PgDataTypeSyntax pgByteaType = PgDataTypeSyntax (PgDataTypeDescrOid (Pg.typoid Pg.bytea) Nothing) (emit "BYTEA") (pgDataTypeJSON "bytea") pgSmallSerialType, pgSerialType, pgBigSerialType :: PgDataTypeSyntax pgSmallSerialType = PgDataTypeSyntax (pgDataTypeDescr smallIntType) (emit "SMALLSERIAL") (pgDataTypeJSON "smallserial") pgSerialType = PgDataTypeSyntax (pgDataTypeDescr intType) (emit "SERIAL") (pgDataTypeJSON "serial") pgBigSerialType = PgDataTypeSyntax (PgDataTypeDescrOid (Pg.typoid Pg.int8) Nothing) (emit "BIGSERIAL") (pgDataTypeJSON "bigserial") pgPointType, pgLineType, pgLineSegmentType, pgBoxType :: PgDataTypeSyntax pgPointType = PgDataTypeSyntax (PgDataTypeDescrOid (Pg.typoid Pg.point) Nothing) (emit "POINT") (pgDataTypeJSON "point") pgLineType = PgDataTypeSyntax (PgDataTypeDescrOid (Pg.typoid Pg.line) Nothing) (emit "LINE") (pgDataTypeJSON "line") pgLineSegmentType = PgDataTypeSyntax (PgDataTypeDescrOid (Pg.typoid Pg.lseg) Nothing) (emit "LSEG") (pgDataTypeJSON "lseg") pgBoxType = PgDataTypeSyntax (PgDataTypeDescrOid (Pg.typoid Pg.box) Nothing) (emit "BOX") (pgDataTypeJSON "box") pgUnboundedArrayType :: PgDataTypeSyntax -> PgDataTypeSyntax pgUnboundedArrayType (PgDataTypeSyntax _ syntax serialized) = PgDataTypeSyntax (error "Can't do array migrations yet") (syntax <> emit "[]") (pgDataTypeJSON (object [ "unbounded-array" .= fromBeamSerializedDataType serialized ])) pgTsQueryTypeInfo :: Pg.TypeInfo pgTsQueryTypeInfo = Pg.Basic (Pg.Oid 3615) 'U' ',' "tsquery" pgTsQueryType :: PgDataTypeSyntax pgTsQueryType = PgDataTypeSyntax (PgDataTypeDescrOid (Pg.typoid pgTsQueryTypeInfo) Nothing) (emit "TSQUERY") (pgDataTypeJSON "tsquery") -- | Postgres TypeInfo for tsvector -- TODO Is the Oid stable from postgres instance to postgres instance? pgTsVectorTypeInfo :: Pg.TypeInfo pgTsVectorTypeInfo = Pg.Basic (Pg.Oid 3614) 'U' ',' "tsvector" pgTsVectorType :: PgDataTypeSyntax pgTsVectorType = PgDataTypeSyntax (PgDataTypeDescrOid (Pg.typoid pgTsVectorTypeInfo) Nothing) (emit "TSVECTOR") (pgDataTypeJSON "tsvector") pgTextType :: PgDataTypeSyntax pgTextType = PgDataTypeSyntax (PgDataTypeDescrOid (Pg.typoid Pg.text) Nothing) (emit "TEXT") (pgDataTypeJSON "text") pgJsonType, pgJsonbType :: PgDataTypeSyntax pgJsonType = PgDataTypeSyntax (PgDataTypeDescrOid (Pg.typoid Pg.json) Nothing) (emit "JSON") (pgDataTypeJSON "json") pgJsonbType = PgDataTypeSyntax (PgDataTypeDescrOid (Pg.typoid Pg.jsonb) Nothing) (emit "JSONB") (pgDataTypeJSON "jsonb") pgUuidType :: PgDataTypeSyntax pgUuidType = PgDataTypeSyntax (PgDataTypeDescrOid (Pg.typoid Pg.uuid) Nothing) (emit "UUID") (pgDataTypeJSON "uuid") pgMoneyType :: PgDataTypeSyntax pgMoneyType = PgDataTypeSyntax (PgDataTypeDescrOid (Pg.typoid Pg.money) Nothing) (emit "MONEY") (pgDataTypeJSON "money") mkNumericPrec :: Maybe (Word, Maybe Word) -> Maybe Int32 mkNumericPrec Nothing = Nothing mkNumericPrec (Just (whole, dec)) = Just $ (fromIntegral whole `shiftL` 16) .|. (fromIntegral (fromMaybe 0 dec) .&. 0xFFFF) instance IsCustomSqlSyntax PgExpressionSyntax where newtype CustomSqlSyntax PgExpressionSyntax = PgCustomExpressionSyntax { fromPgCustomExpression :: PgSyntax } deriving Monoid customExprSyntax = PgExpressionSyntax . fromPgCustomExpression renderSyntax = PgCustomExpressionSyntax . pgParens . fromPgExpression instance Semigroup (CustomSqlSyntax PgExpressionSyntax) where (<>) = mappend instance IsString (CustomSqlSyntax PgExpressionSyntax) where fromString = PgCustomExpressionSyntax . emit . fromString instance IsSql92QuantifierSyntax PgComparisonQuantifierSyntax where quantifyOverAll = PgComparisonQuantifierSyntax (emit "ALL") quantifyOverAny = PgComparisonQuantifierSyntax (emit "ANY") instance IsSql92ExtractFieldSyntax PgExtractFieldSyntax where secondsField = PgExtractFieldSyntax (emit "SECOND") minutesField = PgExtractFieldSyntax (emit "MINUTE") hourField = PgExtractFieldSyntax (emit "HOUR") dayField = PgExtractFieldSyntax (emit "DAY") monthField = PgExtractFieldSyntax (emit "MONTH") yearField = PgExtractFieldSyntax (emit "YEAR") instance IsSql92ExpressionSyntax PgExpressionSyntax where type Sql92ExpressionValueSyntax PgExpressionSyntax = PgValueSyntax type Sql92ExpressionSelectSyntax PgExpressionSyntax = PgSelectSyntax type Sql92ExpressionFieldNameSyntax PgExpressionSyntax = PgFieldNameSyntax type Sql92ExpressionQuantifierSyntax PgExpressionSyntax = PgComparisonQuantifierSyntax type Sql92ExpressionCastTargetSyntax PgExpressionSyntax = PgDataTypeSyntax type Sql92ExpressionExtractFieldSyntax PgExpressionSyntax = PgExtractFieldSyntax addE = pgBinOp "+" subE = pgBinOp "-" mulE = pgBinOp "*" divE = pgBinOp "/" modE = pgBinOp "%" orE = pgBinOp "OR" andE = pgBinOp "AND" likeE = pgBinOp "LIKE" overlapsE = pgBinOp "OVERLAPS" eqE = pgCompOp "=" neqE = pgCompOp "<>" eqMaybeE a b _ = pgBinOp "IS NOT DISTINCT FROM" a b neqMaybeE a b _ = pgBinOp "IS DISTINCT FROM" a b ltE = pgCompOp "<" gtE = pgCompOp ">" leE = pgCompOp "<=" geE = pgCompOp ">=" negateE = pgUnOp "-" notE = pgUnOp "NOT" existsE select = PgExpressionSyntax (emit "EXISTS (" <> fromPgSelect select <> emit ")") uniqueE select = PgExpressionSyntax (emit "UNIQUE (" <> fromPgSelect select <> emit ")") isNotNullE = pgPostFix "IS NOT NULL" isNullE = pgPostFix "IS NULL" isTrueE = pgPostFix "IS TRUE" isFalseE = pgPostFix "IS FALSE" isNotTrueE = pgPostFix "IS NOT TRUE" isNotFalseE = pgPostFix "IS NOT FALSE" isUnknownE = pgPostFix "IS UNKNOWN" isNotUnknownE = pgPostFix "IS NOT UNKNOWN" betweenE a b c = PgExpressionSyntax (emit "(" <> fromPgExpression a <> emit ") BETWEEN (" <> fromPgExpression b <> emit ") AND (" <> fromPgExpression c <> emit ")") valueE = coerce rowE vs = PgExpressionSyntax $ emit "(" <> pgSepBy (emit ", ") (coerce vs) <> emit ")" quantifierListE vs = PgExpressionSyntax $ emit "(VALUES " <> pgSepBy (emit ", ") (fmap (pgParens . fromPgExpression) vs) <> emit ")" fieldE = coerce subqueryE s = PgExpressionSyntax (emit "(" <> fromPgSelect s <> emit ")") positionE needle haystack = PgExpressionSyntax $ emit "POSITION((" <> fromPgExpression needle <> emit ") IN (" <> fromPgExpression haystack <> emit "))" nullIfE a b = PgExpressionSyntax (emit "NULLIF(" <> fromPgExpression a <> emit ", " <> fromPgExpression b <> emit ")") absE x = PgExpressionSyntax (emit "ABS(" <> fromPgExpression x <> emit ")") bitLengthE x = PgExpressionSyntax (emit "BIT_LENGTH(" <> fromPgExpression x <> emit ")") charLengthE x = PgExpressionSyntax (emit "CHAR_LENGTH(" <> fromPgExpression x <> emit ")") octetLengthE x = PgExpressionSyntax (emit "OCTET_LENGTH(" <> fromPgExpression x <> emit ")") lowerE x = PgExpressionSyntax (emit "LOWER(" <> fromPgExpression x <> emit ")") upperE x = PgExpressionSyntax (emit "UPPER(" <> fromPgExpression x <> emit ")") trimE x = PgExpressionSyntax (emit "TRIM(" <> fromPgExpression x <> emit ")") coalesceE es = PgExpressionSyntax (emit "COALESCE(" <> pgSepBy (emit ", ") (map fromPgExpression es) <> emit ")") extractE field from = PgExpressionSyntax (emit "EXTRACT(" <> fromPgExtractField field <> emit " FROM (" <> fromPgExpression from <> emit "))") castE e to = PgExpressionSyntax (emit "CAST((" <> fromPgExpression e <> emit ") AS " <> fromPgDataType to <> emit ")") caseE cases else_ = PgExpressionSyntax $ emit "CASE " <> foldMap (\(cond, res) -> emit "WHEN " <> fromPgExpression cond <> emit " THEN " <> fromPgExpression res <> emit " ") cases <> emit "ELSE " <> fromPgExpression else_ <> emit " END" currentTimestampE = PgExpressionSyntax $ emit "CURRENT_TIMESTAMP" defaultE = PgExpressionSyntax $ emit "DEFAULT" inE e es = PgExpressionSyntax $ pgParens (fromPgExpression e) <> emit " IN " <> pgParens (pgSepBy (emit ", ") (map fromPgExpression es)) instance IsSql99FunctionExpressionSyntax PgExpressionSyntax where functionCallE name args = PgExpressionSyntax $ fromPgExpression name <> pgParens (pgSepBy (emit ", ") (map fromPgExpression args)) functionNameE nm = PgExpressionSyntax (emit (TE.encodeUtf8 nm)) instance IsSql99ExpressionSyntax PgExpressionSyntax where distinctE select = PgExpressionSyntax (emit "DISTINCT (" <> fromPgSelect select <> emit ")") similarToE = pgBinOp "SIMILAR TO" instanceFieldE i nm = PgExpressionSyntax $ pgParens (fromPgExpression i) <> emit "." <> escapeIdentifier (TE.encodeUtf8 nm) refFieldE i nm = PgExpressionSyntax $ pgParens (fromPgExpression i) <> emit "->" <> escapeIdentifier (TE.encodeUtf8 nm) instance IsSql99ConcatExpressionSyntax PgExpressionSyntax where concatE [] = valueE (sqlValueSyntax ("" :: T.Text)) concatE [x] = x concatE es = PgExpressionSyntax $ emit "CONCAT" <> pgParens (pgSepBy (emit ", ") (map fromPgExpression es)) instance IsSql2003ExpressionSyntax PgExpressionSyntax where type Sql2003ExpressionWindowFrameSyntax PgExpressionSyntax = PgWindowFrameSyntax overE expr frame = PgExpressionSyntax $ fromPgExpression expr <> emit " " <> fromPgWindowFrame frame rowNumberE = PgExpressionSyntax $ emit "ROW_NUMBER()" instance IsSql2003EnhancedNumericFunctionsExpressionSyntax PgExpressionSyntax where lnE x = PgExpressionSyntax (emit "LN(" <> fromPgExpression x <> emit ")") expE x = PgExpressionSyntax (emit "EXP(" <> fromPgExpression x <> emit ")") sqrtE x = PgExpressionSyntax (emit "SQRT(" <> fromPgExpression x <> emit ")") ceilE x = PgExpressionSyntax (emit "CEIL(" <> fromPgExpression x <> emit ")") floorE x = PgExpressionSyntax (emit "FLOOR(" <> fromPgExpression x <> emit ")") powerE x y = PgExpressionSyntax (emit "POWER(" <> fromPgExpression x <> emit ", " <> fromPgExpression y <> emit ")") instance IsSql2003ExpressionAdvancedOLAPOperationsSyntax PgExpressionSyntax where denseRankAggE = PgExpressionSyntax $ emit "DENSE_RANK()" percentRankAggE = PgExpressionSyntax $ emit "PERCENT_RANK()" cumeDistAggE = PgExpressionSyntax $ emit "CUME_DIST()" instance IsSql2003ExpressionElementaryOLAPOperationsSyntax PgExpressionSyntax where rankAggE = PgExpressionSyntax $ emit "RANK()" filterAggE agg filter = PgExpressionSyntax $ fromPgExpression agg <> emit " FILTER (WHERE " <> fromPgExpression filter <> emit ")" instance IsSql2003EnhancedNumericFunctionsAggregationExpressionSyntax PgExpressionSyntax where stddevPopE = pgUnAgg "STDDEV_POP" stddevSampE = pgUnAgg "STDDEV_SAMP" varPopE = pgUnAgg "VAR_POP" varSampE = pgUnAgg "VAR_SAMP" covarPopE = pgBinAgg "COVAR_POP" covarSampE = pgBinAgg "COVAR_SAMP" corrE = pgBinAgg "CORR" regrSlopeE = pgBinAgg "REGR_SLOPE" regrInterceptE = pgBinAgg "REGR_INTERCEPT" regrCountE = pgBinAgg "REGR_COUNT" regrRSquaredE = pgBinAgg "REGR_R2" regrAvgXE = pgBinAgg "REGR_AVGX" regrAvgYE = pgBinAgg "REGR_AVGY" regrSXXE = pgBinAgg "REGR_SXX" regrSYYE = pgBinAgg "REGR_SYY" regrSXYE = pgBinAgg "REGR_SXY" instance IsSql2003NtileExpressionSyntax PgExpressionSyntax where ntileE x = PgExpressionSyntax (emit "NTILE(" <> fromPgExpression x <> emit ")") instance IsSql2003LeadAndLagExpressionSyntax PgExpressionSyntax where leadE x Nothing Nothing = PgExpressionSyntax (emit "LEAD(" <> fromPgExpression x <> emit ")") leadE x (Just n) Nothing = PgExpressionSyntax (emit "LEAD(" <> fromPgExpression x <> emit ", " <> fromPgExpression n <> emit ")") leadE x (Just n) (Just def) = PgExpressionSyntax (emit "LEAD(" <> fromPgExpression x <> emit ", " <> fromPgExpression n <> emit ", " <> fromPgExpression def <> emit ")") leadE x Nothing (Just def) = PgExpressionSyntax (emit "LEAD(" <> fromPgExpression x <> emit ", 1, " <> fromPgExpression def <> emit ")") lagE x Nothing Nothing = PgExpressionSyntax (emit "LAG(" <> fromPgExpression x <> emit ")") lagE x (Just n) Nothing = PgExpressionSyntax (emit "LAG(" <> fromPgExpression x <> emit ", " <> fromPgExpression n <> emit ")") lagE x (Just n) (Just def) = PgExpressionSyntax (emit "LAG(" <> fromPgExpression x <> emit ", " <> fromPgExpression n <> emit ", " <> fromPgExpression def <> emit ")") lagE x Nothing (Just def) = PgExpressionSyntax (emit "LAG(" <> fromPgExpression x <> emit ", 1, " <> fromPgExpression def <> emit ")") instance IsSql2003FirstValueAndLastValueExpressionSyntax PgExpressionSyntax where firstValueE x = PgExpressionSyntax (emit "FIRST_VALUE(" <> fromPgExpression x <> emit ")") lastValueE x = PgExpressionSyntax (emit "LAST_VALUE(" <> fromPgExpression x <> emit ")") instance IsSql2003NthValueExpressionSyntax PgExpressionSyntax where nthValueE x n = PgExpressionSyntax (emit "NTH_VALUE(" <> fromPgExpression x <> emit ", " <> fromPgExpression n <> emit ")") instance IsSql2003WindowFrameSyntax PgWindowFrameSyntax where type Sql2003WindowFrameExpressionSyntax PgWindowFrameSyntax = PgExpressionSyntax type Sql2003WindowFrameOrderingSyntax PgWindowFrameSyntax = PgOrderingSyntax type Sql2003WindowFrameBoundsSyntax PgWindowFrameSyntax = PgWindowFrameBoundsSyntax frameSyntax partition_ ordering_ bounds_ = PgWindowFrameSyntax $ emit "OVER " <> pgParens ( maybe mempty (\p -> emit "PARTITION BY " <> pgSepBy (emit ", ") (map fromPgExpression p)) partition_ <> maybe mempty (\o -> emit " ORDER BY " <> pgSepBy (emit ", ") (map fromPgOrdering o)) ordering_ <> maybe mempty (\b -> emit " ROWS " <> fromPgWindowFrameBounds b) bounds_ ) instance IsSql2003WindowFrameBoundsSyntax PgWindowFrameBoundsSyntax where type Sql2003WindowFrameBoundsBoundSyntax PgWindowFrameBoundsSyntax = PgWindowFrameBoundSyntax fromToBoundSyntax from Nothing = PgWindowFrameBoundsSyntax (fromPgWindowFrameBound from "PRECEDING") fromToBoundSyntax from (Just to) = PgWindowFrameBoundsSyntax $ emit "BETWEEN " <> fromPgWindowFrameBound from "PRECEDING" <> emit " AND " <> fromPgWindowFrameBound to "FOLLOWING" instance IsSql2003WindowFrameBoundSyntax PgWindowFrameBoundSyntax where unboundedSyntax = PgWindowFrameBoundSyntax $ \where_ -> emit "UNBOUNDED " <> emit where_ nrowsBoundSyntax 0 = PgWindowFrameBoundSyntax $ \_ -> emit "CURRENT ROW" nrowsBoundSyntax n = PgWindowFrameBoundSyntax $ \where_ -> emit (fromString (show n)) <> emit " " <> emit where_ instance IsSql92AggregationExpressionSyntax PgExpressionSyntax where type Sql92AggregationSetQuantifierSyntax PgExpressionSyntax = PgAggregationSetQuantifierSyntax countAllE = PgExpressionSyntax (emit "COUNT(*)") countE = pgUnAgg "COUNT" avgE = pgUnAgg "AVG" sumE = pgUnAgg "SUM" minE = pgUnAgg "MIN" maxE = pgUnAgg "MAX" instance IsSql99AggregationExpressionSyntax PgExpressionSyntax where everyE = pgUnAgg "EVERY" -- According to the note at -- the following functions are equivalent. someE = pgUnAgg "BOOL_ANY" anyE = pgUnAgg "BOOL_ANY" instance IsSql92AggregationSetQuantifierSyntax PgAggregationSetQuantifierSyntax where setQuantifierDistinct = PgAggregationSetQuantifierSyntax $ emit "DISTINCT" setQuantifierAll = PgAggregationSetQuantifierSyntax $ emit "ALL" instance IsSql92AggregationSetQuantifierSyntax PgSelectSetQuantifierSyntax where setQuantifierDistinct = PgSelectSetQuantifierSyntax $ emit "DISTINCT" setQuantifierAll = PgSelectSetQuantifierSyntax $ emit "ALL" pgSelectSetQuantifierDistinctOn :: [PgExpressionSyntax] -> PgSelectSetQuantifierSyntax pgSelectSetQuantifierDistinctOn exprs = PgSelectSetQuantifierSyntax $ emit "DISTINCT ON " <> pgParens (pgSepBy (emit ", ") (fromPgExpression <$> exprs)) pgUnAgg :: ByteString -> Maybe PgAggregationSetQuantifierSyntax -> PgExpressionSyntax -> PgExpressionSyntax pgUnAgg fn q e = PgExpressionSyntax $ emit fn <> emit "(" <> maybe mempty (\q -> fromPgAggregationSetQuantifier q <> emit " ") q <> fromPgExpression e <> emit ")" pgBinAgg :: ByteString -> Maybe PgAggregationSetQuantifierSyntax -> PgExpressionSyntax -> PgExpressionSyntax -> PgExpressionSyntax pgBinAgg fn q x y = PgExpressionSyntax $ emit fn <> emit "(" <> maybe mempty (\q -> fromPgAggregationSetQuantifier q <> emit " ") q <> fromPgExpression x <> emit ", " <> fromPgExpression y <> emit ")" instance IsSql92FieldNameSyntax PgFieldNameSyntax where qualifiedField a b = PgFieldNameSyntax $ pgQuotedIdentifier a <> emit "." <> pgQuotedIdentifier b unqualifiedField = PgFieldNameSyntax . pgQuotedIdentifier instance IsSql92TableSourceSyntax PgTableSourceSyntax where type Sql92TableSourceSelectSyntax PgTableSourceSyntax = PgSelectSyntax type Sql92TableSourceExpressionSyntax PgTableSourceSyntax = PgExpressionSyntax type Sql92TableSourceTableNameSyntax PgTableSourceSyntax = PgTableNameSyntax tableNamed = PgTableSourceSyntax . fromPgTableName tableFromSubSelect s = PgTableSourceSyntax $ emit "(" <> fromPgSelect s <> emit ")" tableFromValues vss = PgTableSourceSyntax . pgParens $ emit "VALUES " <> pgSepBy (emit ", ") (map (\vs -> pgParens (pgSepBy (emit ", ") (map fromPgExpression vs))) vss) instance IsSql92ProjectionSyntax PgProjectionSyntax where type Sql92ProjectionExpressionSyntax PgProjectionSyntax = PgExpressionSyntax projExprs exprs = PgProjectionSyntax $ pgSepBy (emit ", ") (map (\(expr, nm) -> fromPgExpression expr <> maybe mempty (\nm -> emit " AS " <> pgQuotedIdentifier nm) nm) exprs) instance IsSql92InsertSyntax PgInsertSyntax where type Sql92InsertTableNameSyntax PgInsertSyntax = PgTableNameSyntax type Sql92InsertValuesSyntax PgInsertSyntax = PgInsertValuesSyntax insertStmt tblName fields values = PgInsertSyntax $ emit "INSERT INTO " <> fromPgTableName tblName <> emit "(" <> pgSepBy (emit ", ") (map pgQuotedIdentifier fields) <> emit ") " <> fromPgInsertValues values instance IsSql92InsertValuesSyntax PgInsertValuesSyntax where type Sql92InsertValuesExpressionSyntax PgInsertValuesSyntax = PgExpressionSyntax type Sql92InsertValuesSelectSyntax PgInsertValuesSyntax = PgSelectSyntax insertSqlExpressions es = PgInsertValuesSyntax $ emit "VALUES " <> pgSepBy (emit ", ") (map (\es -> emit "(" <> pgSepBy (emit ", ") (coerce es) <> emit ")") es) insertFromSql (PgSelectSyntax a) = PgInsertValuesSyntax a instance IsSql92DropTableSyntax PgDropTableSyntax where type Sql92DropTableTableNameSyntax PgDropTableSyntax = PgTableNameSyntax dropTableSyntax tblNm = PgDropTableSyntax $ emit "DROP TABLE " <> fromPgTableName tblNm instance IsSql92AlterTableSyntax PgAlterTableSyntax where type Sql92AlterTableAlterTableActionSyntax PgAlterTableSyntax = PgAlterTableActionSyntax type Sql92AlterTableTableNameSyntax PgAlterTableSyntax = PgTableNameSyntax alterTableSyntax tblNm action = PgAlterTableSyntax $ emit "ALTER TABLE " <> fromPgTableName tblNm <> emit " " <> fromPgAlterTableAction action instance IsSql92AlterTableActionSyntax PgAlterTableActionSyntax where type Sql92AlterTableAlterColumnActionSyntax PgAlterTableActionSyntax = PgAlterColumnActionSyntax type Sql92AlterTableColumnSchemaSyntax PgAlterTableActionSyntax = PgColumnSchemaSyntax alterColumnSyntax colNm action = PgAlterTableActionSyntax $ emit "ALTER COLUMN " <> pgQuotedIdentifier colNm <> emit " " <> fromPgAlterColumnAction action addColumnSyntax colNm schema = PgAlterTableActionSyntax $ emit "ADD COLUMN " <> pgQuotedIdentifier colNm <> emit " " <> fromPgColumnSchema schema dropColumnSyntax colNm = PgAlterTableActionSyntax $ emit "DROP COLUMN " <> pgQuotedIdentifier colNm renameTableToSyntax newNm = PgAlterTableActionSyntax $ emit "RENAME TO " <> pgQuotedIdentifier newNm renameColumnToSyntax oldNm newNm = PgAlterTableActionSyntax $ emit "RENAME COLUMN " <> pgQuotedIdentifier oldNm <> emit " TO " <> pgQuotedIdentifier newNm instance IsSql92AlterColumnActionSyntax PgAlterColumnActionSyntax where setNullSyntax = PgAlterColumnActionSyntax (emit "DROP NOT NULL") setNotNullSyntax = PgAlterColumnActionSyntax (emit "SET NOT NULL") instance IsSql92CreateTableSyntax PgCreateTableSyntax where type Sql92CreateTableTableNameSyntax PgCreateTableSyntax = PgTableNameSyntax type Sql92CreateTableColumnSchemaSyntax PgCreateTableSyntax = PgColumnSchemaSyntax type Sql92CreateTableTableConstraintSyntax PgCreateTableSyntax = PgTableConstraintSyntax type Sql92CreateTableOptionsSyntax PgCreateTableSyntax = PgTableOptionsSyntax createTableSyntax options tblNm fieldTypes constraints = let (beforeOptions, afterOptions) = case options of Nothing -> (emit " ", emit " ") Just (PgTableOptionsSyntax before after) -> ( emit " " <> before <> emit " " , emit " " <> after <> emit " " ) in PgCreateTableSyntax $ emit "CREATE" <> beforeOptions <> emit "TABLE " <> fromPgTableName tblNm <> emit " (" <> pgSepBy (emit ", ") (map (\(nm, type_) -> pgQuotedIdentifier nm <> emit " " <> fromPgColumnSchema type_) fieldTypes <> map fromPgTableConstraint constraints) <> emit ")" <> afterOptions instance IsSql92TableConstraintSyntax PgTableConstraintSyntax where primaryKeyConstraintSyntax fieldNames = PgTableConstraintSyntax $ emit "PRIMARY KEY(" <> pgSepBy (emit ", ") (map pgQuotedIdentifier fieldNames) <> emit ")" instance Hashable PgColumnSchemaSyntax where hashWithSalt salt = hashWithSalt salt . fromPgColumnSchema instance IsSql92ColumnSchemaSyntax PgColumnSchemaSyntax where type Sql92ColumnSchemaColumnTypeSyntax PgColumnSchemaSyntax = PgDataTypeSyntax type Sql92ColumnSchemaExpressionSyntax PgColumnSchemaSyntax = PgExpressionSyntax type Sql92ColumnSchemaColumnConstraintDefinitionSyntax PgColumnSchemaSyntax = PgColumnConstraintDefinitionSyntax columnSchemaSyntax colType defaultClause constraints collation = PgColumnSchemaSyntax syntax where syntax = fromPgDataType colType <> maybe mempty (\d -> emit " DEFAULT " <> fromPgExpression d) defaultClause <> (case constraints of [] -> mempty _ -> foldMap (\c -> emit " " <> fromPgColumnConstraintDefinition c) constraints) <> maybe mempty (\nm -> emit " COLLATE " <> pgQuotedIdentifier nm) collation instance IsSql92MatchTypeSyntax PgMatchTypeSyntax where fullMatchSyntax = PgMatchTypeSyntax (emit "FULL") fullMatchSyntax partialMatchSyntax = PgMatchTypeSyntax (emit "PARTIAL") partialMatchSyntax pgMatchTypeJSON :: Value -> BeamSerializedMatchType pgMatchTypeJSON v = BeamSerializedMatchType (beamSerializeJSON "postgres" v) pgSimpleMatchSyntax :: PgMatchTypeSyntax pgSimpleMatchSyntax = PgMatchTypeSyntax (emit "SIMPLE") (pgMatchTypeJSON "simple") instance IsSql92ReferentialActionSyntax PgReferentialActionSyntax where referentialActionCascadeSyntax = PgReferentialActionSyntax (emit "CASCADE") referentialActionCascadeSyntax referentialActionNoActionSyntax = PgReferentialActionSyntax (emit "NO ACTION") referentialActionNoActionSyntax referentialActionSetDefaultSyntax = PgReferentialActionSyntax (emit "SET DEFAULT") referentialActionSetDefaultSyntax referentialActionSetNullSyntax = PgReferentialActionSyntax (emit "SET NULL") referentialActionSetNullSyntax fromSqlConstraintAttributes :: SqlConstraintAttributesBuilder -> PgSyntax fromSqlConstraintAttributes (SqlConstraintAttributesBuilder timing deferrable) = maybe mempty timingBuilder timing <> maybe mempty deferrableBuilder deferrable where timingBuilder InitiallyDeferred = emit "INITIALLY DEFERRED" timingBuilder InitiallyImmediate = emit "INITIALLY IMMEDIATE" deferrableBuilder False = emit "NOT DEFERRABLE" deferrableBuilder True = emit "DEFERRABLE" instance Hashable PgColumnConstraintDefinitionSyntax where hashWithSalt salt = hashWithSalt salt . fromPgColumnConstraintDefinition instance IsSql92ColumnConstraintDefinitionSyntax PgColumnConstraintDefinitionSyntax where type Sql92ColumnConstraintDefinitionConstraintSyntax PgColumnConstraintDefinitionSyntax = PgColumnConstraintSyntax type Sql92ColumnConstraintDefinitionAttributesSyntax PgColumnConstraintDefinitionSyntax = SqlConstraintAttributesBuilder constraintDefinitionSyntax nm constraint attrs = PgColumnConstraintDefinitionSyntax syntax (constraintDefinitionSyntax nm (pgColumnConstraintSerialized constraint) (fmap sqlConstraintAttributesSerialized attrs)) where syntax = maybe mempty (\nm -> emit "CONSTRAINT " <> pgQuotedIdentifier nm <> emit " " ) nm <> fromPgColumnConstraint constraint <> maybe mempty (\a -> emit " " <> fromSqlConstraintAttributes a) attrs instance Sql92SerializableConstraintDefinitionSyntax PgColumnConstraintDefinitionSyntax where serializeConstraint = fromBeamSerializedConstraintDefinition . pgColumnConstraintDefinitionSerialized instance IsSql92ColumnConstraintSyntax PgColumnConstraintSyntax where type Sql92ColumnConstraintMatchTypeSyntax PgColumnConstraintSyntax = PgMatchTypeSyntax type Sql92ColumnConstraintReferentialActionSyntax PgColumnConstraintSyntax = PgReferentialActionSyntax type Sql92ColumnConstraintExpressionSyntax PgColumnConstraintSyntax = PgExpressionSyntax notNullConstraintSyntax = PgColumnConstraintSyntax (emit "NOT NULL") notNullConstraintSyntax uniqueColumnConstraintSyntax = PgColumnConstraintSyntax (emit "UNIQUE") uniqueColumnConstraintSyntax primaryKeyColumnConstraintSyntax = PgColumnConstraintSyntax (emit "PRIMARY KEY") primaryKeyColumnConstraintSyntax checkColumnConstraintSyntax expr = PgColumnConstraintSyntax (emit "CHECK(" <> fromPgExpression expr <> emit ")") (checkColumnConstraintSyntax . BeamSerializedExpression . TE.decodeUtf8 . toStrict . pgRenderSyntaxScript . fromPgExpression $ expr) referencesConstraintSyntax tbl fields matchType onUpdate onDelete = PgColumnConstraintSyntax syntax (referencesConstraintSyntax tbl fields (fmap pgMatchTypeSerialized matchType) (fmap pgReferentialActionSerialized onUpdate) (fmap pgReferentialActionSerialized onDelete)) where syntax = emit "REFERENCES " <> pgQuotedIdentifier tbl <> emit "(" <> pgSepBy (emit ", ") (map pgQuotedIdentifier fields) <> emit ")" <> maybe mempty (\m -> emit " " <> fromPgMatchType m) matchType <> maybe mempty (\a -> emit " ON UPDATE " <> fromPgReferentialAction a) onUpdate <> maybe mempty (\a -> emit " ON DELETE " <> fromPgReferentialAction a) onDelete defaultPgValueSyntax :: Pg.ToField a => a -> PgValueSyntax defaultPgValueSyntax = PgValueSyntax . pgBuildAction . pure . Pg.toField -- Database Predicates data PgHasEnum = PgHasEnum T.Text {- Enumeration name -} [T.Text] {- enum values -} deriving (Show, Eq, Generic) instance Hashable PgHasEnum instance DatabasePredicate PgHasEnum where englishDescription (PgHasEnum enumName values) = "Has postgres enumeration " ++ show enumName ++ " with values " ++ show values predicateSpecificity _ = PredicateSpecificityOnlyBackend "postgres" serializePredicate (PgHasEnum name values) = object [ "has-postgres-enum" .= object [ "name" .= name , "values" .= values ] ] #define DEFAULT_SQL_SYNTAX(ty) \ instance HasSqlValueSyntax PgValueSyntax ty where \ sqlValueSyntax = defaultPgValueSyntax DEFAULT_SQL_SYNTAX(Bool) DEFAULT_SQL_SYNTAX(Double) DEFAULT_SQL_SYNTAX(Float) DEFAULT_SQL_SYNTAX(Int) DEFAULT_SQL_SYNTAX(Int8) DEFAULT_SQL_SYNTAX(Int16) DEFAULT_SQL_SYNTAX(Int32) DEFAULT_SQL_SYNTAX(Int64) DEFAULT_SQL_SYNTAX(Integer) DEFAULT_SQL_SYNTAX(Word) DEFAULT_SQL_SYNTAX(Word8) DEFAULT_SQL_SYNTAX(Word16) DEFAULT_SQL_SYNTAX(Word32) DEFAULT_SQL_SYNTAX(Word64) DEFAULT_SQL_SYNTAX(T.Text) DEFAULT_SQL_SYNTAX(TL.Text) DEFAULT_SQL_SYNTAX(Value) DEFAULT_SQL_SYNTAX(Pg.Oid) DEFAULT_SQL_SYNTAX(LocalTime) DEFAULT_SQL_SYNTAX(UTCTime) DEFAULT_SQL_SYNTAX(TimeOfDay) DEFAULT_SQL_SYNTAX(NominalDiffTime) DEFAULT_SQL_SYNTAX(Day) DEFAULT_SQL_SYNTAX(UUID) DEFAULT_SQL_SYNTAX([Char]) DEFAULT_SQL_SYNTAX(Pg.HStoreMap) DEFAULT_SQL_SYNTAX(Pg.HStoreList) DEFAULT_SQL_SYNTAX(Pg.HStoreBuilder) DEFAULT_SQL_SYNTAX(Pg.Date) DEFAULT_SQL_SYNTAX(Pg.LocalTimestamp) DEFAULT_SQL_SYNTAX(Pg.UTCTimestamp) DEFAULT_SQL_SYNTAX(Scientific) instance HasSqlValueSyntax PgValueSyntax (CI T.Text) where sqlValueSyntax = sqlValueSyntax . CI.original instance HasSqlValueSyntax PgValueSyntax (CI TL.Text) where sqlValueSyntax = sqlValueSyntax . CI.original instance HasSqlValueSyntax PgValueSyntax SqlNull where sqlValueSyntax _ = defaultPgValueSyntax Pg.Null instance HasSqlValueSyntax PgValueSyntax x => HasSqlValueSyntax PgValueSyntax (Maybe x) where sqlValueSyntax Nothing = sqlValueSyntax SqlNull sqlValueSyntax (Just x) = sqlValueSyntax x instance HasSqlValueSyntax PgValueSyntax B.ByteString where sqlValueSyntax = defaultPgValueSyntax . Pg.Binary instance HasSqlValueSyntax PgValueSyntax BL.ByteString where sqlValueSyntax = defaultPgValueSyntax . Pg.Binary instance Pg.ToField a => HasSqlValueSyntax PgValueSyntax (V.Vector a) where sqlValueSyntax = defaultPgValueSyntax pgQuotedIdentifier :: T.Text -> PgSyntax pgQuotedIdentifier t = escapeIdentifier (TE.encodeUtf8 t) pgParens :: PgSyntax -> PgSyntax pgParens a = emit "(" <> a <> emit ")" pgTableOp :: ByteString -> PgSelectTableSyntax -> PgSelectTableSyntax -> PgSelectTableSyntax pgTableOp op tbl1 tbl2 = PgSelectTableSyntax $ emit "(" <> fromPgSelectTable tbl1 <> emit ") " <> emit op <> emit " (" <> fromPgSelectTable tbl2 <> emit ")" pgCompOp :: ByteString -> Maybe PgComparisonQuantifierSyntax -> PgExpressionSyntax -> PgExpressionSyntax -> PgExpressionSyntax pgCompOp op quantifier a b = PgExpressionSyntax $ emit "(" <> fromPgExpression a <> emit (") " <> op) <> maybe (emit " (" <> fromPgExpression b <> emit ")") (\q -> emit " " <> fromPgComparisonQuantifier q <> emit " " <> fromPgExpression b) quantifier pgBinOp :: ByteString -> PgExpressionSyntax -> PgExpressionSyntax -> PgExpressionSyntax pgBinOp op a b = PgExpressionSyntax $ emit "(" <> fromPgExpression a <> emit (") " <> op <> " (") <> fromPgExpression b <> emit ")" pgPostFix, pgUnOp :: ByteString -> PgExpressionSyntax -> PgExpressionSyntax pgPostFix op a = PgExpressionSyntax $ emit "(" <> fromPgExpression a <> emit ") " <> emit op pgUnOp op a = PgExpressionSyntax $ emit (op <> "(") <> fromPgExpression a <> emit ")" pgJoin :: ByteString -> PgFromSyntax -> PgFromSyntax -> Maybe PgExpressionSyntax -> PgFromSyntax pgJoin joinType a b Nothing = PgFromSyntax $ fromPgFrom a <> emit (" " <> joinType <> " ") <> fromPgFrom b <> emit " ON TRUE" pgJoin joinType a b (Just on) = PgFromSyntax $ fromPgFrom a <> emit (" " <> joinType <> " ") <> fromPgFrom b <> emit " ON " <> fromPgExpression on pgSepBy :: PgSyntax -> [PgSyntax] -> PgSyntax pgSepBy _ [] = mempty pgSepBy _ [x] = x pgSepBy sep (x:xs) = x <> sep <> pgSepBy sep xs pgDebugRenderSyntax :: PgSyntax -> IO () pgDebugRenderSyntax (PgSyntax p) = go p Nothing where go :: PgSyntaxM () -> Maybe (PgSyntaxF ()) -> IO () go p = runF p finish step step x lastBs = case (x, lastBs) of (EmitBuilder s next, lastBs) -> step (EmitByteString (toStrict (toLazyByteString s)) next) lastBs (x, Nothing) -> nextSyntaxStep x (Just (fmap (const ()) x)) (EmitByteString x next, Just (EmitByteString before _)) -> next (Just (EmitByteString (before <> x) ())) (EscapeString x next, Just (EscapeString before _)) -> next (Just (EscapeString (before <> x) ())) (EscapeBytea x next, Just (EscapeBytea before _)) -> next (Just (EscapeBytea (before <> x) ())) (EscapeIdentifier x next, Just (EscapeIdentifier before _)) -> next (Just (EscapeIdentifier (before <> x) ())) (s, Just e) -> renderStep e >> nextSyntaxStep s (Just (fmap (const ()) s)) renderStep (EmitByteString x _) = putStrLn ("EmitByteString " <> show x) renderStep (EmitBuilder x _) = putStrLn ("EmitBuilder " <> show (toLazyByteString x)) renderStep (EscapeString x _) = putStrLn ("EscapeString " <> show x) renderStep (EscapeBytea x _) = putStrLn ("EscapeBytea " <> show x) renderStep (EscapeIdentifier x _) = putStrLn ("EscapeIdentifier " <> show x) finish x Nothing = pure x finish x (Just s) = renderStep s >> pure x pgBuildAction :: [ Pg.Action ] -> PgSyntax pgBuildAction = foldMap $ \action -> case action of Pg.Plain x -> emitBuilder x Pg.Escape str -> emit "'" <> escapeString str <> emit "'" Pg.EscapeByteA bin -> emit "'" <> escapeBytea bin <> emit "'" Pg.EscapeIdentifier id -> escapeIdentifier id Pg.Many as -> pgBuildAction as -- * Postgres-specific extensions -- * Postgres specific commands pgSelectStmt :: PgSelectTableSyntax -> [PgOrderingSyntax] -> Maybe Integer {-^ LIMIT -} -> Maybe Integer {-^ OFFSET -} -> Maybe PgSelectLockingClauseSyntax -> PgSelectSyntax pgSelectStmt tbl ordering limit offset locking = PgSelectSyntax $ mconcat [ coerce tbl , case ordering of [] -> mempty ordering -> emit " ORDER BY " <> pgSepBy (emit ", ") (map fromPgOrdering ordering) , maybe mempty (emit . fromString . (" LIMIT " <>) . show) limit , maybe mempty (emit . fromString . (" OFFSET " <>) . show) offset , maybe mempty fromPgSelectLockingClause locking ] pgCreateExtensionSyntax :: T.Text -> PgCommandSyntax pgCreateExtensionSyntax extName = PgCommandSyntax PgCommandTypeDdl $ emit "CREATE EXTENSION " <> pgQuotedIdentifier extName pgDropExtensionSyntax :: T.Text -> PgCommandSyntax pgDropExtensionSyntax extName = PgCommandSyntax PgCommandTypeDdl $ emit "DROP EXTENSION " <> pgQuotedIdentifier extName pgCreateEnumSyntax :: T.Text -> [PgValueSyntax] -> PgCommandSyntax pgCreateEnumSyntax enumName vals = PgCommandSyntax PgCommandTypeDdl $ emit "CREATE TYPE " <> pgQuotedIdentifier enumName <> emit " AS ENUM(" <> pgSepBy (emit ", ") (fmap fromPgValue vals) <> emit ")" pgDropTypeSyntax :: T.Text -> PgCommandSyntax pgDropTypeSyntax typeName = PgCommandSyntax PgCommandTypeDdl $ emit "DROP TYPE " <> pgQuotedIdentifier typeName -- -- * Pg-specific Q monad data PgEscapeType = PgEscapeString | PgEscapeBytea | PgEscapeIdentifier deriving (Show, Eq, Ord, Enum, Bounded) data PgSyntaxPrim = PgSyntaxPrim (Maybe PgEscapeType) BL.ByteString deriving Show instance IsString PgSyntaxPrim where fromString = PgSyntaxPrim Nothing . fromString pgTestSyntax :: PgSyntax -> [ PgSyntaxPrim ] pgTestSyntax (PgSyntax syntax) = runF syntax finish step Nothing mempty id where finish _ escapeType curBuilder a = let chunk = toLazyByteString curBuilder in if BL.null chunk then a [] else a [ PgSyntaxPrim escapeType chunk ] go next curType nextType curBuilder nextBuilder a | curType == nextType = next curType (curBuilder <> nextBuilder) a | otherwise = next nextType mempty (a . (PgSyntaxPrim curType (toLazyByteString curBuilder):)) step (EmitByteString bs next) curType curBuilder a = go next curType Nothing curBuilder (byteString bs) a step (EmitBuilder bs next) curType curBuilder a = go next curType Nothing curBuilder bs a step (EscapeString s next) curType curBuilder a = go next curType (Just PgEscapeString) curBuilder (byteString s) a step (EscapeBytea s next) curType curBuilder a = go next curType (Just PgEscapeBytea) curBuilder (byteString s) a step (EscapeIdentifier s next) curType curBuilder a = go next curType (Just PgEscapeIdentifier) curBuilder (byteString s) a pgRenderSyntaxScript :: PgSyntax -> BL.ByteString pgRenderSyntaxScript (PgSyntax mkQuery) = toLazyByteString (runF mkQuery finish step) where finish _ = mempty step (EmitBuilder b next) = b <> next step (EmitByteString b next) = byteString b <> next step (EscapeString b next) = escapePgString b <> next step (EscapeBytea b next) = escapePgBytea b <> next step (EscapeIdentifier b next) = escapePgIdentifier b <> next escapePgString b = byteString (B.concatMap (\w -> if w == '\'' then "''" else B.singleton w) b) escapePgBytea _ = error "escapePgBytea: no connection" escapePgIdentifier bs = char8 '"' <> foldMap quoteIdentifierChar (B.unpack bs) <> char8 '"' where quoteIdentifierChar '"' = char8 '"' <> char8 '"' quoteIdentifierChar c = char8 c