packages feed

orville-postgresql 1.0.0.0 → 1.1.0.0

raw patch · 176 files changed

+13233/−3119 lines, 176 filesdep +unliftiodep ~containersdep ~hedgehogdep ~postgresql-libpqPVP ok

version bump matches the API change (PVP)

Dependencies added: unliftio

Dependency ranges changed: containers, hedgehog, postgresql-libpq, resource-pool, time

API changes (from Hackage documentation)

- Orville.PostgreSQL.Expr.TableReferenceList: referencesTable :: Qualified TableName -> TableReferenceList
- Orville.PostgreSQL.PgCatalog: describeDatabaseRelations :: MonadOrville m => [(NamespaceName, RelationName)] -> m DatabaseDescription
- Orville.PostgreSQL.Raw.SqlValue: toPgValue :: SqlValue -> Maybe PgTextFormatValue
+ Orville.PostgreSQL: AttributeBasedIndexKey :: AttributeBasedIndexMigrationKey -> IndexMigrationKey
+ Orville.PostgreSQL: AttributeBasedIndexMigrationKey :: IndexUniqueness -> [FieldName] -> AttributeBasedIndexMigrationKey
+ Orville.PostgreSQL: Commit :: TransactionInstruction
+ Orville.PostgreSQL: InOutermostTransaction :: InWithTransaction
+ Orville.PostgreSQL: InSavepointTransaction :: Natural -> InWithTransaction
+ Orville.PostgreSQL: NamedIndexKey :: NamedIndexMigrationKey -> IndexMigrationKey
+ Orville.PostgreSQL: NamedTriggerKey :: String -> TriggerMigrationKey
+ Orville.PostgreSQL: Rollback :: TransactionInstruction
+ Orville.PostgreSQL: [indexKeyColumns] :: AttributeBasedIndexMigrationKey -> [FieldName]
+ Orville.PostgreSQL: [indexKeyUniqueness] :: AttributeBasedIndexMigrationKey -> IndexUniqueness
+ Orville.PostgreSQL: addTableTriggers :: [TriggerDefinition] -> TableDefinition key writeEntity readEntity -> TableDefinition key writeEntity readEntity
+ Orville.PostgreSQL: afterDelete :: String -> QualifiedOrUnqualified FunctionName -> TriggerDefinition
+ Orville.PostgreSQL: afterInsert :: String -> QualifiedOrUnqualified FunctionName -> TriggerDefinition
+ Orville.PostgreSQL: afterUpdate :: String -> QualifiedOrUnqualified FunctionName -> TriggerDefinition
+ Orville.PostgreSQL: aliasNameAndFieldNameToColumnName :: AliasName -> FieldName -> Qualified ColumnName
+ Orville.PostgreSQL: aliasNameToAliasExpr :: AliasName -> AliasExpr
+ Orville.PostgreSQL: aliasNameToByteString :: AliasName -> ByteString
+ Orville.PostgreSQL: aliasNameToString :: AliasName -> String
+ Orville.PostgreSQL: beforeDelete :: String -> QualifiedOrUnqualified FunctionName -> TriggerDefinition
+ Orville.PostgreSQL: beforeInsert :: String -> QualifiedOrUnqualified FunctionName -> TriggerDefinition
+ Orville.PostgreSQL: beforeUpdate :: String -> QualifiedOrUnqualified FunctionName -> TriggerDefinition
+ Orville.PostgreSQL: byteStringToAliasName :: ByteString -> AliasName
+ Orville.PostgreSQL: class FieldQualifier qualifier
+ Orville.PostgreSQL: class SqlComparable sqlAbstraction haskellValue | sqlAbstraction -> haskellValue
+ Orville.PostgreSQL: data AliasName
+ Orville.PostgreSQL: data AttributeBasedIndexMigrationKey
+ Orville.PostgreSQL: data ExtensionIdentifier
+ Orville.PostgreSQL: data FunctionDefinition
+ Orville.PostgreSQL: data FunctionIdentifier
+ Orville.PostgreSQL: data InWithTransaction
+ Orville.PostgreSQL: data IndexMigrationKey
+ Orville.PostgreSQL: data QualifiedFieldDefinition qualifier nullability a
+ Orville.PostgreSQL: data TransactionInstruction
+ Orville.PostgreSQL: data TriggerDefinition
+ Orville.PostgreSQL: extensionIdName :: ExtensionIdentifier -> ExtensionName
+ Orville.PostgreSQL: extensionIdToString :: ExtensionIdentifier -> String
+ Orville.PostgreSQL: fetchRow :: FetchClause -> SelectOptions
+ Orville.PostgreSQL: fieldIsDistinctFrom :: FieldDefinition nullability a -> a -> BooleanExpr
+ Orville.PostgreSQL: fieldIsNotDistinctFrom :: FieldDefinition nullability a -> a -> BooleanExpr
+ Orville.PostgreSQL: forRowLock :: RowLockingClause -> SelectOptions
+ Orville.PostgreSQL: functionIdQualifiedName :: FunctionIdentifier -> QualifiedOrUnqualified FunctionName
+ Orville.PostgreSQL: functionIdSchemaNameString :: FunctionIdentifier -> Maybe String
+ Orville.PostgreSQL: functionIdToString :: FunctionIdentifier -> String
+ Orville.PostgreSQL: functionIdUnqualifiedNameString :: FunctionIdentifier -> String
+ Orville.PostgreSQL: functionIdentifier :: FunctionDefinition -> FunctionIdentifier
+ Orville.PostgreSQL: functionName :: FunctionDefinition -> QualifiedOrUnqualified FunctionName
+ Orville.PostgreSQL: functionSource :: FunctionDefinition -> String
+ Orville.PostgreSQL: inWithTransaction :: MonadOrville m => m (Maybe InWithTransaction)
+ Orville.PostgreSQL: indexMigrationKey :: IndexDefinition -> IndexMigrationKey
+ Orville.PostgreSQL: keyShareStrength :: RowLockingStrengthExpr
+ Orville.PostgreSQL: markAsIdentity :: FieldIdentityGeneration -> FieldDefinition NotNull a -> FieldDefinition NotNull a
+ Orville.PostgreSQL: marshallQualifyFields :: FieldQualifier qualifier => qualifier -> SqlMarshaller a b -> SqlMarshaller a b
+ Orville.PostgreSQL: mkCreateFunctionExpr :: FunctionDefinition -> Maybe OrReplace -> CreateFunctionExpr
+ Orville.PostgreSQL: mkCreateTriggerExpr :: TriggerDefinition -> Maybe OrReplace -> QualifiedOrUnqualified TableName -> CreateTriggerExpr
+ Orville.PostgreSQL: mkTriggerDefinition :: String -> TriggerTiming -> NonEmpty TriggerEvent -> TriggerFireScope -> QualifiedOrUnqualified FunctionName -> TriggerDefinition
+ Orville.PostgreSQL: mkTriggerFunction :: String -> LanguageName -> String -> FunctionDefinition
+ Orville.PostgreSQL: nameToExtensionId :: String -> ExtensionIdentifier
+ Orville.PostgreSQL: newtype TriggerMigrationKey
+ Orville.PostgreSQL: noKeyUpdateStrength :: RowLockingStrengthExpr
+ Orville.PostgreSQL: noWaitRowLockingOption :: RowLockingOptionExpr
+ Orville.PostgreSQL: orderBySqlComparable :: SqlComparable comparable value => comparable -> OrderByDirection -> OrderByExpr
+ Orville.PostgreSQL: orderBySyntheticField :: SyntheticField a -> OrderByDirection -> OrderByExpr
+ Orville.PostgreSQL: plpgsql :: LanguageName
+ Orville.PostgreSQL: qualifiedFieldColumnName :: FieldQualifier qualifier => QualifiedFieldDefinition qualifier nullability a -> Qualified ColumnName
+ Orville.PostgreSQL: qualifyColumnName :: FieldQualifier qualifier => qualifier -> ColumnName -> Qualified ColumnName
+ Orville.PostgreSQL: qualifyField :: qualifier -> FieldDefinition nullability a -> QualifiedFieldDefinition qualifier nullability a
+ Orville.PostgreSQL: referenceValueExpression :: SqlComparable sqlAbstraction haskellValue => sqlAbstraction -> ValueExpression
+ Orville.PostgreSQL: rowLockingClause :: RowLockingStrengthExpr -> Maybe (NonEmpty TableName) -> Maybe RowLockingOptionExpr -> RowLockingClause
+ Orville.PostgreSQL: selectFetchClause :: SelectOptions -> Maybe FetchClause
+ Orville.PostgreSQL: selectRowLockingClause :: SelectOptions -> Maybe RowLockingClause
+ Orville.PostgreSQL: setFunctionIdSchema :: String -> FunctionIdentifier -> FunctionIdentifier
+ Orville.PostgreSQL: setFunctionSchema :: String -> FunctionDefinition -> FunctionDefinition
+ Orville.PostgreSQL: setTableComment :: Text -> TableDefinition key writeEntity readEntity -> TableDefinition key writeEntity readEntity
+ Orville.PostgreSQL: shareStrength :: RowLockingStrengthExpr
+ Orville.PostgreSQL: skipLockedRowLockingOption :: RowLockingOptionExpr
+ Orville.PostgreSQL: stringToAliasName :: String -> AliasName
+ Orville.PostgreSQL: syntheticFieldName :: SyntheticField a -> FieldName
+ Orville.PostgreSQL: tableComment :: TableDefinition key writeEntity readEntity -> Maybe Text
+ Orville.PostgreSQL: tableTriggers :: TableDefinition key writeEntity readEntity -> Map TriggerMigrationKey TriggerDefinition
+ Orville.PostgreSQL: toComparableSqlValue :: SqlComparable sqlAbstraction haskellValue => sqlAbstraction -> haskellValue -> SqlValue
+ Orville.PostgreSQL: triggerMigrationKey :: TriggerDefinition -> TriggerMigrationKey
+ Orville.PostgreSQL: type NamedIndexMigrationKey = String
+ Orville.PostgreSQL: unmarkIdentity :: FieldDefinition NotNull a -> FieldDefinition NotNull a
+ Orville.PostgreSQL: unqualifiedNameToFunctionId :: String -> FunctionIdentifier
+ Orville.PostgreSQL: updateStrength :: RowLockingStrengthExpr
+ Orville.PostgreSQL: window :: NamedWindowDefinitionExpr -> SelectOptions
+ Orville.PostgreSQL: withTransactionInstruction :: MonadOrville m => m (a, TransactionInstruction) -> m a
+ Orville.PostgreSQL.AutoMigration: [SchemaDropExtension] :: ExtensionIdentifier -> SchemaItem
+ Orville.PostgreSQL.AutoMigration: [SchemaDropFunction] :: FunctionIdentifier -> SchemaItem
+ Orville.PostgreSQL.AutoMigration: [SchemaExtension] :: ExtensionIdentifier -> SchemaItem
+ Orville.PostgreSQL.AutoMigration: [SchemaFunction] :: FunctionDefinition -> SchemaItem
+ Orville.PostgreSQL.AutoMigration: data MigrationLockOptions
+ Orville.PostgreSQL.AutoMigration: defaultLockOptions :: MigrationLockOptions
+ Orville.PostgreSQL.AutoMigration: delayBetweenLockAttemptsMicros :: MigrationLockOptions -> Int
+ Orville.PostgreSQL.AutoMigration: lockDelayVariationMicros :: MigrationLockOptions -> Int
+ Orville.PostgreSQL.AutoMigration: maxLockAttempts :: MigrationLockOptions -> Int
+ Orville.PostgreSQL.AutoMigration: migrationLockId :: MigrationLockOptions -> MigrationLockId
+ Orville.PostgreSQL.Execution.SelectOptions: fetchRow :: FetchClause -> SelectOptions
+ Orville.PostgreSQL.Execution.SelectOptions: forRowLock :: RowLockingClause -> SelectOptions
+ Orville.PostgreSQL.Execution.SelectOptions: selectFetchClause :: SelectOptions -> Maybe FetchClause
+ Orville.PostgreSQL.Execution.SelectOptions: selectRowLockingClause :: SelectOptions -> Maybe RowLockingClause
+ Orville.PostgreSQL.Execution.SelectOptions: selectWindowClause :: SelectOptions -> Maybe WindowClause
+ Orville.PostgreSQL.Execution.SelectOptions: window :: NamedWindowDefinitionExpr -> SelectOptions
+ Orville.PostgreSQL.Execution.Transaction: Commit :: TransactionInstruction
+ Orville.PostgreSQL.Execution.Transaction: InOutermostTransaction :: InWithTransaction
+ Orville.PostgreSQL.Execution.Transaction: InSavepointTransaction :: Natural -> InWithTransaction
+ Orville.PostgreSQL.Execution.Transaction: Rollback :: TransactionInstruction
+ Orville.PostgreSQL.Execution.Transaction: UnexpectedTransactionStatusError :: TransactionStatus -> TransactionEvent -> UnexpectedTransactionStatusError
+ Orville.PostgreSQL.Execution.Transaction: [unexpectedTransactionStatusErrorTransactionEvent] :: UnexpectedTransactionStatusError -> TransactionEvent
+ Orville.PostgreSQL.Execution.Transaction: [unexpectedTransactionStatusErrorTransactionStatus] :: UnexpectedTransactionStatusError -> TransactionStatus
+ Orville.PostgreSQL.Execution.Transaction: data InWithTransaction
+ Orville.PostgreSQL.Execution.Transaction: data TransactionInstruction
+ Orville.PostgreSQL.Execution.Transaction: data UnexpectedTransactionStatusError
+ Orville.PostgreSQL.Execution.Transaction: inWithTransaction :: MonadOrville m => m (Maybe InWithTransaction)
+ Orville.PostgreSQL.Execution.Transaction: instance GHC.Classes.Eq Orville.PostgreSQL.Execution.Transaction.InWithTransaction
+ Orville.PostgreSQL.Execution.Transaction: instance GHC.Classes.Ord Orville.PostgreSQL.Execution.Transaction.InWithTransaction
+ Orville.PostgreSQL.Execution.Transaction: instance GHC.Exception.Type.Exception Orville.PostgreSQL.Execution.Transaction.UnexpectedTransactionStatusError
+ Orville.PostgreSQL.Execution.Transaction: instance GHC.Show.Show Orville.PostgreSQL.Execution.Transaction.InWithTransaction
+ Orville.PostgreSQL.Execution.Transaction: instance GHC.Show.Show Orville.PostgreSQL.Execution.Transaction.UnexpectedTransactionStatusError
+ Orville.PostgreSQL.Execution.Transaction: withTransactionInstruction :: MonadOrville m => m (a, TransactionInstruction) -> m a
+ Orville.PostgreSQL.Expr.Aggregate: aggregateExpression :: FunctionName -> Maybe AggregateOptionExpr -> NonEmpty ValueExpression -> Maybe OrderByClause -> Maybe FilterExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.Aggregate: aggregateStarExpression :: FunctionName -> Maybe FilterExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.Aggregate: aggregateWithinGroupExpression :: FunctionName -> [ValueExpression] -> OrderByClause -> Maybe FilterExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.Aggregate: arrayAggAggregateFunction :: Maybe AggregateOptionExpr -> ValueExpression -> Maybe OrderByClause -> Maybe FilterExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.Aggregate: avgAggregateFunction :: Maybe AggregateOptionExpr -> ValueExpression -> Maybe OrderByClause -> Maybe FilterExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.Aggregate: bitAndAggregateFunction :: Maybe AggregateOptionExpr -> ValueExpression -> Maybe OrderByClause -> Maybe FilterExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.Aggregate: bitOrAggregateFunction :: Maybe AggregateOptionExpr -> ValueExpression -> Maybe OrderByClause -> Maybe FilterExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.Aggregate: bitXorAggregateFunction :: Maybe AggregateOptionExpr -> ValueExpression -> Maybe OrderByClause -> Maybe FilterExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.Aggregate: boolAndAggregateFunction :: Maybe AggregateOptionExpr -> ValueExpression -> Maybe OrderByClause -> Maybe FilterExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.Aggregate: boolOrAggregateFunction :: Maybe AggregateOptionExpr -> ValueExpression -> Maybe OrderByClause -> Maybe FilterExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.Aggregate: corAggregateFunction :: Maybe AggregateOptionExpr -> ValueExpression -> ValueExpression -> Maybe OrderByClause -> Maybe FilterExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.Aggregate: count1AggregateFunction :: ValueExpression
+ Orville.PostgreSQL.Expr.Aggregate: countAggregateFunction :: Maybe AggregateOptionExpr -> ValueExpression -> Maybe OrderByClause -> Maybe FilterExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.Aggregate: countColumnAggregateFunction :: QualifiedOrUnqualified ColumnName -> ValueExpression
+ Orville.PostgreSQL.Expr.Aggregate: countExprAggregateFunction :: ValueExpression -> ValueExpression
+ Orville.PostgreSQL.Expr.Aggregate: covarPopAggregateFunction :: Maybe AggregateOptionExpr -> ValueExpression -> ValueExpression -> Maybe OrderByClause -> Maybe FilterExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.Aggregate: covarSampAggregateFunction :: Maybe AggregateOptionExpr -> ValueExpression -> ValueExpression -> Maybe OrderByClause -> Maybe FilterExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.Aggregate: data AggregateOptionExpr
+ Orville.PostgreSQL.Expr.Aggregate: everyAggregateFunction :: Maybe AggregateOptionExpr -> ValueExpression -> Maybe OrderByClause -> Maybe FilterExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.Aggregate: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.Aggregate.AggregateOptionExpr
+ Orville.PostgreSQL.Expr.Aggregate: jsonAggAggregateFunction :: Maybe AggregateOptionExpr -> ValueExpression -> Maybe OrderByClause -> Maybe FilterExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.Aggregate: jsonObjectAggAggregateFunction :: Maybe AggregateOptionExpr -> ValueExpression -> ValueExpression -> Maybe OrderByClause -> Maybe FilterExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.Aggregate: jsonbAggAggregateFunction :: Maybe AggregateOptionExpr -> ValueExpression -> Maybe OrderByClause -> Maybe FilterExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.Aggregate: jsonbObjectAggAggregateFunction :: Maybe AggregateOptionExpr -> ValueExpression -> ValueExpression -> Maybe OrderByClause -> Maybe FilterExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.Aggregate: maxAggregateFunction :: Maybe AggregateOptionExpr -> ValueExpression -> Maybe OrderByClause -> Maybe FilterExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.Aggregate: minAggregateFunction :: Maybe AggregateOptionExpr -> ValueExpression -> Maybe OrderByClause -> Maybe FilterExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.Aggregate: stddevPopAggregateFunction :: Maybe AggregateOptionExpr -> ValueExpression -> ValueExpression -> Maybe OrderByClause -> Maybe FilterExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.Aggregate: stddevSampAggregateFunction :: Maybe AggregateOptionExpr -> ValueExpression -> ValueExpression -> Maybe OrderByClause -> Maybe FilterExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.Aggregate: stringAggAggregateFunction :: Maybe AggregateOptionExpr -> ValueExpression -> ValueExpression -> Maybe OrderByClause -> Maybe FilterExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.Aggregate: sumAggregateFunction :: Maybe AggregateOptionExpr -> ValueExpression -> Maybe OrderByClause -> Maybe FilterExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.Aggregate: varPopAggregateFunction :: Maybe AggregateOptionExpr -> ValueExpression -> ValueExpression -> Maybe OrderByClause -> Maybe FilterExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.Aggregate: varSampAggregateFunction :: Maybe AggregateOptionExpr -> ValueExpression -> ValueExpression -> Maybe OrderByClause -> Maybe FilterExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.BinaryOperator: isDistinctFromOp :: BinaryOperator
+ Orville.PostgreSQL.Expr.BinaryOperator: isNotDistinctFromOp :: BinaryOperator
+ Orville.PostgreSQL.Expr.ColumnDefinition: alwaysColumnIdentityGeneration :: ColumnIdentityGeneration
+ Orville.PostgreSQL.Expr.ColumnDefinition: byDefaultColumnIdentityGeneration :: ColumnIdentityGeneration
+ Orville.PostgreSQL.Expr.ColumnDefinition: data ColumnIdentityGeneration
+ Orville.PostgreSQL.Expr.ColumnDefinition: identityColumnConstraint :: ColumnIdentityGeneration -> ColumnConstraint
+ Orville.PostgreSQL.Expr.ColumnDefinition: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.ColumnDefinition.ColumnIdentityGeneration
+ Orville.PostgreSQL.Expr.Comment: commentColumnExpr :: Maybe SchemaName -> TableName -> ColumnName -> Maybe Comment -> CommentExpr
+ Orville.PostgreSQL.Expr.Comment: commentTableExpr :: QualifiedOrUnqualified TableName -> Maybe Comment -> CommentExpr
+ Orville.PostgreSQL.Expr.Comment: commentText :: Text -> Comment
+ Orville.PostgreSQL.Expr.Comment: data Comment
+ Orville.PostgreSQL.Expr.Comment: data CommentExpr
+ Orville.PostgreSQL.Expr.Comment: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.Comment.Comment
+ Orville.PostgreSQL.Expr.Comment: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.Comment.CommentExpr
+ Orville.PostgreSQL.Expr.ConditionalExpr: caseExpr :: NonEmpty WhenExpr -> Maybe ValueExpression -> ValueExpression
+ Orville.PostgreSQL.Expr.ConditionalExpr: coalesce :: NonEmpty ValueExpression -> ValueExpression
+ Orville.PostgreSQL.Expr.ConditionalExpr: data WhenExpr
+ Orville.PostgreSQL.Expr.ConditionalExpr: greatest :: NonEmpty ValueExpression -> ValueExpression
+ Orville.PostgreSQL.Expr.ConditionalExpr: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.ConditionalExpr.WhenExpr
+ Orville.PostgreSQL.Expr.ConditionalExpr: least :: NonEmpty ValueExpression -> ValueExpression
+ Orville.PostgreSQL.Expr.ConditionalExpr: nullIf :: ValueExpression -> ValueExpression -> ValueExpression
+ Orville.PostgreSQL.Expr.ConditionalExpr: whenExpr :: BooleanExpr -> ValueExpression -> WhenExpr
+ Orville.PostgreSQL.Expr.Extension: createExtensionExpr :: ExtensionName -> Maybe IfNotExists -> Maybe ExtensionActionExpr -> CreateExtensionExpr
+ Orville.PostgreSQL.Expr.Extension: data CreateExtensionExpr
+ Orville.PostgreSQL.Expr.Extension: data DropExtensionExpr
+ Orville.PostgreSQL.Expr.Extension: data ExtensionActionExpr
+ Orville.PostgreSQL.Expr.Extension: dropExtensionExpr :: ExtensionName -> Maybe IfExists -> Maybe ExtensionActionExpr -> DropExtensionExpr
+ Orville.PostgreSQL.Expr.Extension: extensionCascadeExpr :: ExtensionActionExpr
+ Orville.PostgreSQL.Expr.Extension: extensionRestrictExpr :: ExtensionActionExpr
+ Orville.PostgreSQL.Expr.Extension: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.Extension.CreateExtensionExpr
+ Orville.PostgreSQL.Expr.Extension: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.Extension.DropExtensionExpr
+ Orville.PostgreSQL.Expr.Extension: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.Extension.ExtensionActionExpr
+ Orville.PostgreSQL.Expr.FetchClause: data FetchClause
+ Orville.PostgreSQL.Expr.FetchClause: data FetchClauseModifier
+ Orville.PostgreSQL.Expr.FetchClause: fetchClauseInt :: Int -> FetchClauseModifier -> FetchClause
+ Orville.PostgreSQL.Expr.FetchClause: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.FetchClause.FetchClause
+ Orville.PostgreSQL.Expr.FetchClause: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.FetchClause.FetchClauseModifier
+ Orville.PostgreSQL.Expr.FetchClause: onlyFetchClauseModifier :: FetchClauseModifier
+ Orville.PostgreSQL.Expr.FetchClause: withTiesFetchClauseModifier :: FetchClauseModifier
+ Orville.PostgreSQL.Expr.Filter: data FilterExpr
+ Orville.PostgreSQL.Expr.Filter: filterExpr :: WhereClause -> FilterExpr
+ Orville.PostgreSQL.Expr.Filter: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.Filter.FilterExpr
+ Orville.PostgreSQL.Expr.Function: asDefinition :: String -> FunctionDefinition
+ Orville.PostgreSQL.Expr.Function: createFunction :: Maybe OrReplace -> QualifiedOrUnqualified FunctionName -> FunctionReturns -> FunctionLanguage -> FunctionDefinition -> CreateFunctionExpr
+ Orville.PostgreSQL.Expr.Function: data CreateFunctionExpr
+ Orville.PostgreSQL.Expr.Function: data DropFunctionExpr
+ Orville.PostgreSQL.Expr.Function: data FunctionDefinition
+ Orville.PostgreSQL.Expr.Function: data FunctionLanguage
+ Orville.PostgreSQL.Expr.Function: data FunctionReturns
+ Orville.PostgreSQL.Expr.Function: data LanguageName
+ Orville.PostgreSQL.Expr.Function: data ReturnType
+ Orville.PostgreSQL.Expr.Function: dropFunction :: Maybe IfExists -> QualifiedOrUnqualified FunctionName -> DropFunctionExpr
+ Orville.PostgreSQL.Expr.Function: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.Function.CreateFunctionExpr
+ Orville.PostgreSQL.Expr.Function: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.Function.DropFunctionExpr
+ Orville.PostgreSQL.Expr.Function: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.Function.FunctionDefinition
+ Orville.PostgreSQL.Expr.Function: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.Function.FunctionLanguage
+ Orville.PostgreSQL.Expr.Function: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.Function.FunctionReturns
+ Orville.PostgreSQL.Expr.Function: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.Function.LanguageName
+ Orville.PostgreSQL.Expr.Function: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.Function.ReturnType
+ Orville.PostgreSQL.Expr.Function: language :: LanguageName -> FunctionLanguage
+ Orville.PostgreSQL.Expr.Function: plpgsql :: LanguageName
+ Orville.PostgreSQL.Expr.Function: returnTypeTrigger :: ReturnType
+ Orville.PostgreSQL.Expr.Function: returns :: ReturnType -> FunctionReturns
+ Orville.PostgreSQL.Expr.IfNotExists: data IfNotExists
+ Orville.PostgreSQL.Expr.IfNotExists: ifNotExists :: IfNotExists
+ Orville.PostgreSQL.Expr.IfNotExists: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.IfNotExists.IfNotExists
+ Orville.PostgreSQL.Expr.Insert: valuesExprInsertSource :: ValuesExpr -> InsertSource
+ Orville.PostgreSQL.Expr.Join: data JoinConstraint
+ Orville.PostgreSQL.Expr.Join: data JoinType
+ Orville.PostgreSQL.Expr.Join: fullJoinType :: JoinType
+ Orville.PostgreSQL.Expr.Join: innerJoinType :: JoinType
+ Orville.PostgreSQL.Expr.Join: innerLateralJoinType :: JoinType
+ Orville.PostgreSQL.Expr.Join: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.Join.JoinConstraint
+ Orville.PostgreSQL.Expr.Join: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.Join.JoinType
+ Orville.PostgreSQL.Expr.Join: join :: JoinType -> TableReference -> JoinConstraint -> TableReference -> TableReference
+ Orville.PostgreSQL.Expr.Join: joinOnConstraint :: BooleanExpr -> JoinConstraint
+ Orville.PostgreSQL.Expr.Join: joinedTable :: TableReference -> JoinType -> TableReference -> JoinConstraint -> TableReference
+ Orville.PostgreSQL.Expr.Join: joining :: TableReference -> [TableReference -> TableReference] -> TableReference
+ Orville.PostgreSQL.Expr.Join: leftJoinType :: JoinType
+ Orville.PostgreSQL.Expr.Join: leftLateralJoinType :: JoinType
+ Orville.PostgreSQL.Expr.Join: rightJoinType :: JoinType
+ Orville.PostgreSQL.Expr.Name: aliasQualifyColumn :: AliasExpr -> ColumnName -> Qualified ColumnName
+ Orville.PostgreSQL.Expr.Name: arrayAggFunctionName :: FunctionName
+ Orville.PostgreSQL.Expr.Name: avgFunctionName :: FunctionName
+ Orville.PostgreSQL.Expr.Name: bitAndFunctionName :: FunctionName
+ Orville.PostgreSQL.Expr.Name: bitOrFunctionName :: FunctionName
+ Orville.PostgreSQL.Expr.Name: bitXorFunctionName :: FunctionName
+ Orville.PostgreSQL.Expr.Name: boolAndFunctionName :: FunctionName
+ Orville.PostgreSQL.Expr.Name: boolOrFunctionName :: FunctionName
+ Orville.PostgreSQL.Expr.Name: corFunctionName :: FunctionName
+ Orville.PostgreSQL.Expr.Name: countFunctionName :: FunctionName
+ Orville.PostgreSQL.Expr.Name: covarPopFunctionName :: FunctionName
+ Orville.PostgreSQL.Expr.Name: covarSampFunctionName :: FunctionName
+ Orville.PostgreSQL.Expr.Name: data AliasExpr
+ Orville.PostgreSQL.Expr.Name: data ExtensionName
+ Orville.PostgreSQL.Expr.Name: data QualifiedOrUnqualified name
+ Orville.PostgreSQL.Expr.Name: data TriggerName
+ Orville.PostgreSQL.Expr.Name: data WindowName
+ Orville.PostgreSQL.Expr.Name: everyFunctionName :: FunctionName
+ Orville.PostgreSQL.Expr.Name: extensionName :: String -> ExtensionName
+ Orville.PostgreSQL.Expr.Name: jsonAggFunctionName :: FunctionName
+ Orville.PostgreSQL.Expr.Name: jsonObjectAggFunctionName :: FunctionName
+ Orville.PostgreSQL.Expr.Name: jsonbAggFunctionName :: FunctionName
+ Orville.PostgreSQL.Expr.Name: jsonbObjectAggFunctionName :: FunctionName
+ Orville.PostgreSQL.Expr.Name: maxFunctionName :: FunctionName
+ Orville.PostgreSQL.Expr.Name: minFunctionName :: FunctionName
+ Orville.PostgreSQL.Expr.Name: qualifyFunction :: SchemaName -> FunctionName -> Qualified FunctionName
+ Orville.PostgreSQL.Expr.Name: qualifyIndex :: SchemaName -> IndexName -> Qualified IndexName
+ Orville.PostgreSQL.Expr.Name: stddevPopFunctionName :: FunctionName
+ Orville.PostgreSQL.Expr.Name: stddevSampFunctionName :: FunctionName
+ Orville.PostgreSQL.Expr.Name: stringAggFunctionName :: FunctionName
+ Orville.PostgreSQL.Expr.Name: stringToAliasExpr :: String -> AliasExpr
+ Orville.PostgreSQL.Expr.Name: sumFunctionName :: FunctionName
+ Orville.PostgreSQL.Expr.Name: triggerName :: String -> TriggerName
+ Orville.PostgreSQL.Expr.Name: unqualified :: SqlExpression name => name -> QualifiedOrUnqualified name
+ Orville.PostgreSQL.Expr.Name: untrackQualified :: Qualified name -> QualifiedOrUnqualified name
+ Orville.PostgreSQL.Expr.Name: varPopFunctionName :: FunctionName
+ Orville.PostgreSQL.Expr.Name: varSampFunctionName :: FunctionName
+ Orville.PostgreSQL.Expr.Name: windowName :: String -> WindowName
+ Orville.PostgreSQL.Expr.OnConflict: conflictTargetForConstraint :: ConstraintName -> ConflictTargetExpr
+ Orville.PostgreSQL.Expr.OnConflict: conflictTargetForIndexColumn :: QualifiedOrUnqualified ColumnName -> Maybe WhereClause -> ConflictTargetExpr
+ Orville.PostgreSQL.Expr.OnConflict: conflictTargetForIndexExpr :: IndexBodyExpr -> Maybe WhereClause -> ConflictTargetExpr
+ Orville.PostgreSQL.Expr.OnConflict: data ConflictActionExpr
+ Orville.PostgreSQL.Expr.OnConflict: data ConflictSetItemExpr
+ Orville.PostgreSQL.Expr.OnConflict: data ConflictTargetExpr
+ Orville.PostgreSQL.Expr.OnConflict: data OnConflictExpr
+ Orville.PostgreSQL.Expr.OnConflict: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.OnConflict.ConflictActionExpr
+ Orville.PostgreSQL.Expr.OnConflict: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.OnConflict.ConflictSetItemExpr
+ Orville.PostgreSQL.Expr.OnConflict: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.OnConflict.ConflictTargetExpr
+ Orville.PostgreSQL.Expr.OnConflict: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.OnConflict.OnConflictExpr
+ Orville.PostgreSQL.Expr.OnConflict: onConflict :: Maybe ConflictTargetExpr -> ConflictActionExpr -> OnConflictExpr
+ Orville.PostgreSQL.Expr.OnConflict: onConflictDoNothing :: OnConflictExpr
+ Orville.PostgreSQL.Expr.OnConflict: onConflictDoUpdate :: Maybe ConflictTargetExpr -> NonEmpty ConflictSetItemExpr -> Maybe WhereClause -> OnConflictExpr
+ Orville.PostgreSQL.Expr.OnConflict: setColumnNameExcluded :: QualifiedOrUnqualified ColumnName -> ConflictSetItemExpr
+ Orville.PostgreSQL.Expr.OrReplace: data OrReplace
+ Orville.PostgreSQL.Expr.OrReplace: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.OrReplace.OrReplace
+ Orville.PostgreSQL.Expr.OrReplace: orReplace :: OrReplace
+ Orville.PostgreSQL.Expr.OrderBy: orderByValueExpression :: ValueExpression -> OrderByDirection -> OrderByExpr
+ Orville.PostgreSQL.Expr.Query: allSubquery :: BinaryOperator -> ValueExpression -> QueryExpr -> BooleanExpr
+ Orville.PostgreSQL.Expr.Query: anySubquery :: BinaryOperator -> ValueExpression -> QueryExpr -> BooleanExpr
+ Orville.PostgreSQL.Expr.Query: existsSubquery :: QueryExpr -> BooleanExpr
+ Orville.PostgreSQL.Expr.Query: inSubquery :: ValueExpression -> QueryExpr -> BooleanExpr
+ Orville.PostgreSQL.Expr.Query: instance GHC.Base.Semigroup Orville.PostgreSQL.Expr.Query.SelectList
+ Orville.PostgreSQL.Expr.Query: notInSubquery :: ValueExpression -> QueryExpr -> BooleanExpr
+ Orville.PostgreSQL.Expr.Query: queryValueExpression :: QueryExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.Query: subQueryTableReference :: QueryExpr -> AliasExpr -> TableReference
+ Orville.PostgreSQL.Expr.RowLocking: data RowLockingClause
+ Orville.PostgreSQL.Expr.RowLocking: data RowLockingOptionExpr
+ Orville.PostgreSQL.Expr.RowLocking: data RowLockingStrengthExpr
+ Orville.PostgreSQL.Expr.RowLocking: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.RowLocking.RowLockingClause
+ Orville.PostgreSQL.Expr.RowLocking: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.RowLocking.RowLockingOptionExpr
+ Orville.PostgreSQL.Expr.RowLocking: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.RowLocking.RowLockingStrengthExpr
+ Orville.PostgreSQL.Expr.RowLocking: keyShareStrength :: RowLockingStrengthExpr
+ Orville.PostgreSQL.Expr.RowLocking: noKeyUpdateStrength :: RowLockingStrengthExpr
+ Orville.PostgreSQL.Expr.RowLocking: noWaitRowLockingOption :: RowLockingOptionExpr
+ Orville.PostgreSQL.Expr.RowLocking: rowLockingClause :: RowLockingStrengthExpr -> Maybe (NonEmpty TableName) -> Maybe RowLockingOptionExpr -> RowLockingClause
+ Orville.PostgreSQL.Expr.RowLocking: shareStrength :: RowLockingStrengthExpr
+ Orville.PostgreSQL.Expr.RowLocking: skipLockedRowLockingOption :: RowLockingOptionExpr
+ Orville.PostgreSQL.Expr.RowLocking: updateStrength :: RowLockingStrengthExpr
+ Orville.PostgreSQL.Expr.Select: data DistinctOnExpr
+ Orville.PostgreSQL.Expr.Select: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.Select.DistinctOnExpr
+ Orville.PostgreSQL.Expr.Select: selectDistinctOnExpr :: Maybe (NonEmpty DistinctOnExpr) -> SelectExpr
+ Orville.PostgreSQL.Expr.TableDefinition: alterColumnAddIdentity :: ColumnName -> ColumnIdentityGeneration -> AlterTableAction
+ Orville.PostgreSQL.Expr.TableDefinition: alterColumnDropIdentity :: ColumnName -> Maybe IfExists -> AlterTableAction
+ Orville.PostgreSQL.Expr.TableDefinition: data TruncateTableExpr
+ Orville.PostgreSQL.Expr.TableDefinition: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.TableDefinition.TruncateTableExpr
+ Orville.PostgreSQL.Expr.TableDefinition: renameTableExpr :: QualifiedOrUnqualified TableName -> QualifiedOrUnqualified TableName -> AlterTableExpr
+ Orville.PostgreSQL.Expr.TableDefinition: truncateTablesExpr :: NonEmpty (QualifiedOrUnqualified TableName) -> TruncateTableExpr
+ Orville.PostgreSQL.Expr.TableReferenceList: data TableReference
+ Orville.PostgreSQL.Expr.TableReferenceList: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.TableReferenceList.TableReference
+ Orville.PostgreSQL.Expr.TableReferenceList: singleTableReferenceList :: QualifiedOrUnqualified TableName -> TableReferenceList
+ Orville.PostgreSQL.Expr.TableReferenceList: tableNameReference :: QualifiedOrUnqualified TableName -> Maybe AliasExpr -> TableReference
+ Orville.PostgreSQL.Expr.TableReferenceList: tableReferenceList :: [TableReference] -> TableReferenceList
+ Orville.PostgreSQL.Expr.TextSearch: arabicRegConfig :: RegConfig
+ Orville.PostgreSQL.Expr.TextSearch: danishRegConfig :: RegConfig
+ Orville.PostgreSQL.Expr.TextSearch: data RegConfig
+ Orville.PostgreSQL.Expr.TextSearch: data TSQuery
+ Orville.PostgreSQL.Expr.TextSearch: data TSVector
+ Orville.PostgreSQL.Expr.TextSearch: data TSWeight
+ Orville.PostgreSQL.Expr.TextSearch: dutchRegConfig :: RegConfig
+ Orville.PostgreSQL.Expr.TextSearch: englishRegConfig :: RegConfig
+ Orville.PostgreSQL.Expr.TextSearch: finnishRegConfig :: RegConfig
+ Orville.PostgreSQL.Expr.TextSearch: frenchRegConfig :: RegConfig
+ Orville.PostgreSQL.Expr.TextSearch: germanRegConfig :: RegConfig
+ Orville.PostgreSQL.Expr.TextSearch: greekRegConfig :: RegConfig
+ Orville.PostgreSQL.Expr.TextSearch: hungarianRegConfig :: RegConfig
+ Orville.PostgreSQL.Expr.TextSearch: indonesianRegConfig :: RegConfig
+ Orville.PostgreSQL.Expr.TextSearch: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.TextSearch.RegConfig
+ Orville.PostgreSQL.Expr.TextSearch: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.TextSearch.TSQuery
+ Orville.PostgreSQL.Expr.TextSearch: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.TextSearch.TSVector
+ Orville.PostgreSQL.Expr.TextSearch: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.TextSearch.TSWeight
+ Orville.PostgreSQL.Expr.TextSearch: irishRegConfig :: RegConfig
+ Orville.PostgreSQL.Expr.TextSearch: italianRegConfig :: RegConfig
+ Orville.PostgreSQL.Expr.TextSearch: lithuanianRegConfig :: RegConfig
+ Orville.PostgreSQL.Expr.TextSearch: nepaliRegConfig :: RegConfig
+ Orville.PostgreSQL.Expr.TextSearch: norwegianRegConfig :: RegConfig
+ Orville.PostgreSQL.Expr.TextSearch: plainToTSQuery :: ValueExpression -> Maybe RegConfig -> TSQuery
+ Orville.PostgreSQL.Expr.TextSearch: portugueseRegConfig :: RegConfig
+ Orville.PostgreSQL.Expr.TextSearch: romanianRegConfig :: RegConfig
+ Orville.PostgreSQL.Expr.TextSearch: russianRegConfig :: RegConfig
+ Orville.PostgreSQL.Expr.TextSearch: setTSWeight :: TSVector -> TSWeight -> TSVector
+ Orville.PostgreSQL.Expr.TextSearch: simpleRegConfig :: RegConfig
+ Orville.PostgreSQL.Expr.TextSearch: spanishRegConfig :: RegConfig
+ Orville.PostgreSQL.Expr.TextSearch: swedishRegConfig :: RegConfig
+ Orville.PostgreSQL.Expr.TextSearch: tamilRegConfig :: RegConfig
+ Orville.PostgreSQL.Expr.TextSearch: toTSQuery :: ValueExpression -> Maybe RegConfig -> TSQuery
+ Orville.PostgreSQL.Expr.TextSearch: toTSRank :: TSVector -> TSQuery -> ValueExpression
+ Orville.PostgreSQL.Expr.TextSearch: toTSVector :: ValueExpression -> Maybe RegConfig -> TSVector
+ Orville.PostgreSQL.Expr.TextSearch: tsMatch :: TSVector -> TSQuery -> BooleanExpr
+ Orville.PostgreSQL.Expr.TextSearch: tsMatchTSQueryTSVector :: TSQuery -> TSVector -> BooleanExpr
+ Orville.PostgreSQL.Expr.TextSearch: tsQueryToValueExpression :: TSQuery -> ValueExpression
+ Orville.PostgreSQL.Expr.TextSearch: tsVectorConcat :: TSVector -> TSVector -> TSVector
+ Orville.PostgreSQL.Expr.TextSearch: tsVectorToValueExpression :: TSVector -> ValueExpression
+ Orville.PostgreSQL.Expr.TextSearch: tsWeightA :: TSWeight
+ Orville.PostgreSQL.Expr.TextSearch: tsWeightB :: TSWeight
+ Orville.PostgreSQL.Expr.TextSearch: tsWeightC :: TSWeight
+ Orville.PostgreSQL.Expr.TextSearch: tsWeightD :: TSWeight
+ Orville.PostgreSQL.Expr.TextSearch: tsWeightToValueExpression :: TSWeight -> ValueExpression
+ Orville.PostgreSQL.Expr.TextSearch: turkishRegConfig :: RegConfig
+ Orville.PostgreSQL.Expr.Trigger: createTrigger :: Maybe OrReplace -> TriggerName -> TriggerTiming -> NonEmpty TriggerEvent -> QualifiedOrUnqualified TableName -> TriggerFireScope -> QualifiedOrUnqualified FunctionName -> CreateTriggerExpr
+ Orville.PostgreSQL.Expr.Trigger: data CreateTriggerExpr
+ Orville.PostgreSQL.Expr.Trigger: data DropTriggerExpr
+ Orville.PostgreSQL.Expr.Trigger: data TriggerEvent
+ Orville.PostgreSQL.Expr.Trigger: data TriggerFireScope
+ Orville.PostgreSQL.Expr.Trigger: data TriggerTiming
+ Orville.PostgreSQL.Expr.Trigger: dropTrigger :: Maybe IfExists -> TriggerName -> QualifiedOrUnqualified TableName -> DropTriggerExpr
+ Orville.PostgreSQL.Expr.Trigger: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.Trigger.CreateTriggerExpr
+ Orville.PostgreSQL.Expr.Trigger: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.Trigger.DropTriggerExpr
+ Orville.PostgreSQL.Expr.Trigger: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.Trigger.TriggerEvent
+ Orville.PostgreSQL.Expr.Trigger: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.Trigger.TriggerFireScope
+ Orville.PostgreSQL.Expr.Trigger: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.Trigger.TriggerTiming
+ Orville.PostgreSQL.Expr.Trigger: triggerAfter :: TriggerTiming
+ Orville.PostgreSQL.Expr.Trigger: triggerBefore :: TriggerTiming
+ Orville.PostgreSQL.Expr.Trigger: triggerForEachRow :: TriggerFireScope
+ Orville.PostgreSQL.Expr.Trigger: triggerForEachStatement :: TriggerFireScope
+ Orville.PostgreSQL.Expr.Trigger: triggerInsteadOf :: TriggerTiming
+ Orville.PostgreSQL.Expr.Trigger: triggerOnDelete :: TriggerEvent
+ Orville.PostgreSQL.Expr.Trigger: triggerOnInsert :: TriggerEvent
+ Orville.PostgreSQL.Expr.Trigger: triggerOnTruncate :: TriggerEvent
+ Orville.PostgreSQL.Expr.Trigger: triggerOnUpdate :: TriggerEvent
+ Orville.PostgreSQL.Expr.Trigger: triggerOnUpdateOf :: NonEmpty ColumnName -> TriggerEvent
+ Orville.PostgreSQL.Expr.Update: data UpdateNamedOnly
+ Orville.PostgreSQL.Expr.Update: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.Update.UpdateNamedOnly
+ Orville.PostgreSQL.Expr.Update: onlyUpdateNamedTable :: UpdateNamedOnly
+ Orville.PostgreSQL.Expr.Update: setColumnExpression :: NonEmpty (QualifiedOrUnqualified ColumnName) -> ValueExpression -> SetClause
+ Orville.PostgreSQL.Expr.Vacuum: data VacuumExpr
+ Orville.PostgreSQL.Expr.Vacuum: data VacuumOption
+ Orville.PostgreSQL.Expr.Vacuum: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.Vacuum.VacuumExpr
+ Orville.PostgreSQL.Expr.Vacuum: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.Vacuum.VacuumOption
+ Orville.PostgreSQL.Expr.Vacuum: vacuumAnalyze :: Bool -> VacuumOption
+ Orville.PostgreSQL.Expr.Vacuum: vacuumExpr :: [VacuumOption] -> NonEmpty (QualifiedOrUnqualified TableName) -> VacuumExpr
+ Orville.PostgreSQL.Expr.Vacuum: vacuumFreeze :: Bool -> VacuumOption
+ Orville.PostgreSQL.Expr.Vacuum: vacuumFull :: Bool -> VacuumOption
+ Orville.PostgreSQL.Expr.Vacuum: vacuumVerbose :: Bool -> VacuumOption
+ Orville.PostgreSQL.Expr.Values: data ValuesExpr
+ Orville.PostgreSQL.Expr.Values: data ValuesExprRow
+ Orville.PostgreSQL.Expr.Values: data ValuesExprValue
+ Orville.PostgreSQL.Expr.Values: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.Values.ValuesExpr
+ Orville.PostgreSQL.Expr.Values: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.Values.ValuesExprRow
+ Orville.PostgreSQL.Expr.Values: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.Values.ValuesExprValue
+ Orville.PostgreSQL.Expr.Values: valuesExpr :: NonEmpty ValuesExprRow -> Maybe OrderByExpr -> Maybe LimitExpr -> Maybe OffsetExpr -> Maybe FetchClause -> ValuesExpr
+ Orville.PostgreSQL.Expr.Values: valuesExprDefaultValue :: ValuesExprValue
+ Orville.PostgreSQL.Expr.Values: valuesExprFromValueExpressions :: NonEmpty (NonEmpty ValueExpression) -> ValuesExpr
+ Orville.PostgreSQL.Expr.Values: valuesExprRow :: NonEmpty ValuesExprValue -> ValuesExprRow
+ Orville.PostgreSQL.Expr.Values: valuesExprValue :: ValueExpression -> ValuesExprValue
+ Orville.PostgreSQL.Expr.Values: valuesQueryExpr :: ValuesExpr -> QueryExpr
+ Orville.PostgreSQL.Expr.WhereClause: isDistinctFrom :: ValueExpression -> ValueExpression -> BooleanExpr
+ Orville.PostgreSQL.Expr.WhereClause: isNotDistinctFrom :: ValueExpression -> ValueExpression -> BooleanExpr
+ Orville.PostgreSQL.Expr.WhereClause: notExpr :: BooleanExpr -> BooleanExpr
+ Orville.PostgreSQL.Expr.Window.WindowClause: appendNamedWindowDefinitionExpr :: NamedWindowDefinitionExpr -> NamedWindowDefinitionExpr -> NamedWindowDefinitionExpr
+ Orville.PostgreSQL.Expr.Window.WindowClause: data NamedWindowDefinitionExpr
+ Orville.PostgreSQL.Expr.Window.WindowClause: data WindowClause
+ Orville.PostgreSQL.Expr.Window.WindowClause: instance GHC.Base.Semigroup Orville.PostgreSQL.Expr.Window.WindowClause.NamedWindowDefinitionExpr
+ Orville.PostgreSQL.Expr.Window.WindowClause: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.Window.WindowClause.NamedWindowDefinitionExpr
+ Orville.PostgreSQL.Expr.Window.WindowClause: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.Window.WindowClause.WindowClause
+ Orville.PostgreSQL.Expr.Window.WindowClause: namedWindowDefinition :: WindowName -> WindowDefinitionExpr -> NamedWindowDefinitionExpr
+ Orville.PostgreSQL.Expr.Window.WindowClause: windowClause :: NamedWindowDefinitionExpr -> WindowClause
+ Orville.PostgreSQL.Expr.Window.WindowDefinitionExpr: currentRowFrameEnd :: FrameEndExpr
+ Orville.PostgreSQL.Expr.Window.WindowDefinitionExpr: currentRowFrameExclusion :: FrameExclusionExpr
+ Orville.PostgreSQL.Expr.Window.WindowDefinitionExpr: currentRowFrameStart :: FrameStartExpr
+ Orville.PostgreSQL.Expr.Window.WindowDefinitionExpr: data FrameClause
+ Orville.PostgreSQL.Expr.Window.WindowDefinitionExpr: data FrameEndExpr
+ Orville.PostgreSQL.Expr.Window.WindowDefinitionExpr: data FrameExclusionExpr
+ Orville.PostgreSQL.Expr.Window.WindowDefinitionExpr: data FrameModeExpr
+ Orville.PostgreSQL.Expr.Window.WindowDefinitionExpr: data FrameStartExpr
+ Orville.PostgreSQL.Expr.Window.WindowDefinitionExpr: data PartitionByExpr
+ Orville.PostgreSQL.Expr.Window.WindowDefinitionExpr: data WindowDefinitionExpr
+ Orville.PostgreSQL.Expr.Window.WindowDefinitionExpr: frameClause :: Maybe FrameModeExpr -> FrameStartExpr -> Maybe FrameEndExpr -> Maybe FrameExclusionExpr -> FrameClause
+ Orville.PostgreSQL.Expr.Window.WindowDefinitionExpr: groupFrameExclusion :: FrameExclusionExpr
+ Orville.PostgreSQL.Expr.Window.WindowDefinitionExpr: groupsFrameMode :: FrameModeExpr
+ Orville.PostgreSQL.Expr.Window.WindowDefinitionExpr: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.Window.WindowDefinitionExpr.FrameClause
+ Orville.PostgreSQL.Expr.Window.WindowDefinitionExpr: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.Window.WindowDefinitionExpr.FrameEndExpr
+ Orville.PostgreSQL.Expr.Window.WindowDefinitionExpr: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.Window.WindowDefinitionExpr.FrameExclusionExpr
+ Orville.PostgreSQL.Expr.Window.WindowDefinitionExpr: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.Window.WindowDefinitionExpr.FrameModeExpr
+ Orville.PostgreSQL.Expr.Window.WindowDefinitionExpr: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.Window.WindowDefinitionExpr.FrameStartExpr
+ Orville.PostgreSQL.Expr.Window.WindowDefinitionExpr: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.Window.WindowDefinitionExpr.PartitionByExpr
+ Orville.PostgreSQL.Expr.Window.WindowDefinitionExpr: instance Orville.PostgreSQL.Raw.RawSql.SqlExpression Orville.PostgreSQL.Expr.Window.WindowDefinitionExpr.WindowDefinitionExpr
+ Orville.PostgreSQL.Expr.Window.WindowDefinitionExpr: noOthersFrameExclusion :: FrameExclusionExpr
+ Orville.PostgreSQL.Expr.Window.WindowDefinitionExpr: offsetFollowingFrameEnd :: ValueExpression -> FrameEndExpr
+ Orville.PostgreSQL.Expr.Window.WindowDefinitionExpr: offsetFollowingFrameStart :: ValueExpression -> FrameStartExpr
+ Orville.PostgreSQL.Expr.Window.WindowDefinitionExpr: offsetPrecedingFrameEnd :: ValueExpression -> FrameEndExpr
+ Orville.PostgreSQL.Expr.Window.WindowDefinitionExpr: offsetPrecedingFrameStart :: ValueExpression -> FrameStartExpr
+ Orville.PostgreSQL.Expr.Window.WindowDefinitionExpr: partitionBy :: NonEmpty ValueExpression -> PartitionByExpr
+ Orville.PostgreSQL.Expr.Window.WindowDefinitionExpr: rangeFrameMode :: FrameModeExpr
+ Orville.PostgreSQL.Expr.Window.WindowDefinitionExpr: rowsFrameMode :: FrameModeExpr
+ Orville.PostgreSQL.Expr.Window.WindowDefinitionExpr: tiesFrameExclusion :: FrameExclusionExpr
+ Orville.PostgreSQL.Expr.Window.WindowDefinitionExpr: unboundedFollowingFrameEnd :: FrameEndExpr
+ Orville.PostgreSQL.Expr.Window.WindowDefinitionExpr: unboundedPrecedingFrameStart :: FrameStartExpr
+ Orville.PostgreSQL.Expr.Window.WindowDefinitionExpr: windowDefinition :: Maybe WindowName -> Maybe PartitionByExpr -> Maybe OrderByClause -> Maybe FrameClause -> WindowDefinitionExpr
+ Orville.PostgreSQL.Expr.Window.WindowFunction: arrayAggWindowFunction :: ValueExpression -> Maybe FilterExpr -> WindowDefinitionExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.Window.WindowFunction: avgWindowFunction :: ValueExpression -> Maybe FilterExpr -> WindowDefinitionExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.Window.WindowFunction: bitAndWindowFunction :: ValueExpression -> Maybe FilterExpr -> WindowDefinitionExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.Window.WindowFunction: bitOrWindowFunction :: ValueExpression -> Maybe FilterExpr -> WindowDefinitionExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.Window.WindowFunction: bitXorWindowFunction :: ValueExpression -> Maybe FilterExpr -> WindowDefinitionExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.Window.WindowFunction: boolAndWindowFunction :: ValueExpression -> Maybe FilterExpr -> WindowDefinitionExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.Window.WindowFunction: boolOrWindowFunction :: ValueExpression -> Maybe FilterExpr -> WindowDefinitionExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.Window.WindowFunction: corWindowFunction :: ValueExpression -> ValueExpression -> Maybe FilterExpr -> WindowDefinitionExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.Window.WindowFunction: countWindowFunction :: ValueExpression -> Maybe FilterExpr -> WindowDefinitionExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.Window.WindowFunction: covarPopWindowFunction :: ValueExpression -> ValueExpression -> Maybe FilterExpr -> WindowDefinitionExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.Window.WindowFunction: covarSampWindowFunction :: ValueExpression -> ValueExpression -> Maybe FilterExpr -> WindowDefinitionExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.Window.WindowFunction: denseRankWindowFunction :: Maybe FilterExpr -> WindowDefinitionExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.Window.WindowFunction: everyWindowFunction :: ValueExpression -> Maybe FilterExpr -> WindowDefinitionExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.Window.WindowFunction: jsonAggWindowFunction :: ValueExpression -> Maybe FilterExpr -> WindowDefinitionExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.Window.WindowFunction: jsonObjectAggWindowFunction :: ValueExpression -> ValueExpression -> Maybe FilterExpr -> WindowDefinitionExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.Window.WindowFunction: jsonbAggWindowFunction :: ValueExpression -> Maybe FilterExpr -> WindowDefinitionExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.Window.WindowFunction: jsonbObjectAggWindowFunction :: ValueExpression -> ValueExpression -> Maybe FilterExpr -> WindowDefinitionExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.Window.WindowFunction: maxWindowFunction :: ValueExpression -> Maybe FilterExpr -> WindowDefinitionExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.Window.WindowFunction: minWindowFunction :: ValueExpression -> Maybe FilterExpr -> WindowDefinitionExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.Window.WindowFunction: percentRankWindowFunction :: Maybe FilterExpr -> WindowDefinitionExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.Window.WindowFunction: rankWindowFunction :: Maybe FilterExpr -> WindowDefinitionExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.Window.WindowFunction: rowNumberWindowFunction :: Maybe FilterExpr -> WindowDefinitionExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.Window.WindowFunction: stddevPopWindowFunction :: ValueExpression -> ValueExpression -> Maybe FilterExpr -> WindowDefinitionExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.Window.WindowFunction: stddevSampWindowFunction :: ValueExpression -> ValueExpression -> Maybe FilterExpr -> WindowDefinitionExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.Window.WindowFunction: stringAggWindowFunction :: ValueExpression -> ValueExpression -> Maybe FilterExpr -> WindowDefinitionExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.Window.WindowFunction: sumWindowFunction :: ValueExpression -> Maybe FilterExpr -> WindowDefinitionExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.Window.WindowFunction: varPopWindowFunction :: ValueExpression -> ValueExpression -> Maybe FilterExpr -> WindowDefinitionExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.Window.WindowFunction: varSampWindowFunction :: ValueExpression -> ValueExpression -> Maybe FilterExpr -> WindowDefinitionExpr -> ValueExpression
+ Orville.PostgreSQL.Expr.Window.WindowFunction: windowFunction :: FunctionName -> [ValueExpression] -> Maybe FilterExpr -> WindowDefinitionExpr -> ValueExpression
+ Orville.PostgreSQL.Extension.PgTrgm: mkNamedTrigramGinIndexDefinition :: String -> NonEmpty FieldName -> IndexDefinition
+ Orville.PostgreSQL.Extension.PgTrgm: mkNamedTrigramGistIndexDefinition :: String -> NonEmpty (FieldName, Maybe Int) -> IndexDefinition
+ Orville.PostgreSQL.Extension.PgTrgm: trigramSimilarity :: ValueExpression -> ValueExpression -> ValueExpression
+ Orville.PostgreSQL.Extension.PgTrgm: trigramSimilaritySyntheticField :: ColumnName -> ValueExpression -> String -> SyntheticField Double
+ Orville.PostgreSQL.Extension.PgTrgm: trigramStrictWordSimilarity :: ValueExpression -> ValueExpression -> ValueExpression
+ Orville.PostgreSQL.Extension.PgTrgm: trigramStrictWordSimilaritySyntheticField :: ColumnName -> ValueExpression -> String -> SyntheticField Double
+ Orville.PostgreSQL.Extension.PgTrgm: trigramWordSimilarity :: ValueExpression -> ValueExpression -> ValueExpression
+ Orville.PostgreSQL.Extension.PgTrgm: trigramWordSimilaritySyntheticField :: ColumnName -> ValueExpression -> String -> SyntheticField Double
+ Orville.PostgreSQL.Marshall.AliasName: aliasNameAndFieldNameToColumnName :: AliasName -> FieldName -> Qualified ColumnName
+ Orville.PostgreSQL.Marshall.AliasName: aliasNameToAliasExpr :: AliasName -> AliasExpr
+ Orville.PostgreSQL.Marshall.AliasName: aliasNameToByteString :: AliasName -> ByteString
+ Orville.PostgreSQL.Marshall.AliasName: aliasNameToString :: AliasName -> String
+ Orville.PostgreSQL.Marshall.AliasName: byteStringToAliasName :: ByteString -> AliasName
+ Orville.PostgreSQL.Marshall.AliasName: data AliasName
+ Orville.PostgreSQL.Marshall.AliasName: instance GHC.Classes.Eq Orville.PostgreSQL.Marshall.AliasName.AliasName
+ Orville.PostgreSQL.Marshall.AliasName: instance GHC.Classes.Ord Orville.PostgreSQL.Marshall.AliasName.AliasName
+ Orville.PostgreSQL.Marshall.AliasName: instance GHC.Show.Show Orville.PostgreSQL.Marshall.AliasName.AliasName
+ Orville.PostgreSQL.Marshall.AliasName: stringToAliasName :: String -> AliasName
+ Orville.PostgreSQL.Marshall.FieldDefinition: GeneratedAlways :: FieldIdentityGeneration
+ Orville.PostgreSQL.Marshall.FieldDefinition: GeneratedByDefault :: FieldIdentityGeneration
+ Orville.PostgreSQL.Marshall.FieldDefinition: class FieldQualifier qualifier
+ Orville.PostgreSQL.Marshall.FieldDefinition: data FieldIdentityGeneration
+ Orville.PostgreSQL.Marshall.FieldDefinition: data QualifiedFieldDefinition qualifier nullability a
+ Orville.PostgreSQL.Marshall.FieldDefinition: fieldIdentityGeneration :: FieldDefinition nullability a -> Maybe FieldIdentityGeneration
+ Orville.PostgreSQL.Marshall.FieldDefinition: fieldIsDistinctFrom :: FieldDefinition nullability a -> a -> BooleanExpr
+ Orville.PostgreSQL.Marshall.FieldDefinition: fieldIsNotDistinctFrom :: FieldDefinition nullability a -> a -> BooleanExpr
+ Orville.PostgreSQL.Marshall.FieldDefinition: instance GHC.Classes.Eq Orville.PostgreSQL.Marshall.FieldDefinition.FieldIdentityGeneration
+ Orville.PostgreSQL.Marshall.FieldDefinition: instance Orville.PostgreSQL.Marshall.FieldDefinition.FieldQualifier Orville.PostgreSQL.Marshall.AliasName.AliasName
+ Orville.PostgreSQL.Marshall.FieldDefinition: instance Orville.PostgreSQL.Marshall.FieldDefinition.FieldQualifier Orville.PostgreSQL.Schema.TableIdentifier.TableIdentifier
+ Orville.PostgreSQL.Marshall.FieldDefinition: instance Orville.PostgreSQL.Marshall.FieldDefinition.FieldQualifier qualifier => Orville.PostgreSQL.Marshall.SqlComparable.SqlComparable (Orville.PostgreSQL.Marshall.FieldDefinition.QualifiedFieldDefinition qualifier nullability a) a
+ Orville.PostgreSQL.Marshall.FieldDefinition: instance Orville.PostgreSQL.Marshall.SqlComparable.SqlComparable (Orville.PostgreSQL.Marshall.FieldDefinition.FieldDefinition nullability a) a
+ Orville.PostgreSQL.Marshall.FieldDefinition: markAsIdentity :: FieldIdentityGeneration -> FieldDefinition NotNull a -> FieldDefinition NotNull a
+ Orville.PostgreSQL.Marshall.FieldDefinition: qualifiedFieldColumnName :: FieldQualifier qualifier => QualifiedFieldDefinition qualifier nullability a -> Qualified ColumnName
+ Orville.PostgreSQL.Marshall.FieldDefinition: qualifyColumnName :: FieldQualifier qualifier => qualifier -> ColumnName -> Qualified ColumnName
+ Orville.PostgreSQL.Marshall.FieldDefinition: qualifyField :: qualifier -> FieldDefinition nullability a -> QualifiedFieldDefinition qualifier nullability a
+ Orville.PostgreSQL.Marshall.FieldDefinition: unmarkIdentity :: FieldDefinition NotNull a -> FieldDefinition NotNull a
+ Orville.PostgreSQL.Marshall.SqlComparable: class SqlComparable sqlAbstraction haskellValue | sqlAbstraction -> haskellValue
+ Orville.PostgreSQL.Marshall.SqlComparable: equals :: SqlComparable a b => a -> b -> BooleanExpr
+ Orville.PostgreSQL.Marshall.SqlComparable: greaterThan :: SqlComparable a b => a -> b -> BooleanExpr
+ Orville.PostgreSQL.Marshall.SqlComparable: greaterThanOrEqualTo :: SqlComparable a b => a -> b -> BooleanExpr
+ Orville.PostgreSQL.Marshall.SqlComparable: isDistinctFrom :: SqlComparable a b => a -> b -> BooleanExpr
+ Orville.PostgreSQL.Marshall.SqlComparable: isIn :: SqlComparable a b => a -> NonEmpty b -> BooleanExpr
+ Orville.PostgreSQL.Marshall.SqlComparable: isNotDistinctFrom :: SqlComparable a b => a -> b -> BooleanExpr
+ Orville.PostgreSQL.Marshall.SqlComparable: isNotIn :: SqlComparable a b => a -> NonEmpty b -> BooleanExpr
+ Orville.PostgreSQL.Marshall.SqlComparable: isNotNull :: SqlComparable a b => a -> BooleanExpr
+ Orville.PostgreSQL.Marshall.SqlComparable: isNull :: SqlComparable a b => a -> BooleanExpr
+ Orville.PostgreSQL.Marshall.SqlComparable: lessThan :: SqlComparable a b => a -> b -> BooleanExpr
+ Orville.PostgreSQL.Marshall.SqlComparable: lessThanOrEqualTo :: SqlComparable a b => a -> b -> BooleanExpr
+ Orville.PostgreSQL.Marshall.SqlComparable: like :: SqlComparable a b => a -> Text -> BooleanExpr
+ Orville.PostgreSQL.Marshall.SqlComparable: likeInsensitive :: SqlComparable a b => a -> Text -> BooleanExpr
+ Orville.PostgreSQL.Marshall.SqlComparable: notEquals :: SqlComparable a b => a -> b -> BooleanExpr
+ Orville.PostgreSQL.Marshall.SqlComparable: orderBySqlComparable :: SqlComparable comparable value => comparable -> OrderByDirection -> OrderByExpr
+ Orville.PostgreSQL.Marshall.SqlComparable: referenceValueExpression :: SqlComparable sqlAbstraction haskellValue => sqlAbstraction -> ValueExpression
+ Orville.PostgreSQL.Marshall.SqlComparable: toComparableSqlValue :: SqlComparable sqlAbstraction haskellValue => sqlAbstraction -> haskellValue -> SqlValue
+ Orville.PostgreSQL.Marshall.SqlComparable: toSqlValueTuple :: (SqlComparable a c, SqlComparable b d) => a -> b -> (c, d) -> NonEmpty ValueExpression
+ Orville.PostgreSQL.Marshall.SqlComparable: tupleIn :: (SqlComparable a c, SqlComparable b d) => a -> b -> NonEmpty (c, d) -> BooleanExpr
+ Orville.PostgreSQL.Marshall.SqlComparable: tupleNotIn :: (SqlComparable a c, SqlComparable b d) => a -> b -> NonEmpty (c, d) -> BooleanExpr
+ Orville.PostgreSQL.Marshall.SqlMarshaller: instance Orville.PostgreSQL.Marshall.SqlComparable.SqlComparable (Orville.PostgreSQL.Marshall.SqlMarshaller.SqlMarshaller writeEntity readEntity) writeEntity
+ Orville.PostgreSQL.Marshall.SqlMarshaller: marshallQualifyFields :: FieldQualifier qualifier => qualifier -> SqlMarshaller a b -> SqlMarshaller a b
+ Orville.PostgreSQL.Marshall.SqlMarshaller: marshallerEquals :: SqlMarshaller writeEntity x -> writeEntity -> BooleanExpr
+ Orville.PostgreSQL.Marshall.SqlMarshaller: marshallerGreaterThan :: SqlMarshaller writeEntity x -> writeEntity -> BooleanExpr
+ Orville.PostgreSQL.Marshall.SqlMarshaller: marshallerGreaterThanOrEqualTo :: SqlMarshaller writeEntity x -> writeEntity -> BooleanExpr
+ Orville.PostgreSQL.Marshall.SqlMarshaller: marshallerIn :: SqlMarshaller writeEntity x -> NonEmpty writeEntity -> BooleanExpr
+ Orville.PostgreSQL.Marshall.SqlMarshaller: marshallerIsDistinctFrom :: SqlMarshaller writeEntity x -> writeEntity -> BooleanExpr
+ Orville.PostgreSQL.Marshall.SqlMarshaller: marshallerIsNotDistinctFrom :: SqlMarshaller writeEntity x -> writeEntity -> BooleanExpr
+ Orville.PostgreSQL.Marshall.SqlMarshaller: marshallerLessThan :: SqlMarshaller writeEntity x -> writeEntity -> BooleanExpr
+ Orville.PostgreSQL.Marshall.SqlMarshaller: marshallerLessThanOrEqualTo :: SqlMarshaller writeEntity x -> writeEntity -> BooleanExpr
+ Orville.PostgreSQL.Marshall.SqlMarshaller: marshallerNotEquals :: SqlMarshaller writeEntity x -> writeEntity -> BooleanExpr
+ Orville.PostgreSQL.Marshall.SqlMarshaller: marshallerNotIn :: SqlMarshaller writeEntity x -> NonEmpty writeEntity -> BooleanExpr
+ Orville.PostgreSQL.Marshall.SyntheticField: instance Orville.PostgreSQL.Marshall.SqlComparable.SqlComparable (Orville.PostgreSQL.Marshall.SyntheticField.SyntheticField a) a
+ Orville.PostgreSQL.Marshall.SyntheticField: orderBySyntheticField :: SyntheticField a -> OrderByDirection -> OrderByExpr
+ Orville.PostgreSQL.Marshall.SyntheticField: syntheticFieldName :: SyntheticField a -> FieldName
+ Orville.PostgreSQL.PgCatalog: PgDescription :: Oid -> Oid -> ObjectSubId -> Text -> PgDescription
+ Orville.PostgreSQL.PgCatalog: PgExtension :: ExtensionName -> PgExtension
+ Orville.PostgreSQL.PgCatalog: PgProc :: Oid -> Oid -> ProcName -> Text -> PgProc
+ Orville.PostgreSQL.PgCatalog: PgTrigger :: Oid -> Oid -> TriggerName -> Bool -> PgTrigger
+ Orville.PostgreSQL.PgCatalog: [databaseExtensions] :: DatabaseDescription -> Map ExtensionName PgExtension
+ Orville.PostgreSQL.PgCatalog: [databaseProcedures] :: DatabaseDescription -> Map (NamespaceName, ProcName) PgProc
+ Orville.PostgreSQL.PgCatalog: [pgAttributeIdentity] :: PgAttribute -> Maybe FieldIdentityGeneration
+ Orville.PostgreSQL.PgCatalog: [pgDescriptionClassOid] :: PgDescription -> Oid
+ Orville.PostgreSQL.PgCatalog: [pgDescriptionDescription] :: PgDescription -> Text
+ Orville.PostgreSQL.PgCatalog: [pgDescriptionObjOid] :: PgDescription -> Oid
+ Orville.PostgreSQL.PgCatalog: [pgDescriptionObjSubId] :: PgDescription -> ObjectSubId
+ Orville.PostgreSQL.PgCatalog: [pgExtensionName] :: PgExtension -> ExtensionName
+ Orville.PostgreSQL.PgCatalog: [pgProcName] :: PgProc -> ProcName
+ Orville.PostgreSQL.PgCatalog: [pgProcNamespaceOid] :: PgProc -> Oid
+ Orville.PostgreSQL.PgCatalog: [pgProcOid] :: PgProc -> Oid
+ Orville.PostgreSQL.PgCatalog: [pgProcSource] :: PgProc -> Text
+ Orville.PostgreSQL.PgCatalog: [pgTriggerIsInternal] :: PgTrigger -> Bool
+ Orville.PostgreSQL.PgCatalog: [pgTriggerName] :: PgTrigger -> TriggerName
+ Orville.PostgreSQL.PgCatalog: [pgTriggerOid] :: PgTrigger -> Oid
+ Orville.PostgreSQL.PgCatalog: [pgTriggerRelationOid] :: PgTrigger -> Oid
+ Orville.PostgreSQL.PgCatalog: [relationAttributeComments] :: RelationDescription -> Map AttributeName Text
+ Orville.PostgreSQL.PgCatalog: [relationComment] :: RelationDescription -> Maybe Text
+ Orville.PostgreSQL.PgCatalog: [relationTriggers] :: RelationDescription -> [PgTrigger]
+ Orville.PostgreSQL.PgCatalog: data ExtensionName
+ Orville.PostgreSQL.PgCatalog: data ObjectSubId
+ Orville.PostgreSQL.PgCatalog: data PgDescription
+ Orville.PostgreSQL.PgCatalog: data PgProc
+ Orville.PostgreSQL.PgCatalog: data PgTrigger
+ Orville.PostgreSQL.PgCatalog: data ProcName
+ Orville.PostgreSQL.PgCatalog: data TriggerName
+ Orville.PostgreSQL.PgCatalog: describeDatabase :: MonadOrville m => [(NamespaceName, RelationName)] -> [(NamespaceName, ProcName)] -> [ExtensionName] -> m DatabaseDescription
+ Orville.PostgreSQL.PgCatalog: descriptionField :: FieldDefinition NotNull Text
+ Orville.PostgreSQL.PgCatalog: extensionNameField :: FieldDefinition NotNull ExtensionName
+ Orville.PostgreSQL.PgCatalog: lookupAttributeComment :: AttributeName -> RelationDescription -> Maybe Text
+ Orville.PostgreSQL.PgCatalog: lookupExtension :: ExtensionName -> DatabaseDescription -> Maybe PgExtension
+ Orville.PostgreSQL.PgCatalog: lookupProcedure :: (NamespaceName, ProcName) -> DatabaseDescription -> Maybe PgProc
+ Orville.PostgreSQL.PgCatalog: newtype PgExtension
+ Orville.PostgreSQL.PgCatalog: objOidField :: FieldDefinition NotNull Oid
+ Orville.PostgreSQL.PgCatalog: objSubIdField :: FieldDefinition NotNull ObjectSubId
+ Orville.PostgreSQL.PgCatalog: objectSubIdFromAttributeNumber :: AttributeNumber -> ObjectSubId
+ Orville.PostgreSQL.PgCatalog: objectSubIdZero :: ObjectSubId
+ Orville.PostgreSQL.PgCatalog: pgDescriptionTable :: TableDefinition (HasKey Oid) PgDescription PgDescription
+ Orville.PostgreSQL.PgCatalog: pgExtensionTable :: TableDefinition NoKey PgExtension PgExtension
+ Orville.PostgreSQL.PgCatalog: pgProcTable :: TableDefinition NoKey PgProc PgProc
+ Orville.PostgreSQL.PgCatalog: pgTriggerTable :: TableDefinition NoKey PgTrigger PgTrigger
+ Orville.PostgreSQL.PgCatalog: procNameField :: FieldDefinition NotNull ProcName
+ Orville.PostgreSQL.PgCatalog: procNamespaceOidField :: FieldDefinition NotNull Oid
+ Orville.PostgreSQL.PgCatalog: triggerNameToString :: TriggerName -> String
+ Orville.PostgreSQL.PgCatalog: triggerRelationOidField :: FieldDefinition NotNull Oid
+ Orville.PostgreSQL.Plan: findAllByMarshaller :: Ord param => TableDefinition key writeEntity readEntity -> SqlMarshaller param param -> Plan scope param [readEntity]
+ Orville.PostgreSQL.Plan: findAllWhereByMarshaller :: Ord param => TableDefinition key writeEntity readEntity -> SqlMarshaller param param -> BooleanExpr -> Plan scope param [readEntity]
+ Orville.PostgreSQL.Plan: findMaybeOneByMarshaller :: Ord param => TableDefinition key writeEntity readEntity -> SqlMarshaller param param -> Plan scope param (Maybe readEntity)
+ Orville.PostgreSQL.Plan: findMaybeOneWhereByMarshaller :: Ord param => TableDefinition key writeEntity readEntity -> SqlMarshaller param param -> BooleanExpr -> Plan scope param (Maybe readEntity)
+ Orville.PostgreSQL.Plan: findOneByMarshaller :: (Show param, Ord param) => TableDefinition key writeEntity readEntity -> SqlMarshaller param param -> Plan scope param readEntity
+ Orville.PostgreSQL.Plan: findOneShowViaByMarshaller :: Ord param => (param -> String) -> TableDefinition key writeEntity readEntity -> SqlMarshaller param param -> Plan scope param readEntity
+ Orville.PostgreSQL.Plan: findOneWhereByMarshaller :: (Show param, Ord param) => TableDefinition key writeEntity readEntity -> SqlMarshaller param param -> BooleanExpr -> Plan scope param readEntity
+ Orville.PostgreSQL.Plan: findOneWhereShowViaByMarshaller :: Ord param => (param -> String) -> TableDefinition key writeEntity readEntity -> SqlMarshaller param param -> BooleanExpr -> Plan scope param readEntity
+ Orville.PostgreSQL.Plan: planTraversable :: forall t tScope param result. Traversable t => (forall scope. Plan scope param result) -> Plan tScope (t param) (t result)
+ Orville.PostgreSQL.Plan.Operation: byMarshaller :: Ord param => SqlMarshaller param param -> WherePlanner param
+ Orville.PostgreSQL.Raw.Connection: transactionStatus :: Connection -> IO (Maybe TransactionStatus)
+ Orville.PostgreSQL.Raw.Connection: transactionStatusOrThrow :: Connection -> IO TransactionStatus
+ Orville.PostgreSQL.Raw.RawSql: appendWithCommaSpace :: RawSql -> RawSql -> RawSql
+ Orville.PostgreSQL.Raw.RawSql: nullLiteral :: RawSql
+ Orville.PostgreSQL.Raw.SqlValue: foldSqlValue :: (PgTextFormatValue -> r) -> (NonEmpty r -> r) -> r -> SqlValue -> r
+ Orville.PostgreSQL.Raw.SqlValue: fromRow :: NonEmpty SqlValue -> SqlValue
+ Orville.PostgreSQL.Schema.ExtensionIdentifier: data ExtensionIdentifier
+ Orville.PostgreSQL.Schema.ExtensionIdentifier: extensionIdName :: ExtensionIdentifier -> ExtensionName
+ Orville.PostgreSQL.Schema.ExtensionIdentifier: extensionIdToString :: ExtensionIdentifier -> String
+ Orville.PostgreSQL.Schema.ExtensionIdentifier: instance GHC.Classes.Eq Orville.PostgreSQL.Schema.ExtensionIdentifier.ExtensionIdentifier
+ Orville.PostgreSQL.Schema.ExtensionIdentifier: instance GHC.Classes.Ord Orville.PostgreSQL.Schema.ExtensionIdentifier.ExtensionIdentifier
+ Orville.PostgreSQL.Schema.ExtensionIdentifier: instance GHC.Show.Show Orville.PostgreSQL.Schema.ExtensionIdentifier.ExtensionIdentifier
+ Orville.PostgreSQL.Schema.ExtensionIdentifier: nameToExtensionId :: String -> ExtensionIdentifier
+ Orville.PostgreSQL.Schema.FunctionDefinition: data FunctionDefinition
+ Orville.PostgreSQL.Schema.FunctionDefinition: functionIdentifier :: FunctionDefinition -> FunctionIdentifier
+ Orville.PostgreSQL.Schema.FunctionDefinition: functionName :: FunctionDefinition -> QualifiedOrUnqualified FunctionName
+ Orville.PostgreSQL.Schema.FunctionDefinition: functionSource :: FunctionDefinition -> String
+ Orville.PostgreSQL.Schema.FunctionDefinition: mkCreateFunctionExpr :: FunctionDefinition -> Maybe OrReplace -> CreateFunctionExpr
+ Orville.PostgreSQL.Schema.FunctionDefinition: mkTriggerFunction :: String -> LanguageName -> String -> FunctionDefinition
+ Orville.PostgreSQL.Schema.FunctionDefinition: setFunctionSchema :: String -> FunctionDefinition -> FunctionDefinition
+ Orville.PostgreSQL.Schema.FunctionIdentifier: data FunctionIdentifier
+ Orville.PostgreSQL.Schema.FunctionIdentifier: functionIdQualifiedName :: FunctionIdentifier -> QualifiedOrUnqualified FunctionName
+ Orville.PostgreSQL.Schema.FunctionIdentifier: functionIdSchemaName :: FunctionIdentifier -> Maybe SchemaName
+ Orville.PostgreSQL.Schema.FunctionIdentifier: functionIdSchemaNameString :: FunctionIdentifier -> Maybe String
+ Orville.PostgreSQL.Schema.FunctionIdentifier: functionIdToString :: FunctionIdentifier -> String
+ Orville.PostgreSQL.Schema.FunctionIdentifier: functionIdUnqualifiedName :: FunctionIdentifier -> FunctionName
+ Orville.PostgreSQL.Schema.FunctionIdentifier: functionIdUnqualifiedNameString :: FunctionIdentifier -> String
+ Orville.PostgreSQL.Schema.FunctionIdentifier: instance GHC.Classes.Eq Orville.PostgreSQL.Schema.FunctionIdentifier.FunctionIdentifier
+ Orville.PostgreSQL.Schema.FunctionIdentifier: instance GHC.Classes.Ord Orville.PostgreSQL.Schema.FunctionIdentifier.FunctionIdentifier
+ Orville.PostgreSQL.Schema.FunctionIdentifier: instance GHC.Show.Show Orville.PostgreSQL.Schema.FunctionIdentifier.FunctionIdentifier
+ Orville.PostgreSQL.Schema.FunctionIdentifier: setFunctionIdSchema :: String -> FunctionIdentifier -> FunctionIdentifier
+ Orville.PostgreSQL.Schema.FunctionIdentifier: unqualifiedNameToFunctionId :: String -> FunctionIdentifier
+ Orville.PostgreSQL.Schema.IndexDefinition: AttributeBasedIndexKey :: AttributeBasedIndexMigrationKey -> IndexMigrationKey
+ Orville.PostgreSQL.Schema.IndexDefinition: AttributeBasedIndexMigrationKey :: IndexUniqueness -> [FieldName] -> AttributeBasedIndexMigrationKey
+ Orville.PostgreSQL.Schema.IndexDefinition: NamedIndexKey :: NamedIndexMigrationKey -> IndexMigrationKey
+ Orville.PostgreSQL.Schema.IndexDefinition: [indexKeyColumns] :: AttributeBasedIndexMigrationKey -> [FieldName]
+ Orville.PostgreSQL.Schema.IndexDefinition: [indexKeyUniqueness] :: AttributeBasedIndexMigrationKey -> IndexUniqueness
+ Orville.PostgreSQL.Schema.IndexDefinition: data AttributeBasedIndexMigrationKey
+ Orville.PostgreSQL.Schema.IndexDefinition: data IndexMigrationKey
+ Orville.PostgreSQL.Schema.IndexDefinition: indexMigrationKey :: IndexDefinition -> IndexMigrationKey
+ Orville.PostgreSQL.Schema.IndexDefinition: type NamedIndexMigrationKey = String
+ Orville.PostgreSQL.Schema.TableDefinition: addTableTriggers :: [TriggerDefinition] -> TableDefinition key writeEntity readEntity -> TableDefinition key writeEntity readEntity
+ Orville.PostgreSQL.Schema.TableDefinition: setTableComment :: Text -> TableDefinition key writeEntity readEntity -> TableDefinition key writeEntity readEntity
+ Orville.PostgreSQL.Schema.TableDefinition: tableComment :: TableDefinition key writeEntity readEntity -> Maybe Text
+ Orville.PostgreSQL.Schema.TableDefinition: tableTriggers :: TableDefinition key writeEntity readEntity -> Map TriggerMigrationKey TriggerDefinition
+ Orville.PostgreSQL.Schema.TriggerDefinition: NamedTriggerKey :: String -> TriggerMigrationKey
+ Orville.PostgreSQL.Schema.TriggerDefinition: afterDelete :: String -> QualifiedOrUnqualified FunctionName -> TriggerDefinition
+ Orville.PostgreSQL.Schema.TriggerDefinition: afterInsert :: String -> QualifiedOrUnqualified FunctionName -> TriggerDefinition
+ Orville.PostgreSQL.Schema.TriggerDefinition: afterUpdate :: String -> QualifiedOrUnqualified FunctionName -> TriggerDefinition
+ Orville.PostgreSQL.Schema.TriggerDefinition: beforeDelete :: String -> QualifiedOrUnqualified FunctionName -> TriggerDefinition
+ Orville.PostgreSQL.Schema.TriggerDefinition: beforeInsert :: String -> QualifiedOrUnqualified FunctionName -> TriggerDefinition
+ Orville.PostgreSQL.Schema.TriggerDefinition: beforeUpdate :: String -> QualifiedOrUnqualified FunctionName -> TriggerDefinition
+ Orville.PostgreSQL.Schema.TriggerDefinition: data TriggerDefinition
+ Orville.PostgreSQL.Schema.TriggerDefinition: instance GHC.Classes.Eq Orville.PostgreSQL.Schema.TriggerDefinition.TriggerMigrationKey
+ Orville.PostgreSQL.Schema.TriggerDefinition: instance GHC.Classes.Ord Orville.PostgreSQL.Schema.TriggerDefinition.TriggerMigrationKey
+ Orville.PostgreSQL.Schema.TriggerDefinition: mkCreateTriggerExpr :: TriggerDefinition -> Maybe OrReplace -> QualifiedOrUnqualified TableName -> CreateTriggerExpr
+ Orville.PostgreSQL.Schema.TriggerDefinition: mkTriggerDefinition :: String -> TriggerTiming -> NonEmpty TriggerEvent -> TriggerFireScope -> QualifiedOrUnqualified FunctionName -> TriggerDefinition
+ Orville.PostgreSQL.Schema.TriggerDefinition: newtype TriggerMigrationKey
+ Orville.PostgreSQL.Schema.TriggerDefinition: triggerMigrationKey :: TriggerDefinition -> TriggerMigrationKey
- Orville.PostgreSQL: coerceField :: (Coercible a b, Coercible b a) => FieldDefinition nullability a -> FieldDefinition nullability b
+ Orville.PostgreSQL: coerceField :: Coercible a b => FieldDefinition nullability a -> FieldDefinition nullability b
- Orville.PostgreSQL: collectFromField :: ReadOnlyColumnOption -> (forall nullability a. FieldDefinition nullability a -> result) -> MarshallerField entity -> [result] -> [result]
+ Orville.PostgreSQL: collectFromField :: ReadOnlyColumnOption -> (forall qualifier nullability a. FieldQualifier qualifier => Maybe qualifier -> FieldDefinition nullability a -> result) -> MarshallerField entity -> [result] -> [result]
- Orville.PostgreSQL: indexCreateExpr :: IndexDefinition -> Qualified TableName -> CreateIndexExpr
+ Orville.PostgreSQL: indexCreateExpr :: IndexDefinition -> QualifiedOrUnqualified TableName -> CreateIndexExpr
- Orville.PostgreSQL: orderByColumnName :: ColumnName -> OrderByDirection -> OrderByExpr
+ Orville.PostgreSQL: orderByColumnName :: QualifiedOrUnqualified ColumnName -> OrderByDirection -> OrderByExpr
- Orville.PostgreSQL: sequenceIdQualifiedName :: SequenceIdentifier -> Qualified SequenceName
+ Orville.PostgreSQL: sequenceIdQualifiedName :: SequenceIdentifier -> QualifiedOrUnqualified SequenceName
- Orville.PostgreSQL: sequenceName :: SequenceDefinition -> Qualified SequenceName
+ Orville.PostgreSQL: sequenceName :: SequenceDefinition -> QualifiedOrUnqualified SequenceName
- Orville.PostgreSQL: syntheticField :: ValueExpression -> String -> (SqlValue -> Either String a) -> SyntheticField a
+ Orville.PostgreSQL: syntheticField :: ValueExpression -> String -> SqlType a -> SyntheticField a
- Orville.PostgreSQL: tableIdQualifiedName :: TableIdentifier -> Qualified TableName
+ Orville.PostgreSQL: tableIdQualifiedName :: TableIdentifier -> QualifiedOrUnqualified TableName
- Orville.PostgreSQL: tableName :: TableDefinition key writeEntity readEntity -> Qualified TableName
+ Orville.PostgreSQL: tableName :: TableDefinition key writeEntity readEntity -> QualifiedOrUnqualified TableName
- Orville.PostgreSQL.AutoMigration: withMigrationLock :: MonadOrville m => MigrationLockId -> m a -> m a
+ Orville.PostgreSQL.AutoMigration: withMigrationLock :: MonadOrville m => MigrationLockOptions -> m a -> m a
- Orville.PostgreSQL.Execution.Select: selectMarshalledColumns :: AnnotatedSqlMarshaller writeEntity readEntity -> Qualified TableName -> SelectOptions -> Select readEntity
+ Orville.PostgreSQL.Execution.Select: selectMarshalledColumns :: AnnotatedSqlMarshaller writeEntity readEntity -> QualifiedOrUnqualified TableName -> SelectOptions -> Select readEntity
- Orville.PostgreSQL.Expr.ColumnDefinition: columnDefinition :: ColumnName -> DataType -> Maybe ColumnConstraint -> Maybe ColumnDefault -> ColumnDefinition
+ Orville.PostgreSQL.Expr.ColumnDefinition: columnDefinition :: ColumnName -> DataType -> [ColumnConstraint] -> Maybe ColumnDefault -> ColumnDefinition
- Orville.PostgreSQL.Expr.Count: countColumn :: ColumnName -> ValueExpression
+ Orville.PostgreSQL.Expr.Count: countColumn :: QualifiedOrUnqualified ColumnName -> ValueExpression
- Orville.PostgreSQL.Expr.Delete: deleteExpr :: Qualified TableName -> Maybe WhereClause -> Maybe ReturningExpr -> DeleteExpr
+ Orville.PostgreSQL.Expr.Delete: deleteExpr :: QualifiedOrUnqualified TableName -> Maybe WhereClause -> Maybe ReturningExpr -> DeleteExpr
- Orville.PostgreSQL.Expr.GroupBy: groupByColumnsExpr :: NonEmpty ColumnName -> GroupByExpr
+ Orville.PostgreSQL.Expr.GroupBy: groupByColumnsExpr :: NonEmpty (QualifiedOrUnqualified ColumnName) -> GroupByExpr
- Orville.PostgreSQL.Expr.Index: createIndexExpr :: IndexUniqueness -> Maybe ConcurrentlyExpr -> Qualified TableName -> NonEmpty ColumnName -> CreateIndexExpr
+ Orville.PostgreSQL.Expr.Index: createIndexExpr :: IndexUniqueness -> Maybe ConcurrentlyExpr -> QualifiedOrUnqualified TableName -> NonEmpty ColumnName -> CreateIndexExpr
- Orville.PostgreSQL.Expr.Index: createNamedIndexExpr :: IndexUniqueness -> Maybe ConcurrentlyExpr -> Qualified TableName -> IndexName -> IndexBodyExpr -> CreateIndexExpr
+ Orville.PostgreSQL.Expr.Index: createNamedIndexExpr :: IndexUniqueness -> Maybe ConcurrentlyExpr -> QualifiedOrUnqualified TableName -> IndexName -> IndexBodyExpr -> CreateIndexExpr
- Orville.PostgreSQL.Expr.Index: dropIndexExpr :: IndexName -> DropIndexExpr
+ Orville.PostgreSQL.Expr.Index: dropIndexExpr :: QualifiedOrUnqualified IndexName -> DropIndexExpr
- Orville.PostgreSQL.Expr.Insert: insertColumnList :: [ColumnName] -> InsertColumnList
+ Orville.PostgreSQL.Expr.Insert: insertColumnList :: [QualifiedOrUnqualified ColumnName] -> InsertColumnList
- Orville.PostgreSQL.Expr.Insert: insertExpr :: Qualified TableName -> Maybe InsertColumnList -> InsertSource -> Maybe ReturningExpr -> InsertExpr
+ Orville.PostgreSQL.Expr.Insert: insertExpr :: QualifiedOrUnqualified TableName -> Maybe InsertColumnList -> InsertSource -> Maybe OnConflictExpr -> Maybe ReturningExpr -> InsertExpr
- Orville.PostgreSQL.Expr.Name: qualifySequence :: Maybe SchemaName -> SequenceName -> Qualified SequenceName
+ Orville.PostgreSQL.Expr.Name: qualifySequence :: SchemaName -> SequenceName -> Qualified SequenceName
- Orville.PostgreSQL.Expr.Name: qualifyTable :: Maybe SchemaName -> TableName -> Qualified TableName
+ Orville.PostgreSQL.Expr.Name: qualifyTable :: SchemaName -> TableName -> Qualified TableName
- Orville.PostgreSQL.Expr.OrderBy: orderByColumnName :: ColumnName -> OrderByDirection -> OrderByExpr
+ Orville.PostgreSQL.Expr.OrderBy: orderByColumnName :: QualifiedOrUnqualified ColumnName -> OrderByDirection -> OrderByExpr
- Orville.PostgreSQL.Expr.OrderBy: orderByColumnsExpr :: NonEmpty (ColumnName, OrderByDirection) -> OrderByExpr
+ Orville.PostgreSQL.Expr.OrderBy: orderByColumnsExpr :: NonEmpty (QualifiedOrUnqualified ColumnName, OrderByDirection) -> OrderByExpr
- Orville.PostgreSQL.Expr.Query: selectColumns :: [ColumnName] -> SelectList
+ Orville.PostgreSQL.Expr.Query: selectColumns :: [QualifiedOrUnqualified ColumnName] -> SelectList
- Orville.PostgreSQL.Expr.Query: tableExpr :: TableReferenceList -> Maybe WhereClause -> Maybe GroupByClause -> Maybe OrderByClause -> Maybe LimitExpr -> Maybe OffsetExpr -> TableExpr
+ Orville.PostgreSQL.Expr.Query: tableExpr :: TableReferenceList -> Maybe WhereClause -> Maybe GroupByClause -> Maybe OrderByClause -> Maybe LimitExpr -> Maybe OffsetExpr -> Maybe RowLockingClause -> Maybe WindowClause -> Maybe FetchClause -> TableExpr
- Orville.PostgreSQL.Expr.SequenceDefinition: alterSequenceExpr :: Qualified SequenceName -> Maybe IncrementByExpr -> Maybe MinValueExpr -> Maybe MaxValueExpr -> Maybe StartWithExpr -> Maybe CacheExpr -> Maybe CycleExpr -> AlterSequenceExpr
+ Orville.PostgreSQL.Expr.SequenceDefinition: alterSequenceExpr :: QualifiedOrUnqualified SequenceName -> Maybe IncrementByExpr -> Maybe MinValueExpr -> Maybe MaxValueExpr -> Maybe StartWithExpr -> Maybe CacheExpr -> Maybe CycleExpr -> AlterSequenceExpr
- Orville.PostgreSQL.Expr.SequenceDefinition: createSequenceExpr :: Qualified SequenceName -> Maybe IncrementByExpr -> Maybe MinValueExpr -> Maybe MaxValueExpr -> Maybe StartWithExpr -> Maybe CacheExpr -> Maybe CycleExpr -> CreateSequenceExpr
+ Orville.PostgreSQL.Expr.SequenceDefinition: createSequenceExpr :: QualifiedOrUnqualified SequenceName -> Maybe IncrementByExpr -> Maybe MinValueExpr -> Maybe MaxValueExpr -> Maybe StartWithExpr -> Maybe CacheExpr -> Maybe CycleExpr -> CreateSequenceExpr
- Orville.PostgreSQL.Expr.SequenceDefinition: currVal :: Qualified SequenceName -> ValueExpression
+ Orville.PostgreSQL.Expr.SequenceDefinition: currVal :: QualifiedOrUnqualified SequenceName -> ValueExpression
- Orville.PostgreSQL.Expr.SequenceDefinition: dropSequenceExpr :: Maybe IfExists -> Qualified SequenceName -> DropSequenceExpr
+ Orville.PostgreSQL.Expr.SequenceDefinition: dropSequenceExpr :: Maybe IfExists -> QualifiedOrUnqualified SequenceName -> DropSequenceExpr
- Orville.PostgreSQL.Expr.SequenceDefinition: nextVal :: Qualified SequenceName -> ValueExpression
+ Orville.PostgreSQL.Expr.SequenceDefinition: nextVal :: QualifiedOrUnqualified SequenceName -> ValueExpression
- Orville.PostgreSQL.Expr.SequenceDefinition: setVal :: Qualified SequenceName -> Int64 -> ValueExpression
+ Orville.PostgreSQL.Expr.SequenceDefinition: setVal :: QualifiedOrUnqualified SequenceName -> Int64 -> ValueExpression
- Orville.PostgreSQL.Expr.TableConstraint: foreignKeyConstraint :: NonEmpty ColumnName -> Qualified TableName -> NonEmpty ColumnName -> Maybe ForeignKeyUpdateActionExpr -> Maybe ForeignKeyDeleteActionExpr -> TableConstraint
+ Orville.PostgreSQL.Expr.TableConstraint: foreignKeyConstraint :: NonEmpty ColumnName -> QualifiedOrUnqualified TableName -> NonEmpty ColumnName -> Maybe ForeignKeyUpdateActionExpr -> Maybe ForeignKeyDeleteActionExpr -> TableConstraint
- Orville.PostgreSQL.Expr.TableDefinition: alterTableExpr :: Qualified TableName -> NonEmpty AlterTableAction -> AlterTableExpr
+ Orville.PostgreSQL.Expr.TableDefinition: alterTableExpr :: QualifiedOrUnqualified TableName -> NonEmpty AlterTableAction -> AlterTableExpr
- Orville.PostgreSQL.Expr.TableDefinition: createTableExpr :: Qualified TableName -> [ColumnDefinition] -> Maybe PrimaryKeyExpr -> [TableConstraint] -> CreateTableExpr
+ Orville.PostgreSQL.Expr.TableDefinition: createTableExpr :: QualifiedOrUnqualified TableName -> [ColumnDefinition] -> Maybe PrimaryKeyExpr -> [TableConstraint] -> CreateTableExpr
- Orville.PostgreSQL.Expr.TableDefinition: dropTableExpr :: Maybe IfExists -> Qualified TableName -> DropTableExpr
+ Orville.PostgreSQL.Expr.TableDefinition: dropTableExpr :: Maybe IfExists -> QualifiedOrUnqualified TableName -> DropTableExpr
- Orville.PostgreSQL.Expr.Update: setColumn :: ColumnName -> SqlValue -> SetClause
+ Orville.PostgreSQL.Expr.Update: setColumn :: QualifiedOrUnqualified ColumnName -> SqlValue -> SetClause
- Orville.PostgreSQL.Expr.Update: updateExpr :: Qualified TableName -> SetClauseList -> Maybe WhereClause -> Maybe ReturningExpr -> UpdateExpr
+ Orville.PostgreSQL.Expr.Update: updateExpr :: QualifiedOrUnqualified TableName -> Maybe UpdateNamedOnly -> SetClauseList -> Maybe TableReferenceList -> Maybe WhereClause -> Maybe ReturningExpr -> UpdateExpr
- Orville.PostgreSQL.Expr.ValueExpression: columnReference :: ColumnName -> ValueExpression
+ Orville.PostgreSQL.Expr.ValueExpression: columnReference :: QualifiedOrUnqualified ColumnName -> ValueExpression
- Orville.PostgreSQL.Marshall.FieldDefinition: coerceField :: (Coercible a b, Coercible b a) => FieldDefinition nullability a -> FieldDefinition nullability b
+ Orville.PostgreSQL.Marshall.FieldDefinition: coerceField :: Coercible a b => FieldDefinition nullability a -> FieldDefinition nullability b
- Orville.PostgreSQL.Marshall.SqlMarshaller: [Natural] :: FieldDefinition nullability a -> Maybe (writeEntity -> a) -> MarshallerField writeEntity
+ Orville.PostgreSQL.Marshall.SqlMarshaller: [Natural] :: FieldQualifier qualifier => Maybe qualifier -> FieldDefinition nullability a -> Maybe (writeEntity -> a) -> MarshallerField writeEntity
- Orville.PostgreSQL.Marshall.SqlMarshaller: collectFromField :: ReadOnlyColumnOption -> (forall nullability a. FieldDefinition nullability a -> result) -> MarshallerField entity -> [result] -> [result]
+ Orville.PostgreSQL.Marshall.SqlMarshaller: collectFromField :: ReadOnlyColumnOption -> (forall qualifier nullability a. FieldQualifier qualifier => Maybe qualifier -> FieldDefinition nullability a -> result) -> MarshallerField entity -> [result] -> [result]
- Orville.PostgreSQL.Marshall.SyntheticField: syntheticField :: ValueExpression -> String -> (SqlValue -> Either String a) -> SyntheticField a
+ Orville.PostgreSQL.Marshall.SyntheticField: syntheticField :: ValueExpression -> String -> SqlType a -> SyntheticField a
- Orville.PostgreSQL.PgCatalog: DatabaseDescription :: Map (NamespaceName, RelationName) RelationDescription -> DatabaseDescription
+ Orville.PostgreSQL.PgCatalog: DatabaseDescription :: Map (NamespaceName, RelationName) RelationDescription -> Map (NamespaceName, ProcName) PgProc -> Map ExtensionName PgExtension -> DatabaseDescription
- Orville.PostgreSQL.PgCatalog: PgAttribute :: Oid -> AttributeName -> AttributeNumber -> Oid -> Int16 -> Int32 -> Bool -> Bool -> PgAttribute
+ Orville.PostgreSQL.PgCatalog: PgAttribute :: Oid -> AttributeName -> AttributeNumber -> Oid -> Int16 -> Int32 -> Maybe FieldIdentityGeneration -> Bool -> Bool -> PgAttribute
- Orville.PostgreSQL.PgCatalog: RelationDescription :: PgClass -> Map AttributeName PgAttribute -> Map AttributeNumber PgAttributeDefault -> [ConstraintDescription] -> [IndexDescription] -> Maybe PgSequence -> RelationDescription
+ Orville.PostgreSQL.PgCatalog: RelationDescription :: PgClass -> Map AttributeName PgAttribute -> Map AttributeNumber PgAttributeDefault -> [ConstraintDescription] -> [IndexDescription] -> [PgTrigger] -> Maybe PgSequence -> Maybe Text -> Map AttributeName Text -> RelationDescription
- Orville.PostgreSQL.Schema.IndexDefinition: indexCreateExpr :: IndexDefinition -> Qualified TableName -> CreateIndexExpr
+ Orville.PostgreSQL.Schema.IndexDefinition: indexCreateExpr :: IndexDefinition -> QualifiedOrUnqualified TableName -> CreateIndexExpr
- Orville.PostgreSQL.Schema.SequenceDefinition: sequenceName :: SequenceDefinition -> Qualified SequenceName
+ Orville.PostgreSQL.Schema.SequenceDefinition: sequenceName :: SequenceDefinition -> QualifiedOrUnqualified SequenceName
- Orville.PostgreSQL.Schema.SequenceIdentifier: sequenceIdQualifiedName :: SequenceIdentifier -> Qualified SequenceName
+ Orville.PostgreSQL.Schema.SequenceIdentifier: sequenceIdQualifiedName :: SequenceIdentifier -> QualifiedOrUnqualified SequenceName
- Orville.PostgreSQL.Schema.TableDefinition: mkInsertExpr :: ReturningOption returningClause -> TableDefinition key writeEntity readEntity -> NonEmpty writeEntity -> InsertExpr
+ Orville.PostgreSQL.Schema.TableDefinition: mkInsertExpr :: ReturningOption returningClause -> TableDefinition key writeEntity readEntity -> Maybe OnConflictExpr -> NonEmpty writeEntity -> InsertExpr
- Orville.PostgreSQL.Schema.TableDefinition: tableName :: TableDefinition key writeEntity readEntity -> Qualified TableName
+ Orville.PostgreSQL.Schema.TableDefinition: tableName :: TableDefinition key writeEntity readEntity -> QualifiedOrUnqualified TableName
- Orville.PostgreSQL.Schema.TableIdentifier: tableIdQualifiedName :: TableIdentifier -> Qualified TableName
+ Orville.PostgreSQL.Schema.TableIdentifier: tableIdQualifiedName :: TableIdentifier -> QualifiedOrUnqualified TableName

Files

CHANGELOG.md view
@@ -1,5 +1,18 @@-# ChangeLog+# Changelog -## v1.0.0.0+All notable changes to this project will be documented in this file.++The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).++## [Unreleased]++### Added+### Changed+### Deprecated+### Removed+### Fixed+### Security++## [v1.0.0.0] - 2023-10-30  First official release.
orville-postgresql.cabal view
@@ -1,11 +1,11 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.35.2.+-- This file has been generated from package.yaml by hpack version 0.38.1. -- -- see: https://github.com/sol/hpack  name:           orville-postgresql-version:        1.0.0.0+version:        1.1.0.0 synopsis:       A Haskell library for PostgreSQL description:    Orville's goal is to provide a powerful API for applications to access PostgreSQL databases with minimal use of sophisticated language techniques or extensions. See https://github.com/flipstone/orville for more details. category:       database, library, postgresql@@ -15,7 +15,7 @@ license-file:   LICENSE build-type:     Simple tested-with:-    GHC == 8.10.7, GHC == 9.0.2, GHC == 9.2.8, GHC == 9.4.7, GHC == 9.6.3+    GHC == 8.10.7, GHC == 9.0.2, GHC == 9.2.8, GHC == 9.4.8, GHC == 9.6.7, GHC == 9.8.4, GHC == 9.10.2, GHC == 9.12.2 extra-source-files:     CHANGELOG.md @@ -32,6 +32,7 @@   exposed-modules:       Orville.PostgreSQL       Orville.PostgreSQL.AutoMigration+      Orville.PostgreSQL.ErrorDetailLevel       Orville.PostgreSQL.Execution       Orville.PostgreSQL.Execution.Cursor       Orville.PostgreSQL.Execution.Delete@@ -46,39 +47,61 @@       Orville.PostgreSQL.Execution.Sequence       Orville.PostgreSQL.Execution.Transaction       Orville.PostgreSQL.Execution.Update-      Orville.PostgreSQL.ErrorDetailLevel       Orville.PostgreSQL.Expr+      Orville.PostgreSQL.Expr.Aggregate       Orville.PostgreSQL.Expr.BinaryOperator       Orville.PostgreSQL.Expr.ColumnDefinition+      Orville.PostgreSQL.Expr.Comment+      Orville.PostgreSQL.Expr.ConditionalExpr       Orville.PostgreSQL.Expr.Count       Orville.PostgreSQL.Expr.Cursor       Orville.PostgreSQL.Expr.DataType       Orville.PostgreSQL.Expr.Delete+      Orville.PostgreSQL.Expr.Extension+      Orville.PostgreSQL.Expr.FetchClause+      Orville.PostgreSQL.Expr.Filter+      Orville.PostgreSQL.Expr.Function       Orville.PostgreSQL.Expr.GroupBy       Orville.PostgreSQL.Expr.IfExists+      Orville.PostgreSQL.Expr.IfNotExists       Orville.PostgreSQL.Expr.Index       Orville.PostgreSQL.Expr.Insert+      Orville.PostgreSQL.Expr.Join       Orville.PostgreSQL.Expr.LimitExpr       Orville.PostgreSQL.Expr.Math       Orville.PostgreSQL.Expr.Name       Orville.PostgreSQL.Expr.OffsetExpr+      Orville.PostgreSQL.Expr.OnConflict+      Orville.PostgreSQL.Expr.OrReplace       Orville.PostgreSQL.Expr.OrderBy       Orville.PostgreSQL.Expr.Query       Orville.PostgreSQL.Expr.ReturningExpr+      Orville.PostgreSQL.Expr.RowLocking       Orville.PostgreSQL.Expr.Select       Orville.PostgreSQL.Expr.SequenceDefinition       Orville.PostgreSQL.Expr.TableConstraint       Orville.PostgreSQL.Expr.TableDefinition       Orville.PostgreSQL.Expr.TableReferenceList+      Orville.PostgreSQL.Expr.TextSearch       Orville.PostgreSQL.Expr.Time       Orville.PostgreSQL.Expr.Transaction+      Orville.PostgreSQL.Expr.Trigger       Orville.PostgreSQL.Expr.Update+      Orville.PostgreSQL.Expr.Vacuum       Orville.PostgreSQL.Expr.ValueExpression+      Orville.PostgreSQL.Expr.Values       Orville.PostgreSQL.Expr.WhereClause+      Orville.PostgreSQL.Expr.Window+      Orville.PostgreSQL.Expr.Window.WindowClause+      Orville.PostgreSQL.Expr.Window.WindowDefinitionExpr+      Orville.PostgreSQL.Expr.Window.WindowFunction+      Orville.PostgreSQL.Extension.PgTrgm       Orville.PostgreSQL.Marshall+      Orville.PostgreSQL.Marshall.AliasName       Orville.PostgreSQL.Marshall.DefaultValue       Orville.PostgreSQL.Marshall.FieldDefinition       Orville.PostgreSQL.Marshall.MarshallError+      Orville.PostgreSQL.Marshall.SqlComparable       Orville.PostgreSQL.Marshall.SqlMarshaller       Orville.PostgreSQL.Marshall.SqlType       Orville.PostgreSQL.Marshall.SyntheticField@@ -87,12 +110,12 @@       Orville.PostgreSQL.Monad.MonadOrville       Orville.PostgreSQL.Monad.Orville       Orville.PostgreSQL.OrvilleState+      Orville.PostgreSQL.PgCatalog       Orville.PostgreSQL.Plan       Orville.PostgreSQL.Plan.Explanation       Orville.PostgreSQL.Plan.Many       Orville.PostgreSQL.Plan.Operation       Orville.PostgreSQL.Plan.Syntax-      Orville.PostgreSQL.PgCatalog       Orville.PostgreSQL.Raw.Connection       Orville.PostgreSQL.Raw.PgTextFormatValue       Orville.PostgreSQL.Raw.PgTime@@ -101,17 +124,23 @@       Orville.PostgreSQL.Raw.SqlValue       Orville.PostgreSQL.Schema       Orville.PostgreSQL.Schema.ConstraintDefinition+      Orville.PostgreSQL.Schema.ExtensionIdentifier+      Orville.PostgreSQL.Schema.FunctionDefinition+      Orville.PostgreSQL.Schema.FunctionIdentifier       Orville.PostgreSQL.Schema.IndexDefinition       Orville.PostgreSQL.Schema.PrimaryKey       Orville.PostgreSQL.Schema.SequenceDefinition       Orville.PostgreSQL.Schema.SequenceIdentifier       Orville.PostgreSQL.Schema.TableDefinition       Orville.PostgreSQL.Schema.TableIdentifier+      Orville.PostgreSQL.Schema.TriggerDefinition       Orville.PostgreSQL.UnliftIO   other-modules:+      Orville.PostgreSQL.Expr.Internal.Name.Alias       Orville.PostgreSQL.Expr.Internal.Name.ColumnName       Orville.PostgreSQL.Expr.Internal.Name.ConstraintName       Orville.PostgreSQL.Expr.Internal.Name.CursorName+      Orville.PostgreSQL.Expr.Internal.Name.ExtensionName       Orville.PostgreSQL.Expr.Internal.Name.FunctionName       Orville.PostgreSQL.Expr.Internal.Name.Identifier       Orville.PostgreSQL.Expr.Internal.Name.IndexName@@ -120,6 +149,8 @@       Orville.PostgreSQL.Expr.Internal.Name.SchemaName       Orville.PostgreSQL.Expr.Internal.Name.SequenceName       Orville.PostgreSQL.Expr.Internal.Name.TableName+      Orville.PostgreSQL.Expr.Internal.Name.TriggerName+      Orville.PostgreSQL.Expr.Internal.Name.WindowName       Orville.PostgreSQL.Internal.Bracket       Orville.PostgreSQL.Internal.Extra.NonEmpty       Orville.PostgreSQL.Internal.FieldName@@ -134,9 +165,13 @@       Orville.PostgreSQL.PgCatalog.PgAttributeDefault       Orville.PostgreSQL.PgCatalog.PgClass       Orville.PostgreSQL.PgCatalog.PgConstraint+      Orville.PostgreSQL.PgCatalog.PgDescription+      Orville.PostgreSQL.PgCatalog.PgExtension       Orville.PostgreSQL.PgCatalog.PgIndex       Orville.PostgreSQL.PgCatalog.PgNamespace+      Orville.PostgreSQL.PgCatalog.PgProc       Orville.PostgreSQL.PgCatalog.PgSequence+      Orville.PostgreSQL.PgCatalog.PgTrigger       Paths_orville_postgresql   hs-source-dirs:       src@@ -144,23 +179,27 @@       attoparsec >=0.10 && <0.15     , base >=4.8 && <5     , bytestring >=0.10 && <0.13-    , containers ==0.6.*+    , containers >=0.6 && <0.9     , dlist >=0.8 && <1.1     , network-uri ==2.6.*-    , postgresql-libpq >=0.9.4.2 && <0.11+    , postgresql-libpq >=0.9.4.2 && <0.12     , random >=1.0 && <2-    , resource-pool <0.3 || >=0.4 && <0.5+    , resource-pool <0.3 || >=0.4 && <0.6     , safe-exceptions >=0.1.7 && <0.2     , text >=1.2 && <1.3 || >=2.0 && <2.2-    , time >=1.9.1 && <1.13+    , time >=1.9.1 && <1.16     , transformers >=0.5 && <0.7     , unliftio-core >=0.1 && <0.3     , uuid >=1.3.15 && <1.4   default-language: Haskell2010   if flag(ci)-    ghc-options: -Wall -Werror -Wcompat -Widentities -Wincomplete-uni-patterns -Wincomplete-patterns -Wincomplete-record-updates -Wmissing-local-signatures -Wmissing-export-lists -Wmissing-import-lists -Wnoncanonical-monad-instances -Wredundant-constraints -Wpartial-fields -Wmissed-specialisations -Wno-implicit-prelude -Wno-safe -Wno-unsafe-  else-    ghc-options: -Wall -fwarn-incomplete-uni-patterns -fwarn-incomplete-record-updates+    ghc-options: -Wall -Werror -Wcompat -Widentities -Wincomplete-uni-patterns -Wincomplete-patterns -Wincomplete-record-updates -Wmissing-local-signatures -Wmissing-export-lists -Wmissing-import-lists -Wmonomorphism-restriction -Wnoncanonical-monad-instances -Wpartial-fields -Wredundant-constraints -Wmissed-specialisations -Wno-implicit-prelude -Wno-safe -Wno-unsafe+    if impl (ghc >= 9.2)+      ghc-options: -Woperator-whitespace -Wredundant-bang-patterns+    if impl (ghc >= 9.4)+      ghc-options: -Wredundant-strictness-flags+    if impl (ghc >= 9.8)+      ghc-options: -Wterm-variable-capture  test-suite spec   type: exitcode-stdio-1.0@@ -176,20 +215,29 @@       Test.Entities.User       Test.EntityOperations       Test.Execution+      Test.Expr.Aggregate+      Test.Expr.ConditionalExpr       Test.Expr.Count       Test.Expr.Cursor       Test.Expr.GroupBy       Test.Expr.GroupByOrderBy       Test.Expr.InsertUpdateDelete+      Test.Expr.Join       Test.Expr.Math       Test.Expr.OrderBy       Test.Expr.SequenceDefinition       Test.Expr.TableDefinition+      Test.Expr.TableReferenceList       Test.Expr.TestSchema+      Test.Expr.TextSearch       Test.Expr.Time+      Test.Expr.Trigger+      Test.Expr.Vacuum       Test.Expr.Where+      Test.Expr.Window       Test.FieldDefinition       Test.MarshallError+      Test.Orphans       Test.PgAssert       Test.PgCatalog       Test.PgGen@@ -215,17 +263,17 @@       attoparsec >=0.10 && <0.15     , base >=4.8 && <5     , bytestring >=0.10 && <0.13-    , containers ==0.6.*-    , hedgehog >=1.0.5 && <1.5+    , containers >=0.6 && <0.9+    , hedgehog >=1.0.5 && <1.7     , orville-postgresql-    , postgresql-libpq >=0.9.4.2 && <0.11-    , resource-pool <0.3 || >=0.4 && <0.5+    , postgresql-libpq >=0.9.4.2 && <0.12+    , resource-pool <0.3 || >=0.4 && <0.6     , safe-exceptions >=0.1.7 && <0.2     , text >=1.2 && <1.3 || >=2.0 && <2.2-    , time >=1.9.1 && <1.13+    , time >=1.9.1 && <1.16+    , transformers >=0.5 && <0.7+    , unliftio >=0.1 && <0.3     , uuid >=1.3.15 && <1.4   default-language: Haskell2010   if flag(ci)     ghc-options: -j -Wall -Werror -Wcompat -Widentities -Wincomplete-uni-patterns -Wincomplete-record-updates -Wmissing-local-signatures -Wmissing-export-lists -Wno-implicit-prelude -Wno-safe -Wno-unsafe -Wnoncanonical-monad-instances -Wredundant-constraints -Wpartial-fields -Wmissed-specialisations -Woverflowed-literals-  else-    ghc-options: -Wall -Wcompat -Widentities -Wincomplete-uni-patterns -Wincomplete-record-updates -Wmissing-local-signatures -Wmissing-export-lists -Wno-implicit-prelude -Wno-safe -Wno-unsafe -Wnoncanonical-monad-instances -Wredundant-constraints -Wpartial-fields -Wmissed-specialisations -Woverflowed-literals
src/Orville/PostgreSQL.hs view
@@ -1,5 +1,5 @@ {- |-Copyright : Flipstone Technology Partners 2023+Copyright : Flipstone Technology Partners 2023-2025 License   : MIT Stability : Stable @@ -70,6 +70,10 @@      -- * Opening transactions and savepoints   , Transaction.withTransaction+  , Transaction.withTransactionInstruction+  , Transaction.TransactionInstruction (Commit, Rollback)+  , Transaction.inWithTransaction+  , Transaction.InWithTransaction (InOutermostTransaction, InSavepointTransaction)      -- * Types for incorporating Orville into other Monads   , MonadOrville.MonadOrville@@ -97,10 +101,14 @@   , TableDefinition.mkTableDefinition   , TableDefinition.mkTableDefinitionWithoutKey   , TableDefinition.setTableSchema+  , TableDefinition.setTableComment+  , TableDefinition.tableComment   , TableDefinition.tableConstraints   , TableDefinition.addTableConstraints   , TableDefinition.tableIndexes   , TableDefinition.addTableIndexes+  , TableDefinition.tableTriggers+  , TableDefinition.addTableTriggers   , TableDefinition.dropColumns   , TableDefinition.columnsToDrop   , TableDefinition.tableIdentifier@@ -144,6 +152,21 @@   , IndexDefinition.IndexCreationStrategy (Transactional, Concurrent)   , IndexDefinition.setIndexCreationStrategy   , IndexDefinition.indexCreationStrategy+  , IndexDefinition.IndexMigrationKey (AttributeBasedIndexKey, NamedIndexKey)+  , IndexDefinition.AttributeBasedIndexMigrationKey (AttributeBasedIndexMigrationKey, indexKeyUniqueness, indexKeyColumns)+  , IndexDefinition.NamedIndexMigrationKey+  , IndexDefinition.indexMigrationKey+  , TriggerDefinition.TriggerDefinition+  , TriggerDefinition.beforeInsert+  , TriggerDefinition.afterInsert+  , TriggerDefinition.beforeUpdate+  , TriggerDefinition.afterUpdate+  , TriggerDefinition.beforeDelete+  , TriggerDefinition.afterDelete+  , TriggerDefinition.mkTriggerDefinition+  , TriggerDefinition.mkCreateTriggerExpr+  , TriggerDefinition.TriggerMigrationKey (NamedTriggerKey)+  , TriggerDefinition.triggerMigrationKey   , PrimaryKey.PrimaryKey   , PrimaryKey.primaryKey   , PrimaryKey.compositePrimaryKey@@ -161,17 +184,20 @@   , SqlMarshaller.marshallReadOnlyField   , SqlMarshaller.marshallPartial   , SqlMarshaller.marshallMaybe+  , SqlMarshaller.marshallQualifyFields   , SqlMarshaller.prefixMarshaller   , SqlMarshaller.foldMarshallerFields   , SqlMarshaller.collectFromField   , SqlMarshaller.ReadOnlyColumnOption (IncludeReadOnlyColumns, ExcludeReadOnlyColumns)   , SyntheticField.SyntheticField   , SyntheticField.syntheticFieldExpression+  , SyntheticField.syntheticFieldName   , SyntheticField.syntheticFieldAlias   , SyntheticField.syntheticFieldValueFromSqlValue   , SyntheticField.syntheticField   , SyntheticField.nullableSyntheticField   , SyntheticField.prefixSyntheticField+  , SyntheticField.orderBySyntheticField   , FieldDefinition.FieldDefinition   , FieldDefinition.NotNull   , FieldDefinition.Nullable@@ -220,6 +246,19 @@   , FieldDefinition.setField   , (FieldDefinition..:=)   , FieldDefinition.FieldNullability (NotNullField, NullableField)+  , FieldDefinition.QualifiedFieldDefinition+  , FieldDefinition.qualifyField+  , FieldDefinition.qualifiedFieldColumnName+  , FieldDefinition.FieldQualifier (qualifyColumnName)+  , FieldDefinition.markAsIdentity+  , FieldDefinition.unmarkIdentity+  , Marshall.AliasName+  , Marshall.stringToAliasName+  , Marshall.aliasNameToString+  , Marshall.aliasNameToAliasExpr+  , Marshall.aliasNameAndFieldNameToColumnName+  , Marshall.aliasNameToByteString+  , Marshall.byteStringToAliasName   , DefaultValue.DefaultValue   , DefaultValue.integerDefault   , DefaultValue.smallIntegerDefault@@ -250,10 +289,15 @@   , SelectOptions.where_   , SelectOptions.emptySelectOptions   , SelectOptions.appendSelectOptions+  , SelectOptions.forRowLock+  , SelectOptions.fetchRow+  , SelectOptions.window   , FieldDefinition.fieldEquals   , (FieldDefinition..==)   , FieldDefinition.fieldNotEquals   , (FieldDefinition../=)+  , FieldDefinition.fieldIsDistinctFrom+  , FieldDefinition.fieldIsNotDistinctFrom   , FieldDefinition.fieldGreaterThan   , (FieldDefinition..>)   , FieldDefinition.fieldLessThan@@ -279,15 +323,26 @@   , Expr.descendingOrder   , Expr.descendingOrderWith   , FieldDefinition.orderByField+  , Marshall.SqlComparable (toComparableSqlValue, referenceValueExpression)+  , Marshall.orderBySqlComparable   , Expr.orderByColumnName   , Expr.andExpr   , Expr.orExpr   , (Expr..&&)   , (Expr..||)+  , Expr.rowLockingClause+  , Expr.updateStrength+  , Expr.noKeyUpdateStrength+  , Expr.shareStrength+  , Expr.keyShareStrength+  , Expr.noWaitRowLockingOption+  , Expr.skipLockedRowLockingOption   , SelectOptions.selectGroupByClause   , SelectOptions.selectOrderByClause   , SelectOptions.selectWhereClause   , SelectOptions.selectDistinct+  , SelectOptions.selectRowLockingClause+  , SelectOptions.selectFetchClause      -- * Functions for defining and working with sequences   , Sequence.sequenceNextValue@@ -319,6 +374,29 @@   , SequenceIdentifier.sequenceIdSchemaNameString   , SequenceIdentifier.sequenceIdToString +    -- * Functions for defining and working with PostgreSQL functions+  , FunctionDefinition.FunctionDefinition+  , FunctionDefinition.setFunctionSchema+  , FunctionDefinition.mkTriggerFunction+  , FunctionDefinition.mkCreateFunctionExpr+  , FunctionDefinition.functionName+  , FunctionDefinition.functionIdentifier+  , FunctionDefinition.functionSource+  , FunctionIdentifier.FunctionIdentifier+  , FunctionIdentifier.unqualifiedNameToFunctionId+  , FunctionIdentifier.functionIdUnqualifiedNameString+  , FunctionIdentifier.functionIdQualifiedName+  , FunctionIdentifier.setFunctionIdSchema+  , FunctionIdentifier.functionIdSchemaNameString+  , FunctionIdentifier.functionIdToString+  , Expr.plpgsql++    -- * Functions for working with PostgreSQL extensions+  , ExtensionIdentifier.ExtensionIdentifier+  , ExtensionIdentifier.nameToExtensionId+  , ExtensionIdentifier.extensionIdName+  , ExtensionIdentifier.extensionIdToString+     -- * Numeric types   , SqlType.integer   , SqlType.serial@@ -374,6 +452,7 @@ import qualified Orville.PostgreSQL.Execution.Sequence as Sequence import qualified Orville.PostgreSQL.Execution.Transaction as Transaction import qualified Orville.PostgreSQL.Expr as Expr+import qualified Orville.PostgreSQL.Marshall as Marshall import qualified Orville.PostgreSQL.Marshall.DefaultValue as DefaultValue import qualified Orville.PostgreSQL.Marshall.FieldDefinition as FieldDefinition import qualified Orville.PostgreSQL.Marshall.SqlMarshaller as SqlMarshaller@@ -386,9 +465,13 @@ import qualified Orville.PostgreSQL.Raw.Connection as Connection import qualified Orville.PostgreSQL.Raw.SqlCommenter as SqlCommenter import qualified Orville.PostgreSQL.Schema.ConstraintDefinition as ConstraintDefinition+import qualified Orville.PostgreSQL.Schema.ExtensionIdentifier as ExtensionIdentifier+import qualified Orville.PostgreSQL.Schema.FunctionDefinition as FunctionDefinition+import qualified Orville.PostgreSQL.Schema.FunctionIdentifier as FunctionIdentifier import qualified Orville.PostgreSQL.Schema.IndexDefinition as IndexDefinition import qualified Orville.PostgreSQL.Schema.PrimaryKey as PrimaryKey import qualified Orville.PostgreSQL.Schema.SequenceDefinition as SequenceDefinition import qualified Orville.PostgreSQL.Schema.SequenceIdentifier as SequenceIdentifier import qualified Orville.PostgreSQL.Schema.TableDefinition as TableDefinition import qualified Orville.PostgreSQL.Schema.TableIdentifier as TableIdentifier+import qualified Orville.PostgreSQL.Schema.TriggerDefinition as TriggerDefinition
src/Orville/PostgreSQL/AutoMigration.hs view
@@ -3,7 +3,7 @@ {-# LANGUAGE RankNTypes #-}  {- |-Copyright : Flipstone Technology Partners 2023+Copyright : Flipstone Technology Partners 2023-2025 License   : MIT Stability : Stable @@ -13,7 +13,7 @@ @since 1.0.0.0 -} module Orville.PostgreSQL.AutoMigration-  ( MigrationOptions (runSchemaChanges, runConcurrentIndexCreations, migrationLockId)+  ( MigrationOptions (runSchemaChanges, runConcurrentIndexCreations, migrationLockOptions)   , defaultOptions   , autoMigrateSchema   , SchemaItem (..)@@ -24,6 +24,12 @@   , executeMigrationPlan   , MigrationStep   , MigrationDataError+  , MigrationLock.MigrationLockOptions+  , MigrationLock.defaultLockOptions+  , MigrationLock.migrationLockId+  , MigrationLock.maxLockAttempts+  , MigrationLock.delayBetweenLockAttemptsMicros+  , MigrationLock.lockDelayVariationMicros   , MigrationLock.MigrationLockId   , MigrationLock.defaultLockId   , MigrationLock.nextLockId@@ -42,6 +48,7 @@ import qualified Data.Maybe as Maybe import qualified Data.Set as Set import qualified Data.String as String+import qualified Data.Text as T import qualified Data.Text.Encoding as Enc import qualified Database.PostgreSQL.LibPQ as LibPQ @@ -49,12 +56,13 @@ import qualified Orville.PostgreSQL.Expr as Expr import qualified Orville.PostgreSQL.Internal.IndexDefinition as IndexDefinition import qualified Orville.PostgreSQL.Internal.MigrationLock as MigrationLock+import qualified Orville.PostgreSQL.Marshall as Marshall import qualified Orville.PostgreSQL.PgCatalog as PgCatalog import qualified Orville.PostgreSQL.Raw.RawSql as RawSql import qualified Orville.PostgreSQL.Schema as Schema+import qualified Orville.PostgreSQL.Schema.TableIdentifier as TableIdentifier -{- |-  A 'SchemaItem' represents a single item in a database schema such as a table,+{- | A 'SchemaItem' represents a single item in a database schema such as a table,   index or constraint. The constructor functions below can be used to create   items from other types (such as 'Orville.TableDefinition') to put them into   a list to be used with 'autoMigrateSchema'.@@ -62,35 +70,60 @@ @since 1.0.0.0 -} data SchemaItem where-  -- |-  --    Constructs a 'SchemaItem' from a 'Orville.TableDefinition'.+  -- | Constructs a 'SchemaItem' from a 'Orville.TableDefinition'.+  --   -- @since 1.0.0.0   SchemaTable ::     Orville.TableDefinition key writeEntity readEntity ->     SchemaItem-  -- |-  --    Constructs a 'SchemaItem' that will drop the specified table if it is+  -- | Constructs a 'SchemaItem' that will drop the specified table if it is   --    found in the database.+  --   -- @since 1.0.0.0   SchemaDropTable ::     Orville.TableIdentifier ->     SchemaItem-  -- |-  --    Constructs a 'SchemaItem' from a 'Orville.SequenceDefinition'.+  -- | Constructs a 'SchemaItem' from a 'Orville.SequenceDefinition'.+  --   -- @since 1.0.0.0   SchemaSequence ::     Orville.SequenceDefinition ->     SchemaItem-  -- |-  --    Constructs a 'SchemaItem' that will drop the specified table if it is-  --    found in the database.+  -- | Constructs a 'SchemaItem' that will drop the specified table if it is+  -- found in the database.+  --   -- @since 1.0.0.0   SchemaDropSequence ::     Orville.SequenceIdentifier ->     SchemaItem+  -- | Constructs a 'SchemaItem' from a 'Orville.FunctionDefinition'.+  --+  -- @since 1.1.0.0+  SchemaFunction ::+    Orville.FunctionDefinition ->+    SchemaItem+  -- | Constructs a 'SchemaItem' that will drop the specified table if it is+  -- found in the database.+  --+  -- @since 1.1.0.0+  SchemaDropFunction ::+    Orville.FunctionIdentifier ->+    SchemaItem+  -- | Constructs a 'SchemaItem' from a 'Orville.ExtensionName', that will load the extension.+  --+  -- @since 1.1.0.0+  SchemaExtension ::+    Orville.ExtensionIdentifier ->+    SchemaItem+  -- | Constructs a 'SchemaItem' that will unload the postgresql extension if it is found in the+  -- database.+  --+  -- @since 1.1.0.0+  SchemaDropExtension ::+    Orville.ExtensionIdentifier ->+    SchemaItem -{- |-  Returns a one-line string describing the 'SchemaItem', suitable for a human+{- | Returns a one-line string describing the 'SchemaItem', suitable for a human   to identify it in a list of output.    For example, a 'SchemaItem' constructed via 'SchemaTable' gives @Table <table@@ -109,6 +142,14 @@       "Sequence " <> Orville.sequenceIdToString (Orville.sequenceIdentifier sequenceDef)     SchemaDropSequence sequenceId ->       "Drop sequence " <> Orville.sequenceIdToString sequenceId+    SchemaFunction functionDef ->+      "Function " <> Orville.functionIdToString (Orville.functionIdentifier functionDef)+    SchemaDropFunction functionId ->+      "Drop function " <> Orville.functionIdToString functionId+    SchemaExtension extension ->+      "Extension " <> Orville.extensionIdToString extension+    SchemaDropExtension extension ->+      "Drop Extension " <> Orville.extensionIdToString extension  {- | A 'MigrationPlan' contains an ordered list of migration steps. Each one is a@@ -128,8 +169,7 @@   , i_concurrentIndexSteps :: [MigrationStep]   } -{- |-  Returns all the 'MigrationStep's found in a 'MigrationPlan' together in a+{- | Returns all the 'MigrationStep's found in a 'MigrationPlan' together in a   single list. This is useful if you merely want to examine the steps of a plan   rather than execute them. You should always use 'executeMigrationPlan' to   execute a migration plan to ensure that the transactional steps are done@@ -151,12 +191,11 @@         $ steps   in     MigrationPlan-      { i_transactionalSteps = map migrationStep transactionalSteps-      , i_concurrentIndexSteps = map migrationStep concurrentIndexSteps+      { i_transactionalSteps = fmap migrationStep transactionalSteps+      , i_concurrentIndexSteps = fmap migrationStep concurrentIndexSteps       } -{- |-  A single SQL statement that will be executed in order to migrate the database+{- | A single SQL statement that will be executed in order to migrate the database   to the desired result. You can use 'generateMigrationPlan' to get a list   of these yourself for inspection and debugging. @@ -169,8 +208,7 @@       RawSql.SqlExpression     ) -{- |-  This type is used internally by Orville to order the migration steps after+{- | This type is used internally by Orville to order the migration steps after   they have been created. It is not exposed outside this module.  @since 1.0.0.0@@ -197,14 +235,20 @@     DropForeignKeys -> True     DropUniqueConstraints -> True     DropIndexes -> True+    DropTriggers -> True+    DropFunctions -> True+    DropExtensions -> True+    AddExtensions -> True     AddRemoveTablesAndColumns -> True+    AddFunctions -> True+    AddTriggers -> True     AddIndexesTransactionally -> True     AddUniqueConstraints -> True     AddForeignKeys -> True     AddIndexesConcurrently -> False+    SetComments -> True -{- |-  Indicates the kind of operation being performed by a 'MigrationStep' so+{- | Indicates the kind of operation being performed by a 'MigrationStep' so   that the steps can be ordered in a sequence that is guaranteed to succeed.   The order of the constructors below indicates the order in which steps will   be run.@@ -212,14 +256,36 @@ @since 1.0.0.0 -} data StepType-  = DropForeignKeys-  | DropUniqueConstraints-  | DropIndexes-  | AddRemoveTablesAndColumns-  | AddIndexesTransactionally-  | AddUniqueConstraints-  | AddForeignKeys-  | AddIndexesConcurrently+  = -- | @since 1.0.0.0+    DropForeignKeys+  | -- | @since 1.0.0.0+    DropUniqueConstraints+  | -- | @since 1.0.0.0+    DropIndexes+  | -- | @since 1.1.0.0+    DropTriggers+  | -- | @since 1.1.0.0+    DropFunctions+  | -- | @since 1.1.0.0+    DropExtensions+  | -- | @since 1.1.0.0+    AddExtensions+  | -- | @since 1.0.0.0+    AddRemoveTablesAndColumns+  | -- | @since 1.1.0.0+    AddFunctions+  | -- | @since 1.1.0.0+    AddTriggers+  | -- | @since 1.0.0.0+    AddIndexesTransactionally+  | -- | @since 1.0.0.0+    AddUniqueConstraints+  | -- | @since 1.0.0.0+    AddForeignKeys+  | -- | @since 1.0.0.0+    AddIndexesConcurrently+  | -- | @since 1.1.0.0+    SetComments   deriving     ( -- | @since 1.0.0.0       Eq@@ -227,8 +293,7 @@       Ord     ) -{- |-  A 'MigrationDataError' will be thrown from the migration functions if data+{- | A 'MigrationDataError' will be thrown from the migration functions if data   necessary for migration cannot be found.  @since 1.0.0.0@@ -268,15 +333,17 @@   --       rest of the schema changes.   --   --       @since 1.0.0.0-  , migrationLockId :: MigrationLock.MigrationLockId+  , migrationLockOptions :: MigrationLock.MigrationLockOptions   -- ^-  --       The 'MigrationLock.MigrationLockId' that will be use to ensure only+  --       The 'MigrationLock.MigrationLockOptions' that will be used to ensure only   --       one application is running migrations at a time. The default value-  --       is 'MigrationLock.defaultLockId'. You may want to change this if you-  --       want to run concurrent index creations separately from the rest of-  --       the schema changes without blocking one another.+  --       is 'MigrationLock.defaultLockOptions'. You may want to change these if you+  --       your application has special migration needs. For instance, you might specify+  --       a custom lock id along with migrating just concurrent indexes to allow them+  --       to run separately from the rest of the schema changes without blocking one+  --       another.   ---  --       @since 1.0.0.0+  --       @since 1.1.0.0   }  {- |@@ -290,11 +357,10 @@   MigrationOptions     { runSchemaChanges = True     , runConcurrentIndexCreations = True-    , migrationLockId = MigrationLock.defaultLockId+    , migrationLockOptions = MigrationLock.defaultLockOptions     } -{- |-  This function compares the list of 'SchemaItem's provided against the current+{- | This function compares the list of 'SchemaItem's provided against the current   schema found in the database to determine whether any migrations are   necessary.  If any changes need to be made, this function executes. You can   call 'generateMigrationPlan' and 'executeMigrationPlan' yourself if you want@@ -312,12 +378,11 @@   [SchemaItem] ->   m () autoMigrateSchema options schemaItems =-  MigrationLock.withMigrationLock (migrationLockId options) $ do+  MigrationLock.withMigrationLock (migrationLockOptions options) $ do     plan <- generateMigrationPlanWithoutLock schemaItems     executeMigrationPlanWithoutLock options plan -{- |-  Compares the list of 'SchemaItem's provided against the current schema found+{- | Compares the list of 'SchemaItem's provided against the current schema found   in the database and returns a 'MigrationPlan' that could be executed to make   the database schema match the items given. @@ -337,7 +402,7 @@   [SchemaItem] ->   m MigrationPlan generateMigrationPlan options =-  MigrationLock.withMigrationLock (migrationLockId options)+  MigrationLock.withMigrationLock (migrationLockOptions options)     . generateMigrationPlanWithoutLock  generateMigrationPlanWithoutLock :: Orville.MonadOrville m => [SchemaItem] -> m MigrationPlan@@ -346,18 +411,27 @@     currentNamespace <- findCurrentNamespace      let-      pgCatalogRelations = fmap (schemaItemPgCatalogRelation currentNamespace) schemaItems+      pgCatalogRelations =+        Maybe.mapMaybe+          (schemaItemPgCatalogRelation currentNamespace)+          schemaItems -    dbDesc <- PgCatalog.describeDatabaseRelations pgCatalogRelations+      pgCatalogFunctions =+        Maybe.mapMaybe+          (schemaItemPgCatalogFunction currentNamespace)+          schemaItems+      pgCatalogExtensions =+        Maybe.mapMaybe schemaItemPgCatalogExtension schemaItems +    dbDesc <- PgCatalog.describeDatabase pgCatalogRelations pgCatalogFunctions pgCatalogExtensions+     case traverse (calculateMigrationSteps currentNamespace dbDesc) schemaItems of       Left err ->         liftIO . throwIO $ err       Right migrationSteps ->         pure . mkMigrationPlan . concat $ migrationSteps -{- |-  Executes a 'MigrationPlan' that has been previously devised via+{- | Executes a 'MigrationPlan' that has been previously devised via   'generateMigrationPlan'. Normally all the steps in a migration plan are   executed in a transaction so that they will all be applied together   successfully or all rolled-back if one of them fails. Any indexes using the@@ -381,7 +455,7 @@   MigrationPlan ->   m () executeMigrationPlan options =-  MigrationLock.withMigrationLock (migrationLockId options)+  MigrationLock.withMigrationLock (migrationLockOptions options)     . executeMigrationPlanWithoutLock options  executeMigrationPlanWithoutLock ::@@ -398,8 +472,7 @@    when (runConcurrentIndexCreations options)     . executeMigrationStepsWithoutTransaction-    $ i_concurrentIndexSteps-    $ plan+    $ i_concurrentIndexSteps plan  executeMigrationStepsWithoutTransaction :: Orville.MonadOrville m => [MigrationStep] -> m () executeMigrationStepsWithoutTransaction =@@ -482,9 +555,68 @@                   AddRemoveTablesAndColumns                   (Expr.dropSequenceExpr Nothing (Orville.sequenceIdQualifiedName sequenceId))               ]+    SchemaFunction functionDef ->+      Right $+        let+          (schemaName, procName) =+            functionIdToPgCatalogNames currentNamespace (Orville.functionIdentifier functionDef)+        in+          case PgCatalog.lookupProcedure (schemaName, procName) dbDesc of+            Nothing ->+              [ mkMigrationStepWithType+                  AddFunctions+                  (Orville.mkCreateFunctionExpr functionDef Nothing)+              ]+            Just pgProc ->+              if PgCatalog.pgProcSource pgProc == T.pack (Orville.functionSource functionDef)+                then []+                else+                  [ mkMigrationStepWithType+                      AddFunctions+                      (Orville.mkCreateFunctionExpr functionDef (Just Expr.orReplace))+                  ]+    SchemaDropFunction functionId ->+      Right $+        let+          (schemaName, procName) =+            functionIdToPgCatalogNames currentNamespace functionId+        in+          case PgCatalog.lookupProcedure (schemaName, procName) dbDesc of+            Nothing ->+              []+            Just _proc ->+              [ mkMigrationStepWithType+                  DropFunctions+                  (Expr.dropFunction Nothing (Orville.functionIdQualifiedName functionId))+              ]+    SchemaExtension extension ->+      Right $+        let+          catalogExtensionName = extensionIdToPgCatalogName extension+        in+          case PgCatalog.lookupExtension catalogExtensionName dbDesc of+            Nothing ->+              [ mkMigrationStepWithType+                  AddExtensions+                  (Expr.createExtensionExpr (Orville.extensionIdName extension) Nothing (Just Expr.extensionCascadeExpr))+              ]+            Just _pgExtension ->+              []+    SchemaDropExtension extension ->+      Right $+        let+          catalogExtensionName = extensionIdToPgCatalogName extension+        in+          case PgCatalog.lookupExtension catalogExtensionName dbDesc of+            Nothing ->+              []+            Just _pgExtension ->+              [ mkMigrationStepWithType+                  DropExtensions+                  (Expr.dropExtensionExpr (Orville.extensionIdName extension) Nothing Nothing)+              ] -{- |-  Builds 'MigrationStep's that will perform table creation. This function+{- | Builds 'MigrationStep's that will perform table creation. This function   assumes the table does not exist. The migration step it produces will fail if   the table already exists in its schema. Multiple steps may be required to   create the table if foreign keys exist to that reference other tables, which@@ -511,6 +643,8 @@         (Orville.mkTablePrimaryKeyExpr tableDef)         [] +    commentSteps = mkCreateTableCommentSteps tableDef+     addConstraintActions =       concatMap         (mkAddConstraintActions currentNamespace Set.empty)@@ -520,13 +654,62 @@       concatMap         (mkAddIndexSteps Set.empty tableName)         (Orville.tableIndexes tableDef)++    addTriggerSteps =+      concatMap+        (mkAddTriggerSteps Set.empty tableName)+        (Orville.tableTriggers tableDef)   in     mkMigrationStepWithType AddRemoveTablesAndColumns createTableExpr       : mkConstraintSteps tableName addConstraintActions         <> addIndexSteps+        <> addTriggerSteps+        <> commentSteps -{- |-  Builds migration steps that are required to create or alter the table's+mkCreateTableCommentSteps ::+  Orville.TableDefinition key writeEntity readEntity ->+  [MigrationStepWithType]+mkCreateTableCommentSteps tableDef =+  let+    mbCommentTableStep =+      fmap+        ( mkMigrationStepWithType SetComments+            . Expr.commentTableExpr (Schema.tableName tableDef)+            . Just+            . Expr.commentText+        )+        (Schema.tableComment tableDef)++    tableId = Orville.tableIdentifier tableDef++    mbCommentColumnStep colName =+      fmap+        ( mkMigrationStepWithType SetComments+            . Expr.commentColumnExpr+              (Schema.tableIdSchemaName tableId)+              (Schema.tableIdUnqualifiedName tableId)+              colName+            . Just+            . Expr.commentText+            . T.pack+        )++    mbCommentColumnsSteps =+      Orville.foldMarshallerFields+        (Orville.unannotatedSqlMarshaller $ Schema.tableMarshaller tableDef)+        []+        ( Orville.collectFromField+            Orville.IncludeReadOnlyColumns+            ( const $ \fieldDef ->+                mbCommentColumnStep+                  (Orville.fieldNameToColumnName $ Orville.fieldName fieldDef)+                  (Orville.fieldDescription fieldDef)+            )+        )+  in+    Maybe.catMaybes (mbCommentTableStep : mbCommentColumnsSteps)++{- | Builds migration steps that are required to create or alter the table's   schema to make it match the given table definition.    This function uses the given relation description to determine what@@ -547,7 +730,7 @@         Orville.foldMarshallerFields           (Orville.unannotatedSqlMarshaller $ Orville.tableMarshaller tableDef)           []-          (Orville.collectFromField Orville.IncludeReadOnlyColumns (mkAddAlterColumnActions relationDesc))+          (Orville.collectFromField Orville.IncludeReadOnlyColumns (const (mkAddAlterColumnActions relationDesc)))      dropColumnActions =       concatMap@@ -606,9 +789,32 @@      dropIndexSteps =       concatMap-        (mkDropIndexSteps indexesToKeep systemIndexOids)+        (mkDropIndexSteps indexesToKeep systemIndexOids . TableIdentifier.tableIdSchemaName $ Orville.tableIdentifier tableDef)         (PgCatalog.relationIndexes relationDesc) +    existingTriggers =+      Set.fromList+        . fmap pgTriggerMigrationKey+        . PgCatalog.relationTriggers+        $ relationDesc++    triggersToKeep =+      Map.keysSet+        . Orville.tableTriggers+        $ tableDef++    addTriggerSteps =+      concatMap+        (mkAddTriggerSteps existingTriggers tableName)+        (Orville.tableTriggers tableDef)++    dropTriggerSteps =+      concatMap+        (mkDropTriggerSteps triggersToKeep tableName)+        (PgCatalog.relationTriggers relationDesc)++    commentSteps = mkCommentSteps relationDesc tableDef+     tableName =       Orville.tableName tableDef   in@@ -616,9 +822,11 @@       <> mkConstraintSteps tableName (addConstraintActions <> dropConstraintActions)       <> addIndexSteps       <> dropIndexSteps+      <> addTriggerSteps+      <> dropTriggerSteps+      <> commentSteps -{- |-  Consolidates alter table actions (which should all be related to adding and+{- | Consolidates alter table actions (which should all be related to adding and   dropping constraints) into migration steps based on their 'StepType'. Actions   with the same 'StepType' will be performed togethir in a single @ALTER TABLE@   statement.@@ -626,7 +834,7 @@ @since 1.0.0.0 -} mkConstraintSteps ::-  Expr.Qualified Expr.TableName ->+  Expr.QualifiedOrUnqualified Expr.TableName ->   [(StepType, Expr.AlterTableAction)] ->   [MigrationStepWithType] mkConstraintSteps tableName actions =@@ -635,24 +843,23 @@       (StepType, Expr.AlterTableAction) ->       (StepType, NonEmpty Expr.AlterTableAction)     mkMapEntry (keyType, action) =-      (keyType, (action :| []))+      (keyType, action :| [])      addStep stepType actionExprs steps =       mkMigrationStepWithType stepType (Expr.alterTableExpr tableName actionExprs) : steps   in     Map.foldrWithKey addStep []       . Map.fromListWith (<>)-      . map mkMapEntry+      . fmap mkMapEntry       $ actions -{- |-  If there are any alter table actions for adding or removing columns, creates a migration+{- | If there are any alter table actions for adding or removing columns, creates a migration   step to perform them. Otherwise returns an empty list.  @since 1.0.0.0 -} mkAlterColumnSteps ::-  Expr.Qualified Expr.TableName ->+  Expr.QualifiedOrUnqualified Expr.TableName ->   [Expr.AlterTableAction] ->   [MigrationStepWithType] mkAlterColumnSteps tableName actionExprs =@@ -662,8 +869,7 @@     Just nonEmptyActionExprs ->       [mkMigrationStepWithType AddRemoveTablesAndColumns (Expr.alterTableExpr tableName nonEmptyActionExprs)] -{- |-  Builds 'Expr.AlterTableAction' expressions to bring the database schema in+{- | Builds 'Expr.AlterTableAction' expressions to bring the database schema in   line with the given 'Orville.FieldDefinition', or none if no change is   required. @@ -690,7 +896,7 @@                   || (Orville.sqlTypeMaximumLength sqlType /= PgCatalog.pgAttributeMaxLength attr)                columnName =-                Orville.fieldColumnName fieldDef+                Orville.fieldNameToColumnName $ Orville.fieldName fieldDef                dataType =                 Orville.sqlTypeExpr sqlType@@ -709,7 +915,7 @@                alterNullability = do                 guard nullabilityIsChanged-                [Expr.alterColumnNullability (Orville.fieldColumnName fieldDef) nullabilityAction]+                [Expr.alterColumnNullability columnName nullabilityAction]                maybeExistingDefault =                 PgCatalog.lookupAttributeDefault attr relationDesc@@ -749,8 +955,28 @@                           ( Just (Expr.alterColumnDropDefault columnName)                           , Just (Expr.alterColumnSetDefault columnName newDefault)                           )++              alterIdentity =+                case (Marshall.fieldIdentityGeneration fieldDef, PgCatalog.pgAttributeIdentity attr) of+                  (Nothing, Nothing) -> mempty+                  (Just Marshall.GeneratedAlways, Just Marshall.GeneratedAlways) -> mempty+                  (Just Marshall.GeneratedByDefault, Just Marshall.GeneratedByDefault) -> mempty+                  (Nothing, Just _) ->+                    pure $ Expr.alterColumnDropIdentity columnName Nothing+                  (Just Marshall.GeneratedAlways, Nothing) ->+                    pure $ Expr.alterColumnAddIdentity columnName Expr.alwaysColumnIdentityGeneration+                  (Just Marshall.GeneratedAlways, Just _existing) ->+                    [Expr.alterColumnDropIdentity columnName Nothing, Expr.alterColumnAddIdentity columnName Expr.alwaysColumnIdentityGeneration]+                  (Just Marshall.GeneratedByDefault, Nothing) ->+                    pure $ Expr.alterColumnAddIdentity columnName Expr.byDefaultColumnIdentityGeneration+                  (Just Marshall.GeneratedByDefault, Just _existing) ->+                    [Expr.alterColumnDropIdentity columnName Nothing, Expr.alterColumnAddIdentity columnName Expr.byDefaultColumnIdentityGeneration]             in-              Maybe.maybeToList dropDefault <> alterType <> Maybe.maybeToList setDefault <> alterNullability+              Maybe.maybeToList dropDefault+                <> alterType+                <> Maybe.maybeToList setDefault+                <> alterNullability+                <> alterIdentity       _ ->         -- Either the column doesn't exist in the table _OR_ it's a system         -- column. If it's a system column, attempting to add it will result@@ -762,8 +988,7 @@         -- attributes.         [Expr.addColumn (Orville.fieldColumnDefinition fieldDef)] -{- |-  Builds 'Expr.AlterTableAction' expressions for the given attribute to make+{- | Builds 'Expr.AlterTableAction' expressions for the given attribute to make   the database schema match the given 'Orville.TableDefinition'. This function   is only responsible for handling cases where the attribute does not have a   correspending 'Orville.FieldDefinition'. See 'mkAlterTableSteps' for those@@ -784,8 +1009,7 @@    [Expr.dropColumn $ Expr.columnName attrName] -{- |-  Sets the schema name on a constraint to the given namespace when the+{- | Sets the schema name on a constraint to the given namespace when the   constraint has no namespace explicitly given. This is important for Orville   to discover whether a constraint from a table definition matches a constraint   found to already exist in the database because constraints in the database@@ -814,8 +1038,7 @@         Just _ ->           constraintKey -{- |-  Builds 'Expr.AlterTableAction' expressions to create the given table+{- | Builds 'Expr.AlterTableAction' expressions to create the given table   constraint if it does not exist.  @since 1.0.0.0@@ -840,8 +1063,7 @@       then []       else [(stepType, Expr.addConstraint (Orville.constraintSqlExpr constraintDef))] -{- |-  Builds 'Expr.AlterTableAction' expressions to drop the given table+{- | Builds 'Expr.AlterTableAction' expressions to drop the given table   constraint if it should not exist.  @since 1.0.0.0@@ -873,8 +1095,7 @@           in             [(stepType, Expr.dropConstraint constraintName)] -{- |-  Builds the orville migration key for a description of an existing constraint+{- | Builds the orville migration key for a description of an existing constraint   so that it can be compared with constraints found in a table definition.   Constraint keys built this way always have a schema name populated, so it's   important to set the schema names for the constraints found in the table@@ -899,8 +1120,9 @@     constraint =       PgCatalog.constraintRecord constraintDesc +    pgAttributeNamesToFieldNames :: [PgCatalog.PgAttribute] -> [Orville.FieldName]     pgAttributeNamesToFieldNames =-      map (Orville.stringToFieldName . PgCatalog.attributeNameToString . PgCatalog.pgAttributeName)+      fmap (Orville.stringToFieldName . PgCatalog.attributeNameToString . PgCatalog.pgAttributeName)      foreignRelationTableId :: PgCatalog.ForeignRelationDescription -> Orville.TableIdentifier     foreignRelationTableId foreignRelationDesc =@@ -941,14 +1163,13 @@               PgCatalog.pgConstraintForeignKeyOnDeleteType $ PgCatalog.constraintRecord constraintDesc           } -{- |-  Builds migration steps to create an index if it does not exist.+{- | Builds migration steps to create an index if it does not exist.  @since 1.0.0.0 -} mkAddIndexSteps ::   Set.Set IndexDefinition.IndexMigrationKey ->-  Expr.Qualified Expr.TableName ->+  Expr.QualifiedOrUnqualified Expr.TableName ->   Orville.IndexDefinition ->   [MigrationStepWithType] mkAddIndexSteps existingIndexes tableName indexDef =@@ -965,17 +1186,17 @@       then []       else [mkMigrationStepWithType indexStep (Orville.indexCreateExpr indexDef tableName)] -{- |-  Builds migration steps to drop an index if it should not exist.+{- | Builds migration steps to drop an index if it should not exist.  @since 1.0.0.0 -} mkDropIndexSteps ::   Set.Set IndexDefinition.IndexMigrationKey ->   Set.Set LibPQ.Oid ->+  Maybe Expr.SchemaName ->   PgCatalog.IndexDescription ->   [MigrationStepWithType]-mkDropIndexSteps indexesToKeep systemIndexOids indexDesc =+mkDropIndexSteps indexesToKeep systemIndexOids mSchema indexDesc =   case pgIndexMigrationKeys indexDesc of     [] ->       []@@ -990,16 +1211,122 @@             . PgCatalog.pgClassRelationName             $ pgClass +        qualifiedIndexName = case mSchema of+          Just schema -> Expr.untrackQualified $ Expr.qualifyIndex schema indexName+          Nothing -> Expr.unqualified indexName+         indexOid =           PgCatalog.pgClassOid pgClass       in         if any (flip Set.member indexesToKeep) indexKeys           || Set.member indexOid systemIndexOids           then []-          else [mkMigrationStepWithType DropIndexes (Expr.dropIndexExpr indexName)]+          else [mkMigrationStepWithType DropIndexes (Expr.dropIndexExpr qualifiedIndexName)] -{- |-  Primary Key, Unique, and Exclusion constraints automatically create indexes+{- | Builds migration steps to create a trigger if it does not exist.++@since 1.1.0.0+-}+mkAddTriggerSteps ::+  Set.Set Schema.TriggerMigrationKey ->+  Expr.QualifiedOrUnqualified Expr.TableName ->+  Orville.TriggerDefinition ->+  [MigrationStepWithType]+mkAddTriggerSteps existingTriggers tableName triggerDef =+  let+    triggerKey =+      Schema.triggerMigrationKey triggerDef+  in+    if Set.member triggerKey existingTriggers+      then []+      else [mkMigrationStepWithType AddTriggers (Orville.mkCreateTriggerExpr triggerDef Nothing tableName)]++{- | Builds migration steps to drop an trigger if it should not exist.++@since 1.1.0.0+-}+mkDropTriggerSteps ::+  Set.Set Schema.TriggerMigrationKey ->+  Expr.QualifiedOrUnqualified Expr.TableName ->+  PgCatalog.PgTrigger ->+  [MigrationStepWithType]+mkDropTriggerSteps triggersToKeep tableName pgTrigger =+  let+    migrationKey =+      pgTriggerMigrationKey pgTrigger++    triggerName =+      Expr.triggerName+        . PgCatalog.triggerNameToString+        . PgCatalog.pgTriggerName+        $ pgTrigger+  in+    if Set.member migrationKey triggersToKeep || PgCatalog.pgTriggerIsInternal pgTrigger+      then []+      else [mkMigrationStepWithType DropTriggers (Expr.dropTrigger Nothing triggerName tableName)]++mkCommentSteps ::+  PgCatalog.RelationDescription ->+  Orville.TableDefinition key writeEntity readEntity ->+  [MigrationStepWithType]+mkCommentSteps relationDesc tableDef =+  let+    tableName = Orville.tableName tableDef++    commentTableStep =+      if PgCatalog.relationComment relationDesc == Schema.tableComment tableDef+        then []+        else+          [ mkMigrationStepWithType+              SetComments+              ( Expr.commentTableExpr+                  tableName+                  (fmap Expr.commentText (Schema.tableComment tableDef))+              )+          ]++    commentColumnsSteps =+      Maybe.catMaybes $+        Orville.foldMarshallerFields+          (Orville.unannotatedSqlMarshaller $ Orville.tableMarshaller tableDef)+          []+          ( Orville.collectFromField+              Orville.IncludeReadOnlyColumns+              (const . mkCommentColumnStep relationDesc $ Orville.tableIdentifier tableDef)+          )+  in+    commentTableStep <> commentColumnsSteps++mkCommentColumnStep ::+  PgCatalog.RelationDescription ->+  Schema.TableIdentifier ->+  Orville.FieldDefinition n a ->+  Maybe MigrationStepWithType+mkCommentColumnStep relationDesc tableId fieldDef =+  let+    fieldAttributeName =+      String.fromString (Orville.fieldNameToString $ Orville.fieldName fieldDef)++    currentFieldComment =+      PgCatalog.lookupAttributeComment fieldAttributeName relationDesc++    targetFieldComment =+      fmap T.pack (Orville.fieldDescription fieldDef)+  in+    if currentFieldComment == targetFieldComment+      then Nothing+      else+        Just $+          mkMigrationStepWithType+            SetComments+            ( Expr.commentColumnExpr+                (Schema.tableIdSchemaName tableId)+                (Schema.tableIdUnqualifiedName tableId)+                (Orville.fieldNameToColumnName $ Orville.fieldName fieldDef)+                (fmap Expr.commentText targetFieldComment)+            )++{- | Primary Key, Unique, and Exclusion constraints automatically create indexes   that we don't want orville to consider for the purposes of migrations. This   function checks the constraint type and returns the OID of the supporting   index if the constraint is one of these types.@@ -1026,8 +1353,7 @@     PgCatalog.ConstraintTrigger ->       Nothing -{- |-  Builds the orville migration keys given a description of an existing index+{- | Builds the orville migration keys given a description of an existing index   so that it can be compared with indexs found in a table definition.    If the description includes expressions as members of the index rather than@@ -1053,7 +1379,7 @@         Nothing ->           []   in-    [mkNamedIndexKey] ++ mkAttributeBasedIndexKey+    mkNamedIndexKey : mkAttributeBasedIndexKey  pgAttributeBasedIndexMigrationKey ::   PgCatalog.IndexDescription ->@@ -1079,21 +1405,102 @@       , IndexDefinition.indexKeyColumns = fieldNames       } +{- | Builds the orville migration key given a description of an existing trigger+  so that it can be compared with triggers found in a table definition.++  If the description includes expressions as members of the trigger rather than+  simple attributes, 'Nothing' is returned.++@since 1.1.0.0+-}+pgTriggerMigrationKey ::+  PgCatalog.PgTrigger ->+  Schema.TriggerMigrationKey+pgTriggerMigrationKey =+  Schema.NamedTriggerKey+    . PgCatalog.triggerNameToString+    . PgCatalog.pgTriggerName++{- | Retrieves from a 'SchemaItem' the relation (if any) that the schema item relates to+  so that the migration infrastructure can look up data about it in the PostgreSQL+  catalog for planning migration steps.+-} schemaItemPgCatalogRelation ::   PgCatalog.NamespaceName ->   SchemaItem ->-  (PgCatalog.NamespaceName, PgCatalog.RelationName)+  Maybe (PgCatalog.NamespaceName, PgCatalog.RelationName) schemaItemPgCatalogRelation currentNamespace item =   case item of     SchemaTable tableDef ->-      tableIdToPgCatalogNames currentNamespace (Orville.tableIdentifier tableDef)+      Just $ tableIdToPgCatalogNames currentNamespace (Orville.tableIdentifier tableDef)     SchemaDropTable tableId ->-      tableIdToPgCatalogNames currentNamespace tableId+      Just $ tableIdToPgCatalogNames currentNamespace tableId     SchemaSequence sequenceDef ->-      sequenceIdToPgCatalogNames currentNamespace (Orville.sequenceIdentifier sequenceDef)+      Just $ sequenceIdToPgCatalogNames currentNamespace (Orville.sequenceIdentifier sequenceDef)     SchemaDropSequence sequenceId ->-      sequenceIdToPgCatalogNames currentNamespace sequenceId+      Just $ sequenceIdToPgCatalogNames currentNamespace sequenceId+    SchemaFunction _functionDef ->+      Nothing+    SchemaDropFunction _functionId ->+      Nothing+    SchemaExtension _extension ->+      Nothing+    SchemaDropExtension _extension ->+      Nothing +{- | Retrieves from a 'SchemaItem' the function (if any) that the schema item relates to+  so that the migration infrastructure can look up data about it in the PostgreSQL+  catalog for planning migration steps.+-}+schemaItemPgCatalogFunction ::+  PgCatalog.NamespaceName ->+  SchemaItem ->+  Maybe (PgCatalog.NamespaceName, PgCatalog.ProcName)+schemaItemPgCatalogFunction currentNamespace item =+  case item of+    SchemaTable _tableDef ->+      Nothing+    SchemaDropTable _tableId ->+      Nothing+    SchemaSequence _sequenceDef ->+      Nothing+    SchemaDropSequence _sequenceId ->+      Nothing+    SchemaFunction functionDef ->+      Just $ functionIdToPgCatalogNames currentNamespace (Orville.functionIdentifier functionDef)+    SchemaDropFunction functionId ->+      Just $ functionIdToPgCatalogNames currentNamespace functionId+    SchemaExtension _extension ->+      Nothing+    SchemaDropExtension _extension ->+      Nothing++{- | Retrieves from a 'SchemaItem' the extension (if any) that the schema item relates to+  so that the migration infrastructure can look up data about it in the PostgreSQL+  catalog for planning migration steps.+-}+schemaItemPgCatalogExtension ::+  SchemaItem ->+  Maybe PgCatalog.ExtensionName+schemaItemPgCatalogExtension item =+  case item of+    SchemaTable _tableDef ->+      Nothing+    SchemaDropTable _tableId ->+      Nothing+    SchemaSequence _sequenceDef ->+      Nothing+    SchemaDropSequence _sequenceId ->+      Nothing+    SchemaFunction _functionDef ->+      Nothing+    SchemaDropFunction _functionId ->+      Nothing+    SchemaExtension extension ->+      Just $ extensionIdToPgCatalogName extension+    SchemaDropExtension extension ->+      Just $ extensionIdToPgCatalogName extension+ tableIdToPgCatalogNames ::   PgCatalog.NamespaceName ->   Orville.TableIdentifier ->@@ -1112,6 +1519,30 @@   in     (actualNamespace, relationName) +functionIdToPgCatalogNames ::+  PgCatalog.NamespaceName ->+  Orville.FunctionIdentifier ->+  (PgCatalog.NamespaceName, PgCatalog.ProcName)+functionIdToPgCatalogNames currentNamespace functionId =+  let+    actualNamespace =+      maybe currentNamespace String.fromString+        . Orville.functionIdSchemaNameString+        $ functionId++    relationName =+      String.fromString+        . Orville.functionIdUnqualifiedNameString+        $ functionId+  in+    (actualNamespace, relationName)++extensionIdToPgCatalogName ::+  Orville.ExtensionIdentifier ->+  PgCatalog.ExtensionName+extensionIdToPgCatalogName =+  String.fromString . Orville.extensionIdToString+ mkAlterSequenceSteps ::   Orville.SequenceDefinition ->   PgCatalog.PgSequence ->@@ -1192,7 +1623,7 @@             -- put it in quotes it tries to treat it as a regular column name,             -- which then can't be found as a column in the query.             (RawSql.unsafeSqlExpression "current_schema")-            (Orville.fieldColumnName PgCatalog.namespaceNameField)+            (Orville.fieldNameToColumnName $ Orville.fieldName PgCatalog.namespaceNameField)         ]     )     Nothing
src/Orville/PostgreSQL/ErrorDetailLevel.hs view
@@ -17,8 +17,7 @@   ) where -{- |-  'ErrorDetailLevel' provides a means to configure what elements of information+{- | 'ErrorDetailLevel' provides a means to configure what elements of information   are included in error messages that originate from decoding rows queried   from the database. This can be specified either by manually rendering the   error message and providing the desired configuration, or by setting the@@ -40,8 +39,7 @@       Show     ) -{- |-  A minimal 'ErrorDetailLevel' where all information (including any+{- | A minimal 'ErrorDetailLevel' where all information (including any   situationally-specific error messages!) is redacted from error messages.  @since 1.0.0.0@@ -55,8 +53,7 @@     , includeNonIdentifierValues = False     } -{- |-  A default 'ErrorDetailLevel' that strikes a balance of including all "Generic"+{- | A default 'ErrorDetailLevel' that strikes a balance of including all "Generic"   information such as the error message, schema names and row identifiers, but   avoids unintentionally leaking non-identifier values from the database by   redacting them.@@ -72,8 +69,7 @@     , includeNonIdentifierValues = False     } -{- |-  A maximal 'ErrorDetailLevel' that redacts no information from the error+{- | A maximal 'ErrorDetailLevel' that redacts no information from the error   messages. Error messages will include values from the database for any   columns that are involved in a decoding failure, including some which you may   not have intended to expose through error messages. Use with caution.@@ -89,8 +85,7 @@     , includeNonIdentifierValues = True     } -{- |-  Redacts given the error message string if the 'ErrorDetailLevel' indicates+{- | Redacts given the error message string if the 'ErrorDetailLevel' indicates   that error messages should be redacted.  @since 1.0.0.0@@ -101,8 +96,7 @@     then message     else redactedValue -{- |-  Redacts given the schema name string if the 'ErrorDetailLevel' indicates+{- | Redacts given the schema name string if the 'ErrorDetailLevel' indicates   that schema names should be redacted.  @since 1.0.0.0@@ -113,8 +107,7 @@     then schemaName     else redactedValue -{- |-  Redacts given the identifier value string if the 'ErrorDetailLevel' indicates+{- | Redacts given the identifier value string if the 'ErrorDetailLevel' indicates   that identifier values should be redacted.  @since 1.0.0.0@@ -125,11 +118,10 @@     then idValue     else redactedValue -{- |-  Redacts given the non-identifier value string if the 'ErrorDetailLevel' indicates+{- | Redacts given the non-identifier value string if the 'ErrorDetailLevel' indicates   that non-identifier values should be redacted. -@since 1.0.0.0+  @since 1.0.0.0 -} redactNonIdentifierValue :: ErrorDetailLevel -> String -> String redactNonIdentifierValue detailLevel nonIdValue =
src/Orville/PostgreSQL/Execution/Cursor.hs view
@@ -51,8 +51,7 @@ import Orville.PostgreSQL.Marshall (AnnotatedSqlMarshaller) import qualified Orville.PostgreSQL.Monad as Monad -{- |-  A 'Cursor' allows you to fetch rows incrementally from PostgreSQL. Using+{- | A 'Cursor' allows you to fetch rows incrementally from PostgreSQL. Using   a cursor will allow you to execute a query that returns a very large   result set without the entire result set being loaded in memory in your   application and instead pulling rows as you're able to process them.@@ -68,8 +67,7 @@     Expr.CursorName ->     Cursor readEntity -{- |-  Declares a @CURSOR@ in PostgreSQL that is available for the duration of the+{- | Declares a @CURSOR@ in PostgreSQL that is available for the duration of the   action passed to 'withCursor' and closes the cursor when that action   completes (or raises an exception). @@ -90,14 +88,12 @@   Select readEntity ->   (Cursor readEntity -> m a) ->   m a-withCursor scrollExpr holdExpr select useCursor =+withCursor scrollExpr holdExpr select =   Bracket.bracketWithResult     (declareCursor scrollExpr holdExpr select)     (\cursor _bracketResult -> closeCursor cursor)-    useCursor -{- |-  Declares a @CURSOR@ in PostgreSQL and returns it for you to use. The cursor+{- | Declares a @CURSOR@ in PostgreSQL and returns it for you to use. The cursor   must be closed via 'closeCursor' (or another means) when you are done using   it. Generally you should use 'withCursor' instead of 'declareCursor' to   ensure that the cursor gets closed properly.@@ -125,8 +121,7 @@     _ <- Execute.executeVoid QueryType.CursorQuery declareExpr     pure (Cursor marshaller cursorName) -{- |-  Closes a @CURSOR@ in PostgreSQL that was previously declared.+{- | Closes a @CURSOR@ in PostgreSQL that was previously declared.   This should be used to close any cursors you open via 'declareCursor',   though we recommend you use 'withCursor' instead to ensure that any   opened cursors are closed in the event of an exception.@@ -143,8 +138,7 @@     . Right     $ cursorName -{- |-  Fetch rows from a cursor according to the 'Expr.CursorDirection' given. See+{- | Fetch rows from a cursor according to the 'Expr.CursorDirection' given. See   @https://www.postgresql.org/docs/current/sql-fetch.html@ for details about   the effects of fetch and the meanings of cursor directions to PostgreSQL. @@ -161,8 +155,7 @@     (Expr.fetch direction cursorName)     marshaller -{- |-  Moves a cursor according to the 'Expr.CursorDirection' given. See+{- | Moves a cursor according to the 'Expr.CursorDirection' given. See   @https://www.postgresql.org/docs/current/sql-fetch.html@ for details about   the effect of move and the meanings of cursor directions to PostgreSQL. @@ -178,8 +171,7 @@     QueryType.CursorQuery     (Expr.move direction cursorName) -{- |-  INTERNAL - Generates a unique (or very nearly guaranteed to be) cursor name.+{- | INTERNAL - Generates a unique (or very nearly guaranteed to be) cursor name.   Cursor names only need to be unique among the currently-open cursors on the   current connection, so using POSIX time plus a 32-bit random tag should be   more than sufficient to ensure conflicts are not seen in practice.
src/Orville/PostgreSQL/Execution/Delete.hs view
@@ -35,8 +35,7 @@ import qualified Orville.PostgreSQL.Monad as Monad import Orville.PostgreSQL.Schema (TableDefinition, mkTableReturningClause, tableMarshaller, tableName) -{- |-Represents a @DELETE@ statement that can be executed against a database. A+{- | Represents a @DELETE@ statement that can be executed against a database. A 'Delete' has a 'Orville.PostgreSQL.SqlMarshaller' bound to it that, when the delete returns data from the database, will be used to decode the database result set when it is executed.@@ -49,8 +48,7 @@     Delete readEntity NoReturningClause   DeleteReturning :: AnnotatedSqlMarshaller writeEntity readEntity -> Expr.DeleteExpr -> Delete readEntity ReturningClause -{- |-  Extracts the query that will be run when the delete is executed. Normally you+{- | Extracts the query that will be run when the delete is executed. Normally you   don't want to extract the query and run it yourself, but this function is   useful to view the query for debugging or query explanation. @@ -60,8 +58,7 @@ deleteFromDeleteExpr (Delete expr) = expr deleteFromDeleteExpr (DeleteReturning _ expr) = expr -{- |-  Executes the database query for the 'Delete' and returns the number of+{- | Executes the database query for the 'Delete' and returns the number of   rows affected by the query.  @since 1.0.0.0@@ -73,8 +70,7 @@ executeDelete (Delete expr) =   Execute.executeAndReturnAffectedRows QueryType.DeleteQuery expr -{- |-Executes the database query for the 'Delete' and uses its+{- | Executes the database query for the 'Delete' and uses its 'Orville.PostgreSQL.SqlMarshaller' to decode the rows (that were just deleted) as returned via a RETURNING clause. @@ -87,8 +83,7 @@ executeDeleteReturnEntities (DeleteReturning marshaller expr) =   Execute.executeAndDecode QueryType.DeleteQuery expr marshaller -{- |-  Builds a 'Delete' that will delete all of the writable columns described in the+{- | Builds a 'Delete' that will delete all of the writable columns described in the   'TableDefinition' without returning the data as seen by the database.  @since 1.0.0.0@@ -100,8 +95,7 @@ deleteFromTable =   deleteTable WithoutReturning -{- |-  Builds a 'Delete' that will delete all of the writable columns described in the+{- | Builds a 'Delete' that will delete all of the writable columns described in the   'TableDefinition' and return the data as seen by the database. This is useful for getting   database-managed columns such as auto-incrementing identifiers and sequences. @@ -130,8 +124,7 @@   in     rawDeleteExpr returningOption (tableMarshaller tableDef) deleteExpr -{- |-  Builds a 'Delete' that will execute the specified query and use the given+{- | Builds a 'Delete' that will execute the specified query and use the given   'Orville.PostgreSQL.SqlMarshaller' to decode it. It is up to the caller to   ensure that the given 'Expr.DeleteExpr' makes sense and returns a result that   the 'Orville.PostgreSQL.SqlMarshaller' can decode.
src/Orville/PostgreSQL/Execution/EntityOperations.hs view
@@ -51,8 +51,7 @@ import qualified Orville.PostgreSQL.Monad as Monad import qualified Orville.PostgreSQL.Schema as Schema -{- |-  Inserts an entity into the specified table.+{- | Inserts an entity into the specified table.  @since 1.0.0.0 -}@@ -64,8 +63,7 @@ insertEntity entityTable =   Monad.void . insertEntityAndReturnRowCount entityTable -{- |-  Inserts an entity into the specified table. Returns the number of rows+{- | Inserts an entity into the specified table. Returns the number of rows   affected by the query.  @since 1.0.0.0@@ -78,8 +76,7 @@ insertEntityAndReturnRowCount entityTable entity =   insertEntitiesAndReturnRowCount entityTable (entity :| []) -{- |-  Inserts an entity into the specified table, returning the data inserted into+{- | Inserts an entity into the specified table, returning the data inserted into   the database.    You can use this function to obtain any column values filled in by the@@ -99,8 +96,7 @@     "insertAndReturnEntity RETURNING clause"     returnedEntities -{- |-  Inserts a non-empty list of entities into the specified table.+{- | Inserts a non-empty list of entities into the specified table.  @since 1.0.0.0 -}@@ -112,8 +108,7 @@ insertEntities tableDef =   Monad.void . insertEntitiesAndReturnRowCount tableDef -{- |-  Inserts a non-empty list of entities into the specified table. Returns the+{- | Inserts a non-empty list of entities into the specified table. Returns the   number of rows affected by the query.  @since 1.0.0.0@@ -126,8 +121,7 @@ insertEntitiesAndReturnRowCount tableDef =   Insert.executeInsert . Insert.insertToTable tableDef -{- |-  Inserts a non-empty list of entities into the specified table, returning the data that+{- | Inserts a non-empty list of entities into the specified table, returning the data that   was inserted into the database.    You can use this function to obtain any column values filled in by the@@ -143,8 +137,7 @@ insertAndReturnEntities tableDef =   Insert.executeInsertReturnEntities . Insert.insertToTableReturning tableDef -{- |-  Updates the row with the given key with the data given by @writeEntity@.+{- | Updates the row with the given key with the data given by @writeEntity@.  @since 1.0.0.0 -}@@ -157,8 +150,7 @@ updateEntity tableDef key =   Monad.void . updateEntityAndReturnRowCount tableDef key -{- |-  Updates the row with the given key with the data given by @writeEntity@.+{- | Updates the row with the given key with the data given by @writeEntity@.   Returns the number of rows affected by the query.  @since 1.0.0.0@@ -176,8 +168,7 @@     Just update ->       Update.executeUpdate update -{- |-  Updates the row with the given key with the data given by @writeEntity@,+{- | Updates the row with the given key with the data given by @writeEntity@,   returning the updated row from the database. If no row matches the given key,   'Nothing' will be returned. @@ -202,8 +193,7 @@         "updateAndReturnEntity RETURNING clause"         returnedEntities -{- |-  Applies the given 'Expr.SetClause's to the rows in the table that match the+{- | Applies the given 'Expr.SetClause's to the rows in the table that match the   given where condition. The easiest way to construct a 'Expr.SetClause' is   via the 'Orville.Postgresql.setField' function (also exported as @.:=@). @@ -218,8 +208,7 @@ updateFields tableDef setClauses =   Monad.void . updateFieldsAndReturnRowCount tableDef setClauses -{- |-  Applies the given 'Expr.SetClause's to the rows in the table that match the+{- | Applies the given 'Expr.SetClause's to the rows in the table that match the   given where condition. The easiest way to construct a 'Expr.SetClause' is   via the 'Orville.Postgresql.setField' function (also exported as @.:=@).   Returns the number of rows affected by the query.@@ -236,8 +225,7 @@   Update.executeUpdate $     Update.updateToTableFields tableDef setClauses mbWhereCondition -{- |-  Like 'updateFields', but uses a @RETURNING@ clause to return the updated+{- | Like 'updateFields', but uses a @RETURNING@ clause to return the updated   version of any rows that were affected by the update.  @since 1.0.0.0@@ -252,8 +240,7 @@   Update.executeUpdateReturnEntities $     Update.updateToTableFieldsReturning tableDef setClauses mbWhereCondition -{- |-  Deletes the row with the given key.+{- | Deletes the row with the given key.  @since 1.0.0.0 -}@@ -265,8 +252,7 @@ deleteEntity entityTable =   Monad.void . deleteEntityAndReturnRowCount entityTable -{- |-  Deletes the row with the given key. Returns the number of rows affected+{- | Deletes the row with the given key. Returns the number of rows affected   by the query.  @since 1.0.0.0@@ -286,8 +272,7 @@     Delete.executeDelete $       Delete.deleteFromTable entityTable (Just primaryKeyCondition) -{- |-  Deletes the row with the given key, returning the row that was deleted.+{- | Deletes the row with the given key, returning the row that was deleted.   If no row matches the given key, 'Nothing' is returned.  @since 1.0.0.0@@ -310,8 +295,7 @@     "deleteAndReturnEntity RETURNING clause"     returnedEntities -{- |-  Deletes all rows in the given table that match the where condition.+{- | Deletes all rows in the given table that match the where condition.  @since 1.0.0.0 -}@@ -323,8 +307,7 @@ deleteEntities entityTable =   Monad.void . deleteEntitiesAndReturnRowCount entityTable -{- |-  Deletes all rows in the given table that match the where condition. Returns+{- | Deletes all rows in the given table that match the where condition. Returns   the number of rows affected by the query.  @since 1.0.0.0@@ -338,8 +321,7 @@   Delete.executeDelete $     Delete.deleteFromTable entityTable whereCondition -{- |-  Deletes all rows in the given table that match the where condition, returning+{- | Deletes all rows in the given table that match the where condition, returning   the rows that were deleted.  @since 1.0.0.0@@ -353,8 +335,7 @@   Delete.executeDeleteReturnEntities $     Delete.deleteFromTableReturning entityTable whereCondition -{- |-  Finds all the entities in the given table according to the specified+{- | Finds all the entities in the given table according to the specified   'SelectOptions.SelectOptions', which may include where conditions to   match, ordering specifications, etc. @@ -369,8 +350,7 @@   Select.executeSelect $     Select.selectTable entityTable selectOptions -{- |-  Like 'findEntitiesBy', but adds a 'LIMIT 1' to the query and then returns+{- | Like 'findEntitiesBy', but adds a 'LIMIT 1' to the query and then returns   the first item from the list. Usually when you use this you will want to   provide an order by clause in the 'SelectOptions.SelectOptions' because the   database will not guarantee ordering.@@ -386,8 +366,7 @@   listToMaybe     <$> findEntitiesBy entityTable (SelectOptions.limit 1 <> selectOptions) -{- |-  Finds a single entity by the table's primary key value.+{- | Finds a single entity by the table's primary key value.  @since 1.0.0.0 -}@@ -405,8 +384,7 @@   in     findFirstEntityBy entityTable (SelectOptions.where_ primaryKeyCondition) -{- |-  Finds multiple entities by the table's primary key.+{- | Finds multiple entities by the table's primary key.  @since 1.0.0.0 -}@@ -424,8 +402,7 @@   in     findEntitiesBy entityTable (SelectOptions.where_ primaryKeyCondition) -{- |-  Thrown by 'updateFields' and 'updateFieldsAndReturnEntities' if the+{- | Thrown by 'updateFields' and 'updateFieldsAndReturnEntities' if the   'Schema.TableDefinition' they are given has no columns to update.  @since 1.0.0.0
src/Orville/PostgreSQL/Execution/Execute.hs view
@@ -32,8 +32,7 @@ import qualified Orville.PostgreSQL.Raw.SqlCommenter as SqlCommenter import qualified Orville.PostgreSQL.Raw.SqlValue as SqlValue -{- |-  Executes a SQL query and decodes the result set using the provided+{- | Executes a SQL query and decodes the result set using the provided   marshaller. Any SQL Execution callbacks that have been added to the   'OrvilleState' will be called. @@ -52,8 +51,7 @@   orvilleState <- askOrvilleState   withConnection (liftIO . executeAndDecodeIO queryType sql marshaller orvilleState) -{- |-  Executes a SQL query and returns the number of rows affected by the query.+{- | Executes a SQL query and returns the number of rows affected by the query.   Any SQL Execution callbacks that have been added to the 'OrvilleState' will   be called. @@ -76,8 +74,7 @@   orvilleState <- askOrvilleState   withConnection (liftIO . executeAndReturnAffectedRowsIO queryType sql orvilleState) -{- |-  Executes a SQL query and ignores the result. Any SQL Execution callbacks+{- | Executes a SQL query and ignores the result. Any SQL Execution callbacks   that have been added to the 'OrvilleState' will be called.    If the query fails an exception will be raised.@@ -93,8 +90,7 @@   orvilleState <- askOrvilleState   withConnection (liftIO . executeVoidIO queryType sql orvilleState) -{- |-  Executes a SQL query and decodes the result set using the provided+{- | Executes a SQL query and decodes the result set using the provided   marshaller. Any SQL Execution callbacks that have been added to the   'OrvilleState' will be called. @@ -129,8 +125,7 @@     Right entities ->       pure entities -{- |-  Executes a SQL query and returns the number of rows affected by the query.+{- | Executes a SQL query and returns the number of rows affected by the query.   Any SQL Execution callbacks that have been added to the 'OrvilleState' will   be called. @@ -166,8 +161,7 @@         Right n ->           pure n -{- |-  Executes a SQL query and ignores the result. Any SQL Execution callbacks+{- | Executes a SQL query and ignores the result. Any SQL Execution callbacks   that have been added to the 'OrvilleState' will be called.    If the query fails, an exception will be raised.@@ -206,8 +200,7 @@       rawSql       (RawSql.execute conn rawSql) -{- |-  Thrown by 'executeAndReturnAffectedRows' and 'executeAndReturnAffectedRowsIO'+{- | Thrown by 'executeAndReturnAffectedRows' and 'executeAndReturnAffectedRowsIO'   if the number of affected rows cannot be successfully read from the LibPQ   command result. 
src/Orville/PostgreSQL/Execution/ExecutionResult.hs view
@@ -1,7 +1,7 @@ {-# LANGUAGE GeneralizedNewtypeDeriving #-}  {- |-Copyright : Flipstone Technology Partners 2023+Copyright : Flipstone Technology Partners 2023-2025 License   : MIT Stability : Stable @@ -25,8 +25,7 @@ import Orville.PostgreSQL.Raw.SqlValue (SqlValue) import qualified Orville.PostgreSQL.Raw.SqlValue as SqlValue -{- |-  A trivial wrapper for `Int` to help keep track of column vs row number.+{- | A trivial wrapper for `Int` to help keep track of column vs row number.  @since 1.0.0.0 -}@@ -43,8 +42,7 @@       Num     ) -{- |-  A trivial wrapper for `Int` to help keep track of column vs row number.+{- | A trivial wrapper for `Int` to help keep track of column vs row number.  @since 1.0.0.0 -}@@ -61,8 +59,7 @@       Num     ) -{- |-  'ExecutionResult' is a common interface for types that represent a result set+{- | 'ExecutionResult' is a common interface for types that represent a result set   returned from the database. For real, live database interactions, the   concrete type will be a 'LibPQ.Result', but the 'FakeLibPQResult' may be   useful as well if you are writing custom code for decoding result sets and@@ -76,8 +73,7 @@   columnName :: result -> Column -> IO (Maybe BS.ByteString)   getValue :: result -> Row -> Column -> IO SqlValue -{- |-  Reads the rows of an 'ExecutionResult' as a list of column name, 'SqlValue'+{- | Reads the rows of an 'ExecutionResult' as a list of column name, 'SqlValue'   pairs. You're almost always better off using a   'Orville.PostgreSQL.SqlMarshaller' instead, but this function is provided for   cases where you really want to decode the rows yourself but don't want to use@@ -107,7 +103,7 @@     readValue rowIndex columnIndex = do       name <- columnName res columnIndex       value <- getValue res rowIndex columnIndex-      pure $ (name, value)+      pure (name, value)      readRow rowIndex =       traverse (readValue rowIndex) columnIndices@@ -140,8 +136,7 @@     SqlValue.fromRawBytesNullable       <$> LibPQ.getvalue' result (LibPQ.toRow row) (LibPQ.toColumn column) -{- |-  `FakeLibPQResult` provides a fake, in-memory implementation of+{- | `FakeLibPQResult` provides a fake, in-memory implementation of   `ExecutionResult`.  This is mostly useful for writing automated tests that   can assume a result set has been loaded and you just need to test decoding   the results.@@ -160,8 +155,7 @@   columnName result = pure . fakeLibPQColumnName result   getValue result column = pure . fakeLibPQGetValue result column -{- |-  Constructs a `FakeLibPQResult`. The column names given are associated with+{- | Constructs a `FakeLibPQResult`. The column names given are associated with   the values for each row by their position in-list. Any missing values (e.g.   because a row is shorter than the header list) are treated as a SQL Null   value.@@ -197,7 +191,7 @@ fakeLibPQMaxColumn result =   let     maxColumnsByRow =-      map fst+      fmap fst         . Maybe.mapMaybe Map.lookupMax         . Map.elems         . fakeLibPQRows@@ -209,7 +203,7 @@       _ ->         Just (maximum maxColumnsByRow) -fakeLibPQColumnName :: FakeLibPQResult -> Column -> (Maybe BS.ByteString)+fakeLibPQColumnName :: FakeLibPQResult -> Column -> Maybe BS.ByteString fakeLibPQColumnName result column =   Map.lookup column (fakeLibPQColumns result) 
src/Orville/PostgreSQL/Execution/Insert.hs view
@@ -38,8 +38,7 @@ import qualified Orville.PostgreSQL.Monad as Monad import Orville.PostgreSQL.Schema (TableDefinition, mkInsertExpr, tableMarshaller) -{- |-Represents an @INSERT@ statement that can be executed against a database. An+{- | Represents an @INSERT@ statement that can be executed against a database. An 'Insert' has a 'Orville.PostgreSQL.SqlMarshaller' bound to it that, when the insert returns data from the database, will be used to decode the database result set when it is executed.@@ -53,8 +52,7 @@     Insert readEntity NoReturningClause   InsertReturning :: AnnotatedSqlMarshaller writeEntity readEntity -> Expr.InsertExpr -> Insert readEntity ReturningClause -{- |-  Extracts the query that will be run when the insert is executed. Normally you+{- | Extracts the query that will be run when the insert is executed. Normally you   don't want to extract the query and run it yourself, but this function is   useful to view the query for debugging or query explanation. @@ -64,8 +62,7 @@ insertToInsertExpr (Insert _ expr) = expr insertToInsertExpr (InsertReturning _ expr) = expr -{- |-  Executes the database query for the 'Insert' and returns the number of rows+{- | Executes the database query for the 'Insert' and returns the number of rows   affected by the query.  @since 1.0.0.0@@ -77,8 +74,7 @@ executeInsert (Insert _ expr) =   Execute.executeAndReturnAffectedRows QueryType.InsertQuery expr -{- |-Executes the database query for the 'Insert' and uses its+{- | Executes the database query for the 'Insert' and uses its 'Orville.PostgreSQL.SqlMarshaller' to decode the rows (that were just inserted) as returned via a RETURNING clause. @@ -91,8 +87,7 @@ executeInsertReturnEntities (InsertReturning marshaller expr) =   Execute.executeAndDecode QueryType.InsertQuery expr marshaller -{- |-  Builds an 'Insert' that will insert all of the writable columns described in the+{- | Builds an 'Insert' that will insert all of the writable columns described in the   'TableDefinition' without returning the data as seen by the database.  @since 1.0.0.0@@ -104,8 +99,7 @@ insertToTable =   insertTable WithoutReturning -{- |-  Builds an 'Insert' that will insert all of the writable columns described in the+{- | Builds an 'Insert' that will insert all of the writable columns described in the   'TableDefinition' and return the data as seen by the database. This is useful for getting   database-managed columns such as auto-incrementing identifiers and sequences. @@ -125,10 +119,9 @@   NonEmpty writeEntity ->   Insert readEntity returningClause insertTable returningOption tableDef entities =-  rawInsertExpr returningOption (tableMarshaller tableDef) (mkInsertExpr returningOption tableDef entities)+  rawInsertExpr returningOption (tableMarshaller tableDef) (mkInsertExpr returningOption tableDef Nothing entities) -{- |-  Builds an 'Insert' that will execute the specified query and use the given+{- | Builds an 'Insert' that will execute the specified query and use the given   'Orville.PostgreSQL.SqlMarshaller' to decode it. It is up to the caller to   ensure that the given 'Expr.InsertExpr' makes sense and produces a value that   can be stored, as well as returning a result that the
src/Orville/PostgreSQL/Execution/QueryType.hs view
@@ -10,8 +10,7 @@   ) where -{- |-  A simple categorization of SQL queries that is used to provide a hint to+{- | A simple categorization of SQL queries that is used to provide a hint to   user callbacks about what kind of query is being run.    See 'Orville.PostgreSQL.addSqlExecutionCallback'
src/Orville/PostgreSQL/Execution/ReturningOption.hs view
@@ -26,10 +26,10 @@ -} data NoReturningClause -{- |-  Specifies whether or not a @RETURNING@ clause should be included when a+{- | Specifies whether or not a @RETURNING@ clause should be included when a   query expression is built. This type is found as a parameter on a number   of the query-building functions related to 'Orville.PostgreSQL.TableDefinition'.+ @since 1.0.0.0 -} data ReturningOption clause where
src/Orville/PostgreSQL/Execution/Select.hs view
@@ -2,7 +2,7 @@ {-# LANGUAGE RankNTypes #-}  {- |-Copyright : Flipstone Technology Partners 2023+Copyright : Flipstone Technology Partners 2023-2025 License   : MIT Stability : Stable @@ -35,8 +35,7 @@ import qualified Orville.PostgreSQL.Monad as Monad import Orville.PostgreSQL.Schema (TableDefinition, tableMarshaller, tableName) -{- |-  Represents a @SELECT@ statement that can be executed against a database. A+{- | Represents a @SELECT@ statement that can be executed against a database. A   'Select' has a 'Orville.PostgreSQL.SqlMarshaller' bound to it that will be   used to decode the database result set when it is executed. @@ -45,8 +44,7 @@ data Select readEntity where   Select :: AnnotatedSqlMarshaller writeEntity readEntity -> Expr.QueryExpr -> Select readEntity -{- |-  Extracts the query that will be run when the select is executed. Normally you+{- | Extracts the query that will be run when the select is executed. Normally you   don't want to extract the query and run it yourself, but this function is   useful to view the query for debugging or query explanation. @@ -55,8 +53,7 @@ selectToQueryExpr :: Select readEntity -> Expr.QueryExpr selectToQueryExpr (Select _ query) = query -{- |-  Executes the database query for the 'Select' and uses its+{- | Executes the database query for the 'Select' and uses its   'Orville.PostgreSQL.SqlMarshaller' to decode the result set.  @since 1.0.0.0@@ -65,8 +62,7 @@ executeSelect =   useSelect (Execute.executeAndDecode QueryType.SelectQuery) -{- |-  Runs a function allowing it to use the data elements containted within the+{- | Runs a function allowing it to use the data elements containted within the   'Select' it pleases. The marshaller that the function is provided can be use   to decode results from the query. @@ -83,8 +79,7 @@ useSelect f (Select marshaller query) =   f query marshaller -{- |-  Builds a 'Select' that will select all the columns described in the+{- | Builds a 'Select' that will select all the columns described in the   'TableDefinition'. This is the safest way to build a 'Select', because table   name and columns are all read from the 'TableDefinition'. If the table is   being managed with Orville auto-migrations, this will match the schema in the@@ -99,8 +94,7 @@ selectTable tableDef =   selectMarshalledColumns (tableMarshaller tableDef) (tableName tableDef) -{- |-  Builds a 'Select' that will select the columns described by the marshaller+{- | Builds a 'Select' that will select the columns described by the marshaller   from the specified table. It is up to the caller to ensure that the columns   in the marshaller make sense for the table. @@ -111,18 +105,17 @@ -} selectMarshalledColumns ::   AnnotatedSqlMarshaller writeEntity readEntity ->-  Expr.Qualified Expr.TableName ->+  Expr.QualifiedOrUnqualified Expr.TableName ->   SelectOptions.SelectOptions ->   Select readEntity selectMarshalledColumns marshaller qualifiedTableName selectOptions =   rawSelectQueryExpr marshaller $     SelectOptions.selectOptionsQueryExpr       (Expr.selectDerivedColumns (marshallerDerivedColumns . unannotatedSqlMarshaller $ marshaller))-      (Expr.referencesTable qualifiedTableName)+      (Expr.singleTableReferenceList qualifiedTableName)       selectOptions -{- |-  Builds a 'Select' that will execute the specified query and use the given+{- | Builds a 'Select' that will execute the specified query and use the given   'Orville.PostgreSQL.SqlMarshaller' to decode it. It is up to the caller to   ensure that the given 'Expr.QueryExpr' makes sense and produces a result set   that the 'Orville.PostgreSQL.SqlMarshaller' can decode.
src/Orville/PostgreSQL/Execution/SelectOptions.hs view
@@ -1,5 +1,5 @@ {- |-Copyright : Flipstone Technology Partners 2023+Copyright : Flipstone Technology Partners 2023-2025 License   : MIT Stability : Stable @@ -15,12 +15,18 @@   , selectGroupByClause   , selectLimitExpr   , selectOffsetExpr+  , selectRowLockingClause+  , selectWindowClause+  , selectFetchClause   , distinct   , where_   , orderBy   , limit   , offset   , groupBy+  , forRowLock+  , window+  , fetchRow   , selectOptionsQueryExpr   ) where@@ -29,8 +35,7 @@  import qualified Orville.PostgreSQL.Expr as Expr -{- |-   A 'SelectOptions' is a set of options that can be used to change the way+{- |  A 'SelectOptions' is a set of options that can be used to change the way    a basic query function works by adding @WHERE@, @ORDER BY@, @GROUP BY@, etc.    Functions are provided to construct 'SelectOptions' for individual options,    which may then be combined via '<>' (also exposed as 'appendSelectOptions').@@ -44,6 +49,9 @@   , i_limitExpr :: First Expr.LimitExpr   , i_offsetExpr :: First Expr.OffsetExpr   , i_groupByExpr :: Maybe Expr.GroupByExpr+  , i_rowLockingClause :: First Expr.RowLockingClause+  , i_windowExpr :: Maybe Expr.NamedWindowDefinitionExpr+  , i_fetchClause :: First Expr.FetchClause   }  -- | @since 1.0.0.0@@ -54,8 +62,7 @@ instance Monoid SelectOptions where   mempty = emptySelectOptions -{- |-  A set of empty 'SelectOptions' that will not change how a query is run.+{- | A set of empty 'SelectOptions' that will not change how a query is run.  @since 1.0.0.0 -}@@ -68,10 +75,12 @@     , i_limitExpr = mempty     , i_offsetExpr = mempty     , i_groupByExpr = mempty+    , i_rowLockingClause = mempty+    , i_windowExpr = Nothing+    , i_fetchClause = mempty     } -{- |-  Combines multple select options together, unioning the options together where+{- | Combines multple select options together, unioning the options together where   possible. For options where this is not possible (e.g. @LIMIT@), the one   on the left is preferred. @@ -86,6 +95,9 @@     (i_limitExpr left <> i_limitExpr right)     (i_offsetExpr left <> i_offsetExpr right)     (i_groupByExpr left <> i_groupByExpr right)+    (i_rowLockingClause left <> i_rowLockingClause right)+    (i_windowExpr left <> i_windowExpr right)+    (i_fetchClause left <> i_fetchClause right)  unionMaybeWith :: (a -> a -> a) -> Maybe a -> Maybe a -> Maybe a unionMaybeWith f mbLeft mbRight =@@ -95,8 +107,7 @@     (Nothing, Just right) -> Just right     (Just left, Just right) -> Just (f left right) -{- |-  Builds the 'Expr.SelectClause' that should be used to include the+{- | Builds the 'Expr.SelectClause' that should be used to include the   'distinct's from the 'SelectOptions' on a query.  @since 1.0.0.0@@ -107,8 +118,7 @@     First (Just True) -> Expr.selectClause . Expr.selectExpr $ Just Expr.Distinct     _ -> Expr.selectClause $ Expr.selectExpr Nothing -{- |-  Builds the 'Expr.WhereClause' that should be used to include the+{- | Builds the 'Expr.WhereClause' that should be used to include the   'Expr.BooleanExpr's from the 'SelectOptions' on a query. This will be 'Nothing'   when no 'Expr.BooleanExpr's have been specified. @@ -118,8 +128,7 @@ selectWhereClause =   fmap Expr.whereClause . i_whereCondition -{- |-  Constructs a 'SelectOptions' with just 'distinct' set to 'True'.+{- | Constructs a 'SelectOptions' with just 'distinct' set to 'True'.  @since 1.0.0.0 -}@@ -129,8 +138,7 @@     { i_distinct = First $ Just True     } -{- |-  Builds the 'Expr.OrderByClause' that should be used to include the+{- | Builds the 'Expr.OrderByClause' that should be used to include the   'Expr.OrderByClause's from the 'SelectOptions' on a query. This will be   'Nothing' when no 'Expr.OrderByClause's have been specified. @@ -140,8 +148,7 @@ selectOrderByClause =   fmap Expr.orderByClause . i_orderBy -{- |-  Builds the 'Expr.GroupByClause' that should be used to include the+{- | Builds the 'Expr.GroupByClause' that should be used to include the   'Expr.GroupByClause's from the 'SelectOptions' on a query. This will be   'Nothing' when no 'Expr.GroupByClause's have been specified. @@ -151,8 +158,7 @@ selectGroupByClause =   fmap Expr.groupByClause . i_groupByExpr -{- |-  Builds a 'Expr.LimitExpr' that will limit the query results to the+{- | Builds a 'Expr.LimitExpr' that will limit the query results to the   number specified in the 'SelectOptions' (if any).  @since 1.0.0.0@@ -161,8 +167,7 @@ selectLimitExpr =   getFirst . i_limitExpr -{- |-  Builds an 'Expr.OffsetExpr' that will limit the query results to the+{- | Builds an 'Expr.OffsetExpr' that will limit the query results to the   number specified in the 'SelectOptions' (if any).  @since 1.0.0.0@@ -171,9 +176,32 @@ selectOffsetExpr =   getFirst . i_offsetExpr -{- |-  Constructs a 'SelectOptions' with just the given 'Expr.BooleanExpr'.+{- | Builds an 'Expr.RowLockingClause' that will apply the locking rules specified in the 'SelectOptions' (if any). +@since 1.1.0.0+-}+selectRowLockingClause :: SelectOptions -> Maybe Expr.RowLockingClause+selectRowLockingClause =+  getFirst . i_rowLockingClause++{- | Builds an 'Expr.WindowClause' that will apply the windowing rules specified in the 'SelectOptions' (if any).++@since 1.1.0.0+-}+selectWindowClause :: SelectOptions -> Maybe Expr.WindowClause+selectWindowClause =+  fmap Expr.windowClause . i_windowExpr++{- | Builds an 'Expr.FetchClause' that will apply the fetching rules specified in the 'SelectOptions' (if any).++@since 1.1.0.0+-}+selectFetchClause :: SelectOptions -> Maybe Expr.FetchClause+selectFetchClause =+  getFirst . i_fetchClause++{- | Constructs a 'SelectOptions' with just the given 'Expr.BooleanExpr'.+ @since 1.0.0.0 -} where_ :: Expr.BooleanExpr -> SelectOptions@@ -182,8 +210,7 @@     { i_whereCondition = Just condition     } -{- |-  Constructs a 'SelectOptions' with just the given 'Expr.OrderByExpr'.+{- | Constructs a 'SelectOptions' with just the given 'Expr.OrderByExpr'.  @since 1.0.0.0 -}@@ -193,8 +220,7 @@     { i_orderBy = Just order     } -{- |-  Constructs a 'SelectOptions' that will apply the given limit.+{- | Constructs a 'SelectOptions' that will apply the given limit.  @since 1.0.0.0 -}@@ -204,8 +230,7 @@     { i_limitExpr = First . Just . Expr.limitExpr $ limitValue     } -{- |-  Constructs a 'SelectOptions' that will apply the given offset.+{- | Constructs a 'SelectOptions' that will apply the given offset.  @since 1.0.0.0 -}@@ -215,8 +240,7 @@     { i_offsetExpr = First . Just . Expr.offsetExpr $ offsetValue     } -{- |-  Constructs a 'SelectOptions' with just the given 'Expr.GroupByClause'.+{- | Constructs a 'SelectOptions' with just the given 'Expr.GroupByExpr'.  @since 1.0.0.0 -}@@ -226,8 +250,37 @@     { i_groupByExpr = Just groupByExpr     } -{- |-  Builds a 'Expr.QueryExpr' that will use the specified 'Expr.SelectList' when+{- | Constructs a 'SelectOptions' with just the given 'Expr.RowLockingClause'.++@since 1.1.0.0+-}+forRowLock :: Expr.RowLockingClause -> SelectOptions+forRowLock rowLockingClause =+  emptySelectOptions+    { i_rowLockingClause = First $ Just rowLockingClause+    }++{- | Constructs a 'SelectOptions' with just the given 'Expr.NamedWindowDefinitionExpr'.++@since 1.1.0.0+-}+window :: Expr.NamedWindowDefinitionExpr -> SelectOptions+window namedWindow =+  emptySelectOptions+    { i_windowExpr = Just namedWindow+    }++{- | Constructs a 'SelectOptions' with just the given 'Expr.FetchClause'.++@since 1.1.0.0+-}+fetchRow :: Expr.FetchClause -> SelectOptions+fetchRow fetchClause =+  emptySelectOptions+    { i_fetchClause = First $ Just fetchClause+    }++{- | Builds a 'Expr.QueryExpr' that will use the specified 'Expr.SelectList' when   building the @SELECT@ statement to execute. It is up to the caller to make   sure that the 'Expr.SelectList' expression makes sense for the table being   queried, and that the names of the columns in the result set match those@@ -260,4 +313,7 @@           (selectOrderByClause selectOptions)           (selectLimitExpr selectOptions)           (selectOffsetExpr selectOptions)+          (selectRowLockingClause selectOptions)+          (selectWindowClause selectOptions)+          (selectFetchClause selectOptions)     )
src/Orville/PostgreSQL/Execution/Sequence.hs view
@@ -26,8 +26,7 @@ import qualified Orville.PostgreSQL.Monad as Monad import Orville.PostgreSQL.Schema (SequenceDefinition, sequenceName) -{- |-  Fetches the next value from a sequence via the PostgreSQL @nextval@ function.+{- | Fetches the next value from a sequence via the PostgreSQL @nextval@ function.  @since 1.0.0.0 -}@@ -37,8 +36,7 @@     "sequenceNextValue"     (Expr.nextVal (sequenceName sequenceDef)) -{- |-  Fetches the current value from a sequence via the PostgreSQL @currval@ function.+{- | Fetches the current value from a sequence via the PostgreSQL @currval@ function.  @since 1.0.0.0 -}@@ -48,8 +46,7 @@     "sequenceCurrentValue"     (Expr.currVal (sequenceName sequenceDef)) -{- |-  Sets the current value from a sequence via the PostgreSQL @setval@ function.+{- | Sets the current value from a sequence via the PostgreSQL @setval@ function.  @since 1.0.0.0 -}
src/Orville/PostgreSQL/Execution/Transaction.hs view
@@ -1,5 +1,5 @@ {- |-Copyright : Flipstone Technology Partners 2023+Copyright : Flipstone Technology Partners 2023-2025 License   : MIT Stability : Stable @@ -10,10 +10,18 @@ -} module Orville.PostgreSQL.Execution.Transaction   ( withTransaction+  , withTransactionInstruction+  , TransactionInstruction (Commit, Rollback)+  , inWithTransaction+  , InWithTransaction (InOutermostTransaction, InSavepointTransaction)+  , UnexpectedTransactionStatusError (..)   ) where -import Control.Monad.IO.Class (MonadIO, liftIO)+import Control.Exception (Exception, finally, onException, throwIO, try, uninterruptibleMask)+import Control.Monad.IO.Class (liftIO)+import qualified Database.PostgreSQL.LibPQ as LibPQ+import Numeric.Natural (Natural)  import qualified Orville.PostgreSQL.Execution.Execute as Execute import qualified Orville.PostgreSQL.Execution.QueryType as QueryType@@ -22,25 +30,57 @@ import qualified Orville.PostgreSQL.Internal.MonadOrville as MonadOrville import qualified Orville.PostgreSQL.Internal.OrvilleState as OrvilleState import qualified Orville.PostgreSQL.Monad as Monad+import qualified Orville.PostgreSQL.Raw.Connection as Connection import qualified Orville.PostgreSQL.Raw.RawSql as RawSql -{- |-  Performs an action in an Orville monad within a database transaction. The transaction+{- | Performs an action in an Orville monad within a database transaction. The transaction   is begun before the action is called. If the action completes without raising an exception,   the transaction will be committed. If the action raises an exception, the transaction will-  rollback.+  rollback. This is equivalent to calling 'withTransactionInstruction' and always returning+  'Commit' as the 'TransactionInstruction'.    This function is safe to call from within another transaction. When called this way, the   transaction will establish a new savepoint at the beginning of the nested transaction and   either release the savepoint or rollback to it as appropriate. +  See also: 'withTransactionInstruction'+   Note: Exceptions are handled using the implementations of 'Monad.catch' and   'Monad.mask' provided by the 'Monad.MonadOrvilleControl' instance for @m@.  @since 1.0.0.0 -} withTransaction :: Monad.MonadOrville m => m a -> m a-withTransaction action =+withTransaction =+  withTransactionInstruction . fmap (\a -> (a, Commit))++{- |+Used with 'withTransactionInstruction' to indicate whether the transaction should be+committed or rolled back.++@since 1.1.0.0+-}+data TransactionInstruction = Commit | Rollback++{- | Performs an action in an Orville monad within a database transaction. The transaction+  is begun before the action is called. If the action completes without raising an exception,+  the transaction will be it will either be commited or rolled back, depending on the+  'TransactionInstruction' returned by the action.. If the action raises an exception, the+  transaction will rollback.++  This function is safe to call from within another transaction. When called this way, the+  transaction will establish a new savepoint at the beginning of the nested transaction and+  either release the savepoint or rollback to it as appropriate.++  See also: 'withTransaction'++  Note: Exceptions are handled using the implementations of 'Monad.catch' and+  'Monad.mask' provided by the 'Monad.MonadOrvilleControl' instance for @m@.++@since 1.1.0.0+-}+withTransactionInstruction :: Monad.MonadOrville m => m (a, TransactionInstruction) -> m a+withTransactionInstruction action =   MonadOrville.withConnectedState $ \connectedState -> do     let       conn = OrvilleState.connectedConnection connectedState@@ -61,35 +101,116 @@       callback =         OrvilleState.orvilleTransactionCallback state +      beginTransaction :: IO ()       beginTransaction = do-        liftIO $ do-          let-            openEvent = OrvilleState.openTransactionEvent transaction-          executeTransactionSql (transactionEventSql state openEvent)-          callback openEvent+        status <- Connection.transactionStatusOrThrow conn+        let+          openEvent = OrvilleState.openTransactionEvent transaction+          beginAction = do+            executeTransactionSql (transactionEventSql state openEvent)+            callback openEvent+          transactionError = UnexpectedTransactionStatusError status openEvent+        case status of+          LibPQ.TransIdle -> case openEvent of+            OrvilleState.BeginTransaction -> do+              let+                execRollback :: (IO () -> IO a) -> IO a+                execRollback restore = do+                  executeTransactionSql (transactionEventSql state OrvilleState.RollbackTransaction)+                  restore $ callback OrvilleState.RollbackTransaction+                rollback = uninterruptibleMask $ \restore -> do+                  postBeginStatus <- Connection.transactionStatusOrThrow conn+                  case postBeginStatus of+                    LibPQ.TransInTrans -> execRollback restore+                    LibPQ.TransInError -> execRollback restore+                    LibPQ.TransActive -> execRollback restore+                    LibPQ.TransIdle -> pure ()+                    LibPQ.TransUnknown -> pure ()+              beginAction `onException` rollback+            _ ->+              throwIO transactionError+          LibPQ.TransInTrans -> case openEvent of+            OrvilleState.NewSavepoint _ ->+              beginAction+            _ ->+              throwIO transactionError+          LibPQ.TransActive ->+            throwIO transactionError+          LibPQ.TransInError ->+            throwIO transactionError+          LibPQ.TransUnknown ->+            throwIO transactionError        doAction () =         Monad.localOrvilleState           (OrvilleState.connectState innerConnectedState)           action -      finishTransaction :: MonadIO m => () -> Bracket.BracketResult -> m ()-      finishTransaction () result =-        liftIO $-          case result of-            Bracket.BracketSuccess -> do-              let-                successEvent = OrvilleState.transactionSuccessEvent transaction-              executeTransactionSql (transactionEventSql state successEvent)-              callback successEvent-            Bracket.BracketError -> do-              let-                rollbackEvent = OrvilleState.rollbackTransactionEvent transaction-              executeTransactionSql (transactionEventSql state rollbackEvent)-              callback rollbackEvent+      finishTransaction :: Bracket.BracketResult (a, TransactionInstruction) -> IO ()+      finishTransaction result = uninterruptibleMask $ \restore -> do+        status <- Connection.transactionStatusOrThrow conn+        let+          successEvent = OrvilleState.transactionSuccessEvent transaction+          rollbackEvent = OrvilleState.rollbackTransactionEvent transaction+          rollback = do+            executeTransactionSql (transactionEventSql state rollbackEvent)+            restore $ callback rollbackEvent+          transactionError = UnexpectedTransactionStatusError status $ case result of+            Bracket.BracketSuccess (_, Commit) -> successEvent+            Bracket.BracketSuccess (_, Rollback) -> rollbackEvent+            Bracket.BracketError -> rollbackEvent+        case status of+          LibPQ.TransInTrans -> case result of+            Bracket.BracketSuccess (_, Commit) -> do+              eSuccess <- try $ executeTransactionSql (transactionEventSql state successEvent)+              case eSuccess of+                Right () ->+                  restore $ callback successEvent+                Left ex -> do+                  restore (callback rollbackEvent) `finally` throwIO (ex :: Connection.SqlExecutionError)+            Bracket.BracketSuccess (_, Rollback) -> do+              rollback+            Bracket.BracketError ->+              rollback+          LibPQ.TransInError ->+            rollback+          LibPQ.TransActive ->+            throwIO transactionError+          LibPQ.TransIdle ->+            throwIO transactionError+          LibPQ.TransUnknown ->+            throwIO transactionError -    Bracket.bracketWithResult beginTransaction finishTransaction doAction+    fmap fst $+      Bracket.bracketWithResult+        (liftIO beginTransaction)+        (const $ liftIO . finishTransaction)+        doAction +{- | 'withTransaction' will throw this exception if libpq reports a transaction status on the underlying+  connection that is incompatible with the current transaction event.++@since 1.1.0.0+-}+data UnexpectedTransactionStatusError = UnexpectedTransactionStatusError+  { unexpectedTransactionStatusErrorTransactionStatus :: LibPQ.TransactionStatus+  , unexpectedTransactionStatusErrorTransactionEvent :: OrvilleState.TransactionEvent+  }++{- |++@since 1.1.0.0+-}+instance Show UnexpectedTransactionStatusError where+  show (UnexpectedTransactionStatusError status event) =+    "Unexpected transaction status during event " <> show event <> ": " <> show status++{- |++@since 1.1.0.0+-}+instance Exception UnexpectedTransactionStatusError+ transactionEventSql ::   OrvilleState.OrvilleState ->   OrvilleState.TransactionEvent ->@@ -101,16 +222,15 @@     OrvilleState.NewSavepoint savepoint ->       RawSql.toRawSql $ Expr.savepoint (savepointName savepoint)     OrvilleState.RollbackTransaction ->-      RawSql.toRawSql $ Expr.rollback+      RawSql.toRawSql Expr.rollback     OrvilleState.RollbackToSavepoint savepoint ->       RawSql.toRawSql $ Expr.rollbackTo (savepointName savepoint)     OrvilleState.CommitTransaction ->-      RawSql.toRawSql $ Expr.commit+      RawSql.toRawSql Expr.commit     OrvilleState.ReleaseSavepoint savepoint ->       RawSql.toRawSql $ Expr.releaseSavepoint (savepointName savepoint) -{- |-  INTERNAL: Constructs a savepoint name based on the current nesting level of+{- | INTERNAL: Constructs a savepoint name based on the current nesting level of   transactions, as tracked by the `OrvilleState.Savepoint` type. Strictly   speaking this is not necessary for PostgreSQL because it supports shadowing   savepoint names. The SQL standard doesn't allow for savepoint name shadowing,@@ -127,3 +247,43 @@     n = OrvilleState.savepointNestingLevel savepoint   in     Expr.savepointName ("orville_savepoint_level_" <> show n)++{- | Information about the current transaction state of an action passed to 'withTransaction'.++@since 1.1.0.0+-}+data InWithTransaction+  = InOutermostTransaction+  | -- | The 'Natural' indicates the savepoint depth, where @1@ is the first savepoint.+    InSavepointTransaction Natural+  deriving+    ( -- | @since 1.1.0.0+      Eq+    , -- | @since 1.1.0.0+      Ord+    , -- | @since 1.1.0.0+      Show+    )++{- |  Returns 'Just' an 'InWithTransaction' value when called inside of the action passed to+  'withTransaction', and 'Nothing' otherwise.++@since 1.1.0.0+-}+inWithTransaction :: MonadOrville.MonadOrville m => m (Maybe InWithTransaction)+inWithTransaction =+  fmap+    ( \state -> case OrvilleState.orvilleConnectionState state of+        OrvilleState.Connected connectedState ->+          fmap+            ( \transactionState -> case transactionState of+                OrvilleState.OutermostTransaction ->+                  InOutermostTransaction+                OrvilleState.SavepointTransaction i ->+                  InSavepointTransaction . fromIntegral $ OrvilleState.savepointNestingLevel i+            )+            (OrvilleState.connectedTransaction connectedState)+        OrvilleState.NotConnected ->+          Nothing+    )+    Monad.askOrvilleState
src/Orville/PostgreSQL/Execution/Update.hs view
@@ -39,8 +39,7 @@ import qualified Orville.PostgreSQL.Monad as Monad import Orville.PostgreSQL.Schema (HasKey, TableDefinition, mkTableReturningClause, primaryKeyEquals, tableMarshaller, tableName, tablePrimaryKey) -{- |-  Represents an @UPDATE@ statement that can be executed against a database. An+{- | Represents an @UPDATE@ statement that can be executed against a database. An   'Update' has a 'Orville.PostgreSQL.SqlMarshaller' bound to it that, when the   update returns data from the database, will be used to decode the database   result set when it is executed.@@ -51,8 +50,7 @@   UpdateNoReturning :: Expr.UpdateExpr -> Update readEntity NoReturningClause   UpdateReturning :: AnnotatedSqlMarshaller writeEntity readEntity -> Expr.UpdateExpr -> Update readEntity ReturningClause -{- |-  Extracts the query that will be run when the update is executed. Normally you+{- | Extracts the query that will be run when the update is executed. Normally you   don't want to extract the query and run it yourself, but this function is   useful to view the query for debugging or query explanation. @@ -62,8 +60,7 @@ updateToUpdateExpr (UpdateNoReturning expr) = expr updateToUpdateExpr (UpdateReturning _ expr) = expr -{- |-  Executes the database query for the 'Update' and returns the number of+{- | Executes the database query for the 'Update' and returns the number of   affected rows.  @since 1.0.0.0@@ -72,8 +69,7 @@ executeUpdate =   Execute.executeAndReturnAffectedRows QueryType.UpdateQuery . updateToUpdateExpr -{- |-  Executes the database query for the 'Update' and uses its+{- | Executes the database query for the 'Update' and uses its   'AnnotatedSqlMarshaller' to decode any rows that were just updated, as   returned via a RETURNING clause. @@ -83,8 +79,7 @@ executeUpdateReturnEntities (UpdateReturning marshaller expr) =   Execute.executeAndDecode QueryType.UpdateQuery expr marshaller -{- |-  Builds an 'Update' that will update all of the writable columns described in+{- | Builds an 'Update' that will update all of the writable columns described in   the 'TableDefinition' without returning the data as seen by the database.    This function returns 'Nothing' if the 'TableDefinition' has no columns,@@ -100,8 +95,7 @@ updateToTable =   updateTable WithoutReturning -{- |-  Builds an 'Update' that will update all of the writable columns described in+{- | Builds an 'Update' that will update all of the writable columns described in   the 'TableDefinition' and return the data as seen by the database. This is   useful for getting database-managed columns such as auto-incrementing   identifiers and sequences.@@ -119,8 +113,7 @@ updateToTableReturning =   updateTable WithReturning -{- |-  Builds an 'Update' that will apply the specified column set clauses to rows+{- | Builds an 'Update' that will apply the specified column set clauses to rows   within the specified table without returning the data as seen by the database.  @since 1.0.0.0@@ -133,8 +126,7 @@ updateToTableFields =   updateFields WithoutReturning -{- |-  Builds an 'Update' that will apply the specified column set clauses to rows+{- | Builds an 'Update' that will apply the specified column set clauses to rows   within the specified table and return the updated version of any rows affected by   the update state by using a @RETURNING@ clause. @@ -148,8 +140,7 @@ updateToTableFieldsReturning =   updateFields WithReturning -{- |-  Builds an 'Update' that will execute the specified query and use the given 'AnnotatedSqlMarshaller' to+{- | Builds an 'Update' that will execute the specified query and use the given 'AnnotatedSqlMarshaller' to   decode it. It is up to the caller to ensure that the given 'Expr.UpdateExpr' makes sense and   produces a value that can be stored, as well as returning a result that the 'AnnotatedSqlMarshaller' can   decode.@@ -208,6 +199,8 @@     rawUpdateExpr returingOption (tableMarshaller tableDef) $       Expr.updateExpr         (tableName tableDef)+        Nothing         (Expr.setClauseList setClauses)+        Nothing         whereClause         (mkTableReturningClause returingOption tableDef)
src/Orville/PostgreSQL/Expr.hs view
@@ -1,7 +1,7 @@ {-# OPTIONS_GHC -Wno-missing-import-lists #-}  {- |-Copyright : Flipstone Technology Partners 2023+Copyright : Flipstone Technology Partners 2023-2025 License   : MIT Stability : Stable @@ -37,14 +37,19 @@ module Orville.PostgreSQL.Expr   ( module Orville.PostgreSQL.Expr.BinaryOperator   , module Orville.PostgreSQL.Expr.ColumnDefinition+  , module Orville.PostgreSQL.Expr.Comment   , module Orville.PostgreSQL.Expr.Count   , module Orville.PostgreSQL.Expr.Cursor   , module Orville.PostgreSQL.Expr.DataType   , module Orville.PostgreSQL.Expr.Delete   , module Orville.PostgreSQL.Expr.GroupBy+  , module Orville.PostgreSQL.Expr.TableReferenceList   , module Orville.PostgreSQL.Expr.IfExists+  , module Orville.PostgreSQL.Expr.IfNotExists   , module Orville.PostgreSQL.Expr.Index   , module Orville.PostgreSQL.Expr.Insert+  , module Orville.PostgreSQL.Expr.OnConflict+  , module Orville.PostgreSQL.Expr.Join   , module Orville.PostgreSQL.Expr.LimitExpr   , module Orville.PostgreSQL.Expr.Math   , module Orville.PostgreSQL.Expr.Name@@ -56,42 +61,71 @@   , module Orville.PostgreSQL.Expr.SequenceDefinition   , module Orville.PostgreSQL.Expr.TableConstraint   , module Orville.PostgreSQL.Expr.TableDefinition-  , module Orville.PostgreSQL.Expr.TableReferenceList   , module Orville.PostgreSQL.Expr.Time   , module Orville.PostgreSQL.Expr.Transaction   , module Orville.PostgreSQL.Expr.Update   , module Orville.PostgreSQL.Expr.ValueExpression   , module Orville.PostgreSQL.Expr.WhereClause+  , module Orville.PostgreSQL.Expr.Trigger+  , module Orville.PostgreSQL.Expr.Function+  , module Orville.PostgreSQL.Expr.OrReplace+  , module Orville.PostgreSQL.Expr.ConditionalExpr+  , module Orville.PostgreSQL.Expr.Window+  , module Orville.PostgreSQL.Expr.Vacuum+  , module Orville.PostgreSQL.Expr.Extension+  , module Orville.PostgreSQL.Expr.RowLocking+  , module Orville.PostgreSQL.Expr.Aggregate+  , module Orville.PostgreSQL.Expr.Filter+  , module Orville.PostgreSQL.Expr.FetchClause+  , module Orville.PostgreSQL.Expr.Values+  , module Orville.PostgreSQL.Expr.TextSearch   ) where  -- Note: we list the re-exports explicity above to control the order that they -- appear in the generated haddock documentation. +import Orville.PostgreSQL.Expr.Aggregate import Orville.PostgreSQL.Expr.BinaryOperator import Orville.PostgreSQL.Expr.ColumnDefinition+import Orville.PostgreSQL.Expr.Comment+import Orville.PostgreSQL.Expr.ConditionalExpr import Orville.PostgreSQL.Expr.Count import Orville.PostgreSQL.Expr.Cursor import Orville.PostgreSQL.Expr.DataType import Orville.PostgreSQL.Expr.Delete+import Orville.PostgreSQL.Expr.Extension+import Orville.PostgreSQL.Expr.FetchClause+import Orville.PostgreSQL.Expr.Filter+import Orville.PostgreSQL.Expr.Function import Orville.PostgreSQL.Expr.GroupBy import Orville.PostgreSQL.Expr.IfExists+import Orville.PostgreSQL.Expr.IfNotExists import Orville.PostgreSQL.Expr.Index import Orville.PostgreSQL.Expr.Insert+import Orville.PostgreSQL.Expr.Join import Orville.PostgreSQL.Expr.LimitExpr import Orville.PostgreSQL.Expr.Math import Orville.PostgreSQL.Expr.Name import Orville.PostgreSQL.Expr.OffsetExpr+import Orville.PostgreSQL.Expr.OnConflict+import Orville.PostgreSQL.Expr.OrReplace import Orville.PostgreSQL.Expr.OrderBy import Orville.PostgreSQL.Expr.Query import Orville.PostgreSQL.Expr.ReturningExpr+import Orville.PostgreSQL.Expr.RowLocking import Orville.PostgreSQL.Expr.Select import Orville.PostgreSQL.Expr.SequenceDefinition import Orville.PostgreSQL.Expr.TableConstraint import Orville.PostgreSQL.Expr.TableDefinition import Orville.PostgreSQL.Expr.TableReferenceList+import Orville.PostgreSQL.Expr.TextSearch import Orville.PostgreSQL.Expr.Time import Orville.PostgreSQL.Expr.Transaction+import Orville.PostgreSQL.Expr.Trigger import Orville.PostgreSQL.Expr.Update+import Orville.PostgreSQL.Expr.Vacuum import Orville.PostgreSQL.Expr.ValueExpression+import Orville.PostgreSQL.Expr.Values import Orville.PostgreSQL.Expr.WhereClause+import Orville.PostgreSQL.Expr.Window
+ src/Orville/PostgreSQL/Expr/Aggregate.hs view
@@ -0,0 +1,502 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++{- |+Copyright : Flipstone Technology Partners 2024-2025+License   : MIT+Stability : Stable++@since 1.1.0.0+-}+module Orville.PostgreSQL.Expr.Aggregate+  ( AggregateOptionExpr+  , aggregateExpression+  , aggregateStarExpression+  , aggregateWithinGroupExpression+  , arrayAggAggregateFunction+  , avgAggregateFunction+  , bitAndAggregateFunction+  , bitOrAggregateFunction+  , bitXorAggregateFunction+  , boolAndAggregateFunction+  , boolOrAggregateFunction+  , countExprAggregateFunction+  , count1AggregateFunction+  , countColumnAggregateFunction+  , countAggregateFunction+  , everyAggregateFunction+  , jsonAggAggregateFunction+  , jsonObjectAggAggregateFunction+  , jsonbAggAggregateFunction+  , jsonbObjectAggAggregateFunction+  , maxAggregateFunction+  , minAggregateFunction+  , stringAggAggregateFunction+  , sumAggregateFunction+  , corAggregateFunction+  , covarPopAggregateFunction+  , covarSampAggregateFunction+  , stddevPopAggregateFunction+  , stddevSampAggregateFunction+  , varPopAggregateFunction+  , varSampAggregateFunction+  )+where++import qualified Data.List.NonEmpty as NEL++import qualified Orville.PostgreSQL.Expr.Filter as Filter+import Orville.PostgreSQL.Expr.Name (FunctionName)+import qualified Orville.PostgreSQL.Expr.Name as Name+import qualified Orville.PostgreSQL.Expr.OrderBy as OrderBy+import qualified Orville.PostgreSQL.Expr.ValueExpression as ValueExpression+import qualified Orville.PostgreSQL.Raw.RawSql as RawSql++{- | 'AggregateOptionExpr' represents the options to aggregate functions. E.G.++> ALL++or++> DISTINCT++'AggregateOptionExpr' provides a 'RawSql.SqlExpression' instance. See+'RawSql.unsafeSqlExpression' for how to construct a value with your own custom+SQL.++@since 1.1.0.0+-}+newtype AggregateOptionExpr = AggregateOptionExpr RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++{- | Builds an aggregate expression. Note that it is up to the caller to ensure the validity of all of the arguments and that the resulting 'ValueExpression.ValueExpression' is used in an appropriate manner.++@since 1.1.0.0+-}+aggregateExpression :: FunctionName -> Maybe AggregateOptionExpr -> NEL.NonEmpty ValueExpression.ValueExpression -> Maybe OrderBy.OrderByClause -> Maybe Filter.FilterExpr -> ValueExpression.ValueExpression+aggregateExpression function mbAggOption parameters mbOrderByClause mbFilter =+  let+    fnCall =+      RawSql.toRawSql function+        <> RawSql.parenthesized+          ( ( case mbAggOption of+                Nothing -> mempty+                Just aggOption ->+                  RawSql.toRawSql aggOption+                    <> RawSql.space+            )+              <> RawSql.intercalate RawSql.comma parameters+              <> maybe mempty RawSql.toRawSql mbOrderByClause+          )++    filtering =+      case mbFilter of+        Nothing -> mempty+        Just filterExpr -> RawSql.space <> RawSql.toRawSql filterExpr+  in+    RawSql.unsafeFromRawSql $+      fnCall+        <> filtering++{- | Builds an aggregate expression with a * as the function argument. Note that it is up to the caller to ensure the validity of all of the arguments and that the resulting 'ValueExpression.ValueExpression' is used in an appropriate manner.++@since 1.1.0.0+-}+aggregateStarExpression :: FunctionName -> Maybe Filter.FilterExpr -> ValueExpression.ValueExpression+aggregateStarExpression function mbFilter =+  let+    parameters :: [ValueExpression.ValueExpression]+    parameters = pure . RawSql.unsafeFromRawSql $ RawSql.fromString "*"+    filtering =+      case mbFilter of+        Nothing -> mempty+        Just filterExpr -> RawSql.space <> RawSql.toRawSql filterExpr+  in+    RawSql.unsafeFromRawSql $+      RawSql.toRawSql (ValueExpression.functionCall function parameters)+        <> filtering++{- | Builds an orderd-set aggregate expression. These have the 'OrderBy.OrderByClause' as an argument to the aggregating function. Note that it is up to the caller to ensure the validity of all of the arguments and that the resulting 'ValueExpression.ValueExpression' is used in an appropriate manner.++@since 1.1.0.0+-}+aggregateWithinGroupExpression :: FunctionName -> [ValueExpression.ValueExpression] -> OrderBy.OrderByClause -> Maybe Filter.FilterExpr -> ValueExpression.ValueExpression+aggregateWithinGroupExpression function parameters orderByClause mbFilter =+  let+    within =+      RawSql.fromString " WITHIN GROUP "+        <> RawSql.parenthesized (RawSql.toRawSql orderByClause)+    filtering =+      case mbFilter of+        Nothing -> mempty+        Just filterExpr -> RawSql.space <> RawSql.toRawSql filterExpr+  in+    RawSql.unsafeFromRawSql $+      RawSql.toRawSql (ValueExpression.functionCall function parameters)+        <> within+        <> filtering++{- | The SQL @array_agg@ aggregate function.++@since 1.1.0.0+-}+arrayAggAggregateFunction ::+  Maybe AggregateOptionExpr ->+  ValueExpression.ValueExpression ->+  Maybe OrderBy.OrderByClause ->+  Maybe Filter.FilterExpr ->+  ValueExpression.ValueExpression+arrayAggAggregateFunction =+  singleParameterAggregateFunction Name.arrayAggFunctionName++{- | The SQL @avg@ aggregate function.++@since 1.1.0.0+-}+avgAggregateFunction ::+  Maybe AggregateOptionExpr ->+  ValueExpression.ValueExpression ->+  Maybe OrderBy.OrderByClause ->+  Maybe Filter.FilterExpr ->+  ValueExpression.ValueExpression+avgAggregateFunction =+  singleParameterAggregateFunction Name.avgFunctionName++{- | The SQL @bit_and@ aggregate function.++@since 1.1.0.0+-}+bitAndAggregateFunction ::+  Maybe AggregateOptionExpr ->+  ValueExpression.ValueExpression ->+  Maybe OrderBy.OrderByClause ->+  Maybe Filter.FilterExpr ->+  ValueExpression.ValueExpression+bitAndAggregateFunction =+  singleParameterAggregateFunction Name.bitAndFunctionName++{- | The SQL @bit_or@ aggregate function.++@since 1.1.0.0+-}+bitOrAggregateFunction ::+  Maybe AggregateOptionExpr ->+  ValueExpression.ValueExpression ->+  Maybe OrderBy.OrderByClause ->+  Maybe Filter.FilterExpr ->+  ValueExpression.ValueExpression+bitOrAggregateFunction =+  singleParameterAggregateFunction Name.bitOrFunctionName++{- | The SQL @bit_xor@ aggregate function.++@since 1.1.0.0+-}+bitXorAggregateFunction ::+  Maybe AggregateOptionExpr ->+  ValueExpression.ValueExpression ->+  Maybe OrderBy.OrderByClause ->+  Maybe Filter.FilterExpr ->+  ValueExpression.ValueExpression+bitXorAggregateFunction =+  singleParameterAggregateFunction Name.bitXorFunctionName++{- | The SQL @bool_and@ aggregate function.++@since 1.1.0.0+-}+boolAndAggregateFunction ::+  Maybe AggregateOptionExpr ->+  ValueExpression.ValueExpression ->+  Maybe OrderBy.OrderByClause ->+  Maybe Filter.FilterExpr ->+  ValueExpression.ValueExpression+boolAndAggregateFunction =+  singleParameterAggregateFunction Name.boolAndFunctionName++{- | The SQL @bool_or@ aggregate function.++@since 1.1.0.0+-}+boolOrAggregateFunction ::+  Maybe AggregateOptionExpr ->+  ValueExpression.ValueExpression ->+  Maybe OrderBy.OrderByClause ->+  Maybe Filter.FilterExpr ->+  ValueExpression.ValueExpression+boolOrAggregateFunction =+  singleParameterAggregateFunction Name.boolOrFunctionName++{- | A simplified version of 'countAggregateFunction' for the SQL @count@ of a+   'ValueExpression.ValueExpression'++@since 1.1.0.0+-}+countExprAggregateFunction ::+  ValueExpression.ValueExpression ->+  ValueExpression.ValueExpression+countExprAggregateFunction valExpr =+  singleParameterAggregateFunction Name.countFunctionName Nothing valExpr Nothing Nothing++{- | A simplified version of 'countAggregateFunction' for the SQL @count(1)@++@since 1.1.0.0+-}+count1AggregateFunction ::+  ValueExpression.ValueExpression+count1AggregateFunction =+  countExprAggregateFunction (RawSql.unsafeFromRawSql . RawSql.intDecLiteral $ 1)++{- | A simplified version of 'countAggregateFunction' for the SQL @count@ of a column++@since 1.1.0.0+-}+countColumnAggregateFunction ::+  Name.QualifiedOrUnqualified Name.ColumnName ->+  ValueExpression.ValueExpression+countColumnAggregateFunction =+  countExprAggregateFunction . ValueExpression.columnReference++{- | The SQL @count@ aggregate function.++@since 1.1.0.0+-}+countAggregateFunction ::+  Maybe AggregateOptionExpr ->+  ValueExpression.ValueExpression ->+  Maybe OrderBy.OrderByClause ->+  Maybe Filter.FilterExpr ->+  ValueExpression.ValueExpression+countAggregateFunction =+  singleParameterAggregateFunction Name.countFunctionName++{- | The SQL @every@ aggregate function.++@since 1.1.0.0+-}+everyAggregateFunction ::+  Maybe AggregateOptionExpr ->+  ValueExpression.ValueExpression ->+  Maybe OrderBy.OrderByClause ->+  Maybe Filter.FilterExpr ->+  ValueExpression.ValueExpression+everyAggregateFunction =+  singleParameterAggregateFunction Name.everyFunctionName++{- | The SQL @json_agg@ aggregate function.++@since 1.1.0.0+-}+jsonAggAggregateFunction ::+  Maybe AggregateOptionExpr ->+  ValueExpression.ValueExpression ->+  Maybe OrderBy.OrderByClause ->+  Maybe Filter.FilterExpr ->+  ValueExpression.ValueExpression+jsonAggAggregateFunction =+  singleParameterAggregateFunction Name.jsonAggFunctionName++{- | The SQL @json_object_agg@ aggregate function.++@since 1.1.0.0+-}+jsonObjectAggAggregateFunction ::+  Maybe AggregateOptionExpr ->+  ValueExpression.ValueExpression ->+  ValueExpression.ValueExpression ->+  Maybe OrderBy.OrderByClause ->+  Maybe Filter.FilterExpr ->+  ValueExpression.ValueExpression+jsonObjectAggAggregateFunction mbAggOption param1 param2 =+  aggregateExpression Name.jsonObjectAggFunctionName mbAggOption (param1 NEL.:| pure param2)++{- | The SQL @jsonb_agg@ aggregate function.++@since 1.1.0.0+-}+jsonbAggAggregateFunction ::+  Maybe AggregateOptionExpr ->+  ValueExpression.ValueExpression ->+  Maybe OrderBy.OrderByClause ->+  Maybe Filter.FilterExpr ->+  ValueExpression.ValueExpression+jsonbAggAggregateFunction =+  singleParameterAggregateFunction Name.jsonbAggFunctionName++{- | The SQL @jsonb_object_agg@ aggregate function.++@since 1.1.0.0+-}+jsonbObjectAggAggregateFunction ::+  Maybe AggregateOptionExpr ->+  ValueExpression.ValueExpression ->+  ValueExpression.ValueExpression ->+  Maybe OrderBy.OrderByClause ->+  Maybe Filter.FilterExpr ->+  ValueExpression.ValueExpression+jsonbObjectAggAggregateFunction mbAggOption param1 param2 =+  aggregateExpression Name.jsonbObjectAggFunctionName mbAggOption (param1 NEL.:| pure param2)++{- | The SQL @max@ aggregate function.++@since 1.1.0.0+-}+maxAggregateFunction ::+  Maybe AggregateOptionExpr ->+  ValueExpression.ValueExpression ->+  Maybe OrderBy.OrderByClause ->+  Maybe Filter.FilterExpr ->+  ValueExpression.ValueExpression+maxAggregateFunction =+  singleParameterAggregateFunction Name.maxFunctionName++{- | The SQL @min@ aggregate function.++@since 1.1.0.0+-}+minAggregateFunction ::+  Maybe AggregateOptionExpr ->+  ValueExpression.ValueExpression ->+  Maybe OrderBy.OrderByClause ->+  Maybe Filter.FilterExpr ->+  ValueExpression.ValueExpression+minAggregateFunction =+  singleParameterAggregateFunction Name.minFunctionName++{- | The SQL @string_agg@ aggregate function.++@since 1.1.0.0+-}+stringAggAggregateFunction ::+  Maybe AggregateOptionExpr ->+  ValueExpression.ValueExpression ->+  ValueExpression.ValueExpression ->+  Maybe OrderBy.OrderByClause ->+  Maybe Filter.FilterExpr ->+  ValueExpression.ValueExpression+stringAggAggregateFunction mbAggOption param1 param2 =+  aggregateExpression Name.stringAggFunctionName mbAggOption (param1 NEL.:| pure param2)++{- | The SQL @sum@ aggregate function.++@since 1.1.0.0+-}+sumAggregateFunction ::+  Maybe AggregateOptionExpr ->+  ValueExpression.ValueExpression ->+  Maybe OrderBy.OrderByClause ->+  Maybe Filter.FilterExpr ->+  ValueExpression.ValueExpression+sumAggregateFunction =+  singleParameterAggregateFunction Name.sumFunctionName++{- | The SQL @cor@ aggregate function.++@since 1.1.0.0+-}+corAggregateFunction ::+  Maybe AggregateOptionExpr ->+  ValueExpression.ValueExpression ->+  ValueExpression.ValueExpression ->+  Maybe OrderBy.OrderByClause ->+  Maybe Filter.FilterExpr ->+  ValueExpression.ValueExpression+corAggregateFunction mbAggOption param1 param2 =+  aggregateExpression Name.corFunctionName mbAggOption (param1 NEL.:| pure param2)++{- | The SQL @covar_pop@ aggregate function.++@since 1.1.0.0+-}+covarPopAggregateFunction ::+  Maybe AggregateOptionExpr ->+  ValueExpression.ValueExpression ->+  ValueExpression.ValueExpression ->+  Maybe OrderBy.OrderByClause ->+  Maybe Filter.FilterExpr ->+  ValueExpression.ValueExpression+covarPopAggregateFunction mbAggOption param1 param2 =+  aggregateExpression Name.covarPopFunctionName mbAggOption (param1 NEL.:| pure param2)++{- | The SQL @covar_samp@ aggregate function.++@since 1.1.0.0+-}+covarSampAggregateFunction ::+  Maybe AggregateOptionExpr ->+  ValueExpression.ValueExpression ->+  ValueExpression.ValueExpression ->+  Maybe OrderBy.OrderByClause ->+  Maybe Filter.FilterExpr ->+  ValueExpression.ValueExpression+covarSampAggregateFunction mbAggOption param1 param2 =+  aggregateExpression Name.covarSampFunctionName mbAggOption (param1 NEL.:| pure param2)++{- | The SQL @stddev_pop@ aggregate function.++@since 1.1.0.0+-}+stddevPopAggregateFunction ::+  Maybe AggregateOptionExpr ->+  ValueExpression.ValueExpression ->+  ValueExpression.ValueExpression ->+  Maybe OrderBy.OrderByClause ->+  Maybe Filter.FilterExpr ->+  ValueExpression.ValueExpression+stddevPopAggregateFunction mbAggOption param1 param2 =+  aggregateExpression Name.stddevPopFunctionName mbAggOption (param1 NEL.:| pure param2)++{- | The SQL @stddev_samp@ aggregate function.++@since 1.1.0.0+-}+stddevSampAggregateFunction ::+  Maybe AggregateOptionExpr ->+  ValueExpression.ValueExpression ->+  ValueExpression.ValueExpression ->+  Maybe OrderBy.OrderByClause ->+  Maybe Filter.FilterExpr ->+  ValueExpression.ValueExpression+stddevSampAggregateFunction mbAggOption param1 param2 =+  aggregateExpression Name.stddevSampFunctionName mbAggOption (param1 NEL.:| pure param2)++{- | The SQL @var_pop@ aggregate function.++@since 1.1.0.0+-}+varPopAggregateFunction ::+  Maybe AggregateOptionExpr ->+  ValueExpression.ValueExpression ->+  ValueExpression.ValueExpression ->+  Maybe OrderBy.OrderByClause ->+  Maybe Filter.FilterExpr ->+  ValueExpression.ValueExpression+varPopAggregateFunction mbAggOption param1 param2 =+  aggregateExpression Name.varPopFunctionName mbAggOption (param1 NEL.:| pure param2)++{- | The SQL @var_samp@ aggregate function.++@since 1.1.0.0+-}+varSampAggregateFunction ::+  Maybe AggregateOptionExpr ->+  ValueExpression.ValueExpression ->+  ValueExpression.ValueExpression ->+  Maybe OrderBy.OrderByClause ->+  Maybe Filter.FilterExpr ->+  ValueExpression.ValueExpression+varSampAggregateFunction mbAggOption param1 param2 =+  aggregateExpression Name.varSampFunctionName mbAggOption (param1 NEL.:| pure param2)++-- internal helper to make building up the aggregates that only take a single parameter.+singleParameterAggregateFunction ::+  FunctionName ->+  Maybe AggregateOptionExpr ->+  ValueExpression.ValueExpression ->+  Maybe OrderBy.OrderByClause ->+  Maybe Filter.FilterExpr ->+  ValueExpression.ValueExpression+singleParameterAggregateFunction function mbAggOption parameter =+  aggregateExpression function mbAggOption (pure parameter)
src/Orville/PostgreSQL/Expr/BinaryOperator.hs view
@@ -15,6 +15,8 @@   , binaryOperator   , binaryOpExpression   , equalsOp+  , isDistinctFromOp+  , isNotDistinctFromOp   , notEqualsOp   , greaterThanOp   , lessThanOp@@ -41,8 +43,7 @@ import Orville.PostgreSQL.Expr.ValueExpression (ValueExpression) import qualified Orville.PostgreSQL.Raw.RawSql as RawSql -{- |-Type to represent any SQL operator of two arguments. E.G.+{- | Type to represent any SQL operator of two arguments. E.G.  > AND @@ -85,6 +86,20 @@ notEqualsOp =   binaryOperator "<>" +{- | The SQL 'IS DISTINCT FROM' binary 'operator'.++@since 1.1.0.0+-}+isDistinctFromOp :: BinaryOperator+isDistinctFromOp = binaryOperator "IS DISTINCT FROM"++{- | The SQL 'IS NOT DISTINCT FROM' binary 'operator'.++@since 1.1.0.0+-}+isNotDistinctFromOp :: BinaryOperator+isNotDistinctFromOp = binaryOperator "IS NOT DISTINCT FROM"+ {- | The SQL strictly greater than binary operator.  @since 1.0.0.0@@ -253,8 +268,8 @@ binaryOpExpression op left right =   binaryOpExpressionUnparenthenizedArguments     op-    (RawSql.unsafeFromRawSql (RawSql.leftParen <> RawSql.toRawSql left <> RawSql.rightParen))-    (RawSql.unsafeFromRawSql (RawSql.leftParen <> RawSql.toRawSql right <> RawSql.rightParen))+    (RawSql.unsafeFromRawSql (RawSql.parenthesized left))+    (RawSql.unsafeFromRawSql (RawSql.parenthesized right))  -- internal helper function binaryOpExpressionUnparenthenizedArguments ::
src/Orville/PostgreSQL/Expr/ColumnDefinition.hs view
@@ -1,7 +1,7 @@ {-# LANGUAGE GeneralizedNewtypeDeriving #-}  {- |-Copyright : Flipstone Technology Partners 2023+Copyright : Flipstone Technology Partners 2023-2025 License   : MIT Stability : Stable @@ -15,18 +15,19 @@   , nullConstraint   , ColumnDefault   , columnDefault+  , identityColumnConstraint+  , ColumnIdentityGeneration+  , alwaysColumnIdentityGeneration+  , byDefaultColumnIdentityGeneration   ) where -import qualified Data.Maybe as Maybe- import Orville.PostgreSQL.Expr.DataType (DataType) import Orville.PostgreSQL.Expr.Name (ColumnName) import Orville.PostgreSQL.Expr.ValueExpression (ValueExpression) import qualified Orville.PostgreSQL.Raw.RawSql as RawSql -{- |-Represent a complete definition of a column. E.G.+{- | Represent a complete definition of a column. E.G.  > foo INTEGER @@ -52,23 +53,28 @@   ColumnName ->   -- | The SQL type of the column.   DataType ->-  -- | The constraint on the column, if any.-  Maybe ColumnConstraint ->+  -- | The constraints on the column, if any.+  [ColumnConstraint] ->   -- | The default value for the column, if any.   Maybe ColumnDefault ->   ColumnDefinition-columnDefinition columnName dataType maybeColumnConstraint maybeColumnDefault =-  ColumnDefinition-    . RawSql.intercalate RawSql.space-    $ Maybe.catMaybes-      [ Just $ RawSql.toRawSql columnName-      , Just $ RawSql.toRawSql dataType-      , fmap RawSql.toRawSql maybeColumnConstraint-      , fmap RawSql.toRawSql maybeColumnDefault-      ]+columnDefinition columnName dataType columnConstraints maybeColumnDefault =+  let+    constraintRawSql =+      RawSql.intercalate RawSql.space columnConstraints+  in+    ColumnDefinition $+      RawSql.toRawSql columnName+        <> RawSql.space+        <> RawSql.toRawSql dataType+        <> RawSql.space+        <> constraintRawSql+        <> case maybeColumnDefault of+          Nothing -> mempty+          Just colDefault ->+            RawSql.space <> RawSql.toRawSql colDefault -{- |-Represent constraints, such as nullability, on a column. E.G.+{- | Represent constraints, such as nullability, on a column. E.G.  > NOT NULL @@ -101,8 +107,51 @@ nullConstraint =   ColumnConstraint (RawSql.fromString "NULL") -{- |-Represents the default value of a column. E.G.+{- | Represent the generation definition of an identity column. E.G.++> ALWAYS++'ColumnIdentityGeneration' provides a 'RawSql.SqlExpression' instance. See+'RawSql.unsafeSqlExpression' for how to construct a value with your own custom+SQL.++@since 1.1.0.0+-}+newtype ColumnIdentityGeneration+  = ColumnIdentityGeneration RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++{- | Express that a column is an identity column.++@since 1.1.0.0+-}+identityColumnConstraint ::+  ColumnIdentityGeneration ->+  ColumnConstraint+identityColumnConstraint identityGeneration =+  ColumnConstraint $+    RawSql.fromString "GENERATED "+      <> RawSql.toRawSql identityGeneration+      <> RawSql.fromString " AS IDENTITY"++{- | The @ALWAYS@ generation for an identity column++@since 1.1.0.0+-}+alwaysColumnIdentityGeneration :: ColumnIdentityGeneration+alwaysColumnIdentityGeneration = ColumnIdentityGeneration $ RawSql.fromString "ALWAYS"++{- | The @BY DEFAULT@ generation for an identity column++@since 1.1.0.0+-}+byDefaultColumnIdentityGeneration :: ColumnIdentityGeneration+byDefaultColumnIdentityGeneration = ColumnIdentityGeneration $ RawSql.fromString "BY DEFAULT"++{- | Represents the default value of a column. E.G.  > now() 
+ src/Orville/PostgreSQL/Expr/Comment.hs view
@@ -0,0 +1,79 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++{- |+Copyright : Flipstone Technology Partners 2024+License   : MIT+Stability : Stable++@since 1.1.0.0+-}+module Orville.PostgreSQL.Expr.Comment+  ( Comment+  , commentText+  , CommentExpr+  , commentTableExpr+  , commentColumnExpr+  ) where++import qualified Data.Text as T+import qualified Data.Text.Encoding as TEnc++import Orville.PostgreSQL.Expr.Name (ColumnName, QualifiedOrUnqualified, SchemaName, TableName, qualifyColumn)+import qualified Orville.PostgreSQL.Raw.RawSql as RawSql++{- | Type to represent a PostgreSQL comment string literal.++@since 1.1.0.0+-}+newtype Comment = Comment RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++{- | Construct a 'Comment' from a 'T.Text' value. The value will be escaped and quoted.++@since 1.1.0.0+-}+commentText :: T.Text -> Comment+commentText = Comment . RawSql.stringLiteral . TEnc.encodeUtf8++{- | Type to represent a PostgreSQL @COMMENT@ statement.++@since 1.1.0.0+-}+newtype CommentExpr = CommentExpr RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++{- | Construct a 'CommentExpr' for a @COMMENT ON TABLE@ statement.++@since 1.1.0.0+-}+commentTableExpr :: QualifiedOrUnqualified TableName -> Maybe Comment -> CommentExpr+commentTableExpr tableName mbComment =+  CommentExpr $+    RawSql.intercalate+      RawSql.space+      [ RawSql.fromString "COMMENT ON TABLE"+      , RawSql.toRawSql tableName+      , RawSql.fromString "IS"+      , maybe RawSql.nullLiteral RawSql.toRawSql mbComment+      ]++{- | Construct a 'CommentExpr' for a @COMMENT ON COLUMN@ statement.++@since 1.1.0.0+-}+commentColumnExpr :: Maybe SchemaName -> TableName -> ColumnName -> Maybe Comment -> CommentExpr+commentColumnExpr mbSchema tableName colName mbComment =+  CommentExpr $+    RawSql.intercalate+      RawSql.space+      [ RawSql.fromString "COMMENT ON COLUMN"+      , RawSql.toRawSql $ qualifyColumn mbSchema tableName colName+      , RawSql.fromString "IS"+      , maybe RawSql.nullLiteral RawSql.toRawSql mbComment+      ]
+ src/Orville/PostgreSQL/Expr/ConditionalExpr.hs view
@@ -0,0 +1,130 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++{- |+Copyright : Flipstone Technology Partners 2024+License   : MIT+Stability : Stable++@since 1.1.0.0+-}+module Orville.PostgreSQL.Expr.ConditionalExpr+  ( coalesce+  , WhenExpr+  , whenExpr+  , caseExpr+  , nullIf+  , greatest+  , least+  ) where++import qualified Data.List.NonEmpty as NE++import Orville.PostgreSQL.Expr.ValueExpression (ValueExpression)+import Orville.PostgreSQL.Expr.WhereClause (BooleanExpr)+import qualified Orville.PostgreSQL.Raw.RawSql as RawSql++{- | Creates a 'ValueExpression' corresponding to the SQL @COALESE@.++@since 1.1.0.0+-}+coalesce :: NE.NonEmpty ValueExpression -> ValueExpression+coalesce valExprs =+  RawSql.unsafeFromRawSql $+    RawSql.fromString "COALESCE"+      <> RawSql.leftParen+      <> RawSql.intercalate RawSql.comma (fmap RawSql.toRawSql valExprs)+      <> RawSql.rightParen++{- | Type to represent the @WHEN@ portion of a SQL @CASE@ expressions.+E.G.++> WHEN condition THEN result++'WhenExpr' provides a 'RawSql.SqlExpression' instance. See+'RawSql.unsafeSqlExpression' for how to construct a value with your own custom+SQL.++@since 1.1.0.0+-}+newtype WhenExpr = WhenExpr RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++{- | Builds a 'WhenExpr' that will apply when the given 'BooleanExpr' evaluates to @TRUE@, resulting in the 'ValueExpression'++@since 1.1.0.0+-}+whenExpr :: BooleanExpr -> ValueExpression -> WhenExpr+whenExpr boolExpr resultExpr =+  WhenExpr $+    RawSql.fromString "WHEN"+      <> RawSql.space+      <> RawSql.toRawSql boolExpr+      <> RawSql.space+      <> RawSql.fromString "THEN"+      <> RawSql.space+      <> RawSql.toRawSql resultExpr++{- | Builds a 'ValueExpression' corresponding to a SQL @CASE@ using the given 'WhenExpr' as the tested value with results and an optional 'ValueExpression' that corresponds to the @ELSE@ portion of the @CASE@.++@since 1.1.0.0+-}+caseExpr ::+  NE.NonEmpty WhenExpr ->+  Maybe ValueExpression ->+  ValueExpression+caseExpr whens mbElse =+  RawSql.unsafeFromRawSql $+    RawSql.fromString "CASE"+      <> RawSql.space+      <> RawSql.intercalate RawSql.space (fmap RawSql.toRawSql whens)+      <> RawSql.space+      <> case mbElse of+        Nothing ->+          RawSql.fromString "END"+        Just elseVal ->+          RawSql.fromString "ELSE"+            <> RawSql.space+            <> RawSql.toRawSql elseVal+            <> RawSql.space+            <> RawSql.fromString "END"++{- | Creates a 'ValueExpression' corresponding to the SQL @NULLIF@.++@since 1.1.0.0+-}+nullIf :: ValueExpression -> ValueExpression -> ValueExpression+nullIf leftVal rightVal =+  RawSql.unsafeFromRawSql $+    RawSql.fromString "NULLIF"+      <> RawSql.leftParen+      <> RawSql.toRawSql leftVal+      <> RawSql.commaSpace+      <> RawSql.toRawSql rightVal+      <> RawSql.rightParen++{- | Creates a 'ValueExpression' corresponding to the SQL @GREATEST@.++@since 1.1.0.0+-}+greatest :: NE.NonEmpty ValueExpression -> ValueExpression+greatest valExprs =+  RawSql.unsafeFromRawSql $+    RawSql.fromString "GREATEST"+      <> RawSql.leftParen+      <> RawSql.intercalate RawSql.comma (fmap RawSql.toRawSql valExprs)+      <> RawSql.rightParen++{- | Creates a 'ValueExpression' corresponding to the SQL @LEAST@.++@since 1.1.0.0+-}+least :: NE.NonEmpty ValueExpression -> ValueExpression+least valExprs =+  RawSql.unsafeFromRawSql $+    RawSql.fromString "LEAST"+      <> RawSql.leftParen+      <> RawSql.intercalate RawSql.comma (fmap RawSql.toRawSql valExprs)+      <> RawSql.rightParen
src/Orville/PostgreSQL/Expr/Count.hs view
@@ -1,5 +1,5 @@ {- |-Copyright : Flipstone Technology Partners 2023+Copyright : Flipstone Technology Partners 2023-2024 License   : MIT Stability : Stable @@ -13,10 +13,12 @@   ) where -import Orville.PostgreSQL.Expr.Name (ColumnName, FunctionName, functionName)+import Orville.PostgreSQL.Expr.Name (ColumnName, FunctionName, QualifiedOrUnqualified, functionName) import Orville.PostgreSQL.Expr.ValueExpression (ValueExpression, columnReference, functionCall) import qualified Orville.PostgreSQL.Raw.RawSql as RawSql +{-# DEPRECATED countFunction "Use Orville.PostgreSQL.Expr.countFunctionName" #-}+ {- | The SQL @count@ function.  @since 1.0.0.0@@ -24,6 +26,8 @@ countFunction :: FunctionName countFunction = functionName "count" +{-# DEPRECATED count "Use Orville.PostgreSQL.Expr.countExprAggregateFunction" #-}+ {- | Given a 'ValueExpression', use it as the argument to the SQL @count@.  @since 1.0.0.0@@ -32,6 +36,8 @@ count value =   functionCall countFunction [value] +{-# DEPRECATED count1 "Use Orville.PostgreSQL.Expr.count1AggregateFunction" #-}+ {- | The SQL @count(1)@.  @since 1.0.0.0@@ -40,10 +46,12 @@ count1 =   count . RawSql.unsafeFromRawSql . RawSql.intDecLiteral $ 1 +{-# DEPRECATED countColumn "Use Orville.PostgreSQL.Expr.countColumnAggregateFunction" #-}+ {- | Use a given column as the argument to the SQL @count@.  @since 1.0.0.0 -}-countColumn :: ColumnName -> ValueExpression+countColumn :: QualifiedOrUnqualified ColumnName -> ValueExpression countColumn =   count . columnReference
src/Orville/PostgreSQL/Expr/Cursor.hs view
@@ -49,8 +49,7 @@ import Orville.PostgreSQL.Expr.Query (QueryExpr) import qualified Orville.PostgreSQL.Raw.RawSql as RawSql -{- |-'DeclareExpr' corresponds to the SQL DECLARE statement, for declaring and+{- | 'DeclareExpr' corresponds to the SQL DECLARE statement, for declaring and opening cursors. E.G.  > DECLARE FOO CURSOR FOR SELECT * FROM BAR@@ -84,20 +83,19 @@   QueryExpr ->   DeclareExpr declare cursorName maybeScrollExpr maybeHoldExpr queryExpr =-  DeclareExpr $-    RawSql.intercalate RawSql.space $-      catMaybes-        [ Just $ RawSql.fromString "DECLARE"-        , Just $ RawSql.toRawSql cursorName-        , fmap RawSql.toRawSql maybeScrollExpr-        , Just $ RawSql.fromString "CURSOR"-        , fmap RawSql.toRawSql maybeHoldExpr-        , Just $ RawSql.fromString "FOR"-        , Just $ RawSql.toRawSql queryExpr-        ]+  DeclareExpr+    . RawSql.intercalate RawSql.space+    $ catMaybes+      [ Just $ RawSql.fromString "DECLARE"+      , Just $ RawSql.toRawSql cursorName+      , fmap RawSql.toRawSql maybeScrollExpr+      , Just $ RawSql.fromString "CURSOR"+      , fmap RawSql.toRawSql maybeHoldExpr+      , Just $ RawSql.fromString "FOR"+      , Just $ RawSql.toRawSql queryExpr+      ] -{- |-'ScrollExpr' is used to determine if a cursor should be able to fetch+{- | 'ScrollExpr' is used to determine if a cursor should be able to fetch nonsequentially. E.G.  > NO SCROLL@@ -137,8 +135,7 @@ noScroll =   ScrollExpr . RawSql.fromString $ "NO SCROLL" -{- |-'HoldExpr' is used to determine if a cursor should be available for use after+{- | 'HoldExpr' is used to determine if a cursor should be available for use after the transaction that created it has been committed. E.G.  > WITH HOLD@@ -175,8 +172,7 @@ withoutHold =   HoldExpr . RawSql.fromString $ "WITHOUT HOLD" -{- |-'CloseExpr' corresponds to the SQL CLOSE statement. E.G.+{- | 'CloseExpr' corresponds to the SQL CLOSE statement. E.G.  > CLOSE ALL @@ -207,8 +203,7 @@     RawSql.fromString "CLOSE "       <> either RawSql.toRawSql RawSql.toRawSql allOrCursorName -{- |-'AllCursors' corresponds to the ALL keyword in a CLOSE statement. E.G.+{- | 'AllCursors' corresponds to the ALL keyword in a CLOSE statement. E.G.  > ALL @@ -233,8 +228,7 @@ allCursors =   AllCursors . RawSql.fromString $ "ALL" -{- |-'FetchExpr' corresponds to the SQL FETCH statement, for retrieving rows from a+{- | 'FetchExpr' corresponds to the SQL FETCH statement, for retrieving rows from a previously-created cursor. E.G.  > FETCH NEXT FOO@@ -262,16 +256,15 @@ -} fetch :: Maybe CursorDirection -> CursorName -> FetchExpr fetch maybeDirection cursorName =-  FetchExpr $-    RawSql.intercalate RawSql.space $-      catMaybes-        [ Just $ RawSql.fromString "FETCH"-        , fmap RawSql.toRawSql maybeDirection-        , Just $ RawSql.toRawSql cursorName-        ]+  FetchExpr+    . RawSql.intercalate RawSql.space+    $ catMaybes+      [ Just $ RawSql.fromString "FETCH"+      , fmap RawSql.toRawSql maybeDirection+      , Just $ RawSql.toRawSql cursorName+      ] -{- |-'MoveExpr' corresponds to the SQL MOVE statement, for positioning a previously+{- | 'MoveExpr' corresponds to the SQL MOVE statement, for positioning a previously created cursor, /without/ retrieving any rows. E.G.  > MOVE NEXT FOO@@ -303,8 +296,7 @@       , Just $ RawSql.toRawSql cursorName       ] -{- |-'CursorDirection' corresponds to the direction argument to the SQL FETCH and+{- | 'CursorDirection' corresponds to the direction argument to the SQL FETCH and MOVE statements. E.G.  > BACKWARD
src/Orville/PostgreSQL/Expr/DataType.hs view
@@ -33,8 +33,7 @@  import qualified Orville.PostgreSQL.Raw.RawSql as RawSql -{- |-Type to represent any SQL data type expression. E.G.+{- | Type to represent any SQL data type expression. E.G.    > INTEGER @@ -237,8 +236,7 @@ smallint =   DataType (RawSql.fromString "SMALLINT") -{- |-  A 'DataType' that represents the PostgreSQL "JSONB" data type.+{- | A 'DataType' that represents the PostgreSQL "JSONB" data type.  See [postgresql documentation](https://www.postgresql.org/docs/current/datatype-json.html) for more information.
src/Orville/PostgreSQL/Expr/Delete.hs view
@@ -17,13 +17,12 @@  import Data.Maybe (catMaybes) -import Orville.PostgreSQL.Expr.Name (Qualified, TableName)+import Orville.PostgreSQL.Expr.Name (QualifiedOrUnqualified, TableName) import Orville.PostgreSQL.Expr.ReturningExpr (ReturningExpr) import Orville.PostgreSQL.Expr.WhereClause (WhereClause) import qualified Orville.PostgreSQL.Raw.RawSql as RawSql -{- |-Type to represent a SQL delete statement. E.G.+{- | Type to represent a SQL delete statement. E.G.  > DELETE FROM foo WHERE id < 10 @@ -40,24 +39,22 @@       RawSql.SqlExpression     ) -{- |--Construct a SQL DELETE from a table, optionally limiting with a 'WhereClause' and optionally+{- | Construct a SQL DELETE from a table, optionally limiting with a 'WhereClause' and optionally returning a 'ReturningExpr'.  @since 1.0.0.0 -} deleteExpr ::-  Qualified TableName ->+  QualifiedOrUnqualified TableName ->   Maybe WhereClause ->   Maybe ReturningExpr ->   DeleteExpr deleteExpr tableName maybeWhereClause maybeReturningExpr =-  DeleteExpr $-    RawSql.intercalate RawSql.space $-      catMaybes-        [ Just $ RawSql.fromString "DELETE FROM"-        , Just $ RawSql.toRawSql tableName-        , fmap RawSql.toRawSql maybeWhereClause-        , fmap RawSql.toRawSql maybeReturningExpr-        ]+  DeleteExpr+    . RawSql.intercalate RawSql.space+    $ catMaybes+      [ Just $ RawSql.fromString "DELETE FROM"+      , Just $ RawSql.toRawSql tableName+      , fmap RawSql.toRawSql maybeWhereClause+      , fmap RawSql.toRawSql maybeReturningExpr+      ]
+ src/Orville/PostgreSQL/Expr/Extension.hs view
@@ -0,0 +1,116 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++{- |+Copyright : Flipstone Technology Partners 2024+License   : MIT+Stability : Stable++Types and functionality related to loading PostgreSQL extensions. This does not contain features+related to any specific extension, but serves as the basis for loading some extension.++@since 1.1.0.0+-}+module Orville.PostgreSQL.Expr.Extension+  ( CreateExtensionExpr+  , createExtensionExpr+  , DropExtensionExpr+  , dropExtensionExpr+  , ExtensionActionExpr+  , extensionCascadeExpr+  , extensionRestrictExpr+  ) where++import Orville.PostgreSQL.Expr.IfExists (IfExists)+import Orville.PostgreSQL.Expr.IfNotExists (IfNotExists)+import Orville.PostgreSQL.Expr.Name (ExtensionName)+import qualified Orville.PostgreSQL.Raw.RawSql as RawSql++{- | Type to represent a SQL "CREATE EXTENSION" statement. E.G.++> CREATE EXTENSION foo++'CreateIndexExpr' provides a 'RawSql.SqlExpression' instance. See+'RawSql.unsafeSqlExpression' for how to construct a value with your own custom+SQL.++@since 1.1.0.0+-}+newtype CreateExtensionExpr = CreateExtensionExpr RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++{- | Construct a SQL CREATE EXTENSION statement from the extension name and if the statement should+not fail on loading an extension with the same name as an already loaded one.++@since 1.1.0.0+-}+createExtensionExpr :: ExtensionName -> Maybe IfNotExists -> Maybe ExtensionActionExpr -> CreateExtensionExpr+createExtensionExpr extension mbIfNotExists mbAction =+  CreateExtensionExpr $+    RawSql.fromString "CREATE EXTENSION "+      <> maybe mempty ((<> RawSql.space) . RawSql.toRawSql) mbIfNotExists+      <> RawSql.toRawSql extension+      <> maybe mempty ((<> RawSql.space) . RawSql.toRawSql) mbAction++{- | Type to represent a SQL "DROP EXTENSION" statement. E.G.++> DROP EXTENSION foo++'DropIndexExpr' provides a 'RawSql.SqlExpression' instance. See+'RawSql.unsafeSqlExpression' for how to construct a value with your own custom+SQL.++@since 1.1.0.0+-}+newtype DropExtensionExpr = DropExtensionExpr RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++{- | Construct a SQL DROP EXTENSION statement from the extension name and if the statement should+not fail on loading an extension with the same name as an already loaded one.++@since 1.1.0.0+-}+dropExtensionExpr :: ExtensionName -> Maybe IfExists -> Maybe ExtensionActionExpr -> DropExtensionExpr+dropExtensionExpr extension mbIfExists mbAction =+  DropExtensionExpr $+    RawSql.fromString "DROP EXTENSION "+      <> maybe mempty ((<> RawSql.space) . RawSql.toRawSql) mbIfExists+      <> RawSql.toRawSql extension+      <> maybe mempty ((<> RawSql.space) . RawSql.toRawSql) mbAction++{- | Type to represent a extension action on a @EXTENSION@. E.G.+the @CASCADE@ in++> CREATE EXTENSION foo CASCADE++'ExtensionActionExpr' provides a 'RawSql.SqlExpression' instance. See+'RawSql.unsafeSqlExpression' for how to construct a value with your own custom+SQL.++@since 1.1.0.0+-}+newtype ExtensionActionExpr+  = ExtensionActionExpr RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++{- | The extension action @RESTRICT@.++  @since 1.1.0.0+-}+extensionRestrictExpr :: ExtensionActionExpr+extensionRestrictExpr = ExtensionActionExpr $ RawSql.fromString "RESTRICT"++{- | The extension action @CASCADE@.++  @since 1.1.0.0+-}+extensionCascadeExpr :: ExtensionActionExpr+extensionCascadeExpr = ExtensionActionExpr $ RawSql.fromString "CASCADE"
+ src/Orville/PostgreSQL/Expr/FetchClause.hs view
@@ -0,0 +1,100 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++{- |+Copyright : Flipstone Technology Partners 2024+License   : MIT+Stability : Stable++@since 1.1.0.0+-}+module Orville.PostgreSQL.Expr.FetchClause+  ( FetchClause+  , fetchClauseInt+  , FetchClauseModifier+  , withTiesFetchClauseModifier+  , onlyFetchClauseModifier+  )+where++import qualified Orville.PostgreSQL.Raw.RawSql as RawSql+import qualified Orville.PostgreSQL.Raw.SqlValue as SqlValue++{- | Type to represent a SQL FETCH clause. E.G.++> FETCH FIRST 2 ROWS ONLY++'FetchClause' provides a 'RawSql.SqlExpression' instance. See+'RawSql.unsafeSqlExpression' for how to construct a value with your own custom+SQL.++@since 1.1.0.0+-}+newtype FetchClause+  = FetchClause RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++{- | Build a SQL FETCH clause for a given number of rows and 'FetchClauseModifier'.++@since 1.1.0.0+-}+fetchClauseInt :: Int -> FetchClauseModifier -> FetchClause+fetchClauseInt fetchNumber =+  internal_fetchRaw (RawSql.parameter (SqlValue.fromInt fetchNumber))++-- Internal helper for FETCH. Note this allows illegal expressions to be put together. However many+-- potentially interesting expressions are valid. Such as:+--+-- `VALUES (1, 'o'), (2, 't'), (3, 'th') order by column2 fetch first (select 2) ROW with ties;`+--+-- How to best expose these is not clear at this moment as we do not generally allow any raw sql to+-- be injected, but we do not have a good expression to capture the allowed values here. Finally, it+-- would _appear_ that the same restrictions here would also apply to expressions allowed for+-- LIMIT. Which as of this writing, we do not have a helper for creation.+--+-- Finally note that NEXT and ROW are as PostgreSQL docs call them, noise words, they do not impact+-- the meaning. See https://www.postgresql.org/docs/13/sql-select.html. 'NEXT' and 'ROW' are chosen+-- over 'FIRST' and 'ROWS' respecitvely because they are shorter and thus result in a smaller+-- bytestring being sent over the wire.+internal_fetchRaw :: RawSql.RawSql -> FetchClauseModifier -> FetchClause+internal_fetchRaw countingRawSql modifier =+  FetchClause $+    RawSql.fromString "FETCH NEXT "+      <> RawSql.parenthesized countingRawSql+      <> RawSql.fromString " ROW "+      <> RawSql.toRawSql modifier++{- | Type to represent the treatment of ties in a SQL FETCH expression. E.G.++> WITH TIES++'FetchClauseModifier' provides a 'RawSql.SqlExpression' instance. See+'RawSql.unsafeSqlExpression' for how to construct a value with your own custom+SQL.++@since 1.1.0.0+-}+newtype FetchClauseModifier+  = FetchClauseModifier RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++{- | Allow a FETCH clause to have ties.++@since 1.1.0.0+-}+withTiesFetchClauseModifier :: FetchClauseModifier+withTiesFetchClauseModifier =+  FetchClauseModifier (RawSql.fromString "WITH TIES")++{- | Disallow a FETCH clause to have ties.++@since 1.1.0.0+-}+onlyFetchClauseModifier :: FetchClauseModifier+onlyFetchClauseModifier =+  FetchClauseModifier (RawSql.fromString "ONLY")
+ src/Orville/PostgreSQL/Expr/Filter.hs view
@@ -0,0 +1,43 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++{- |+Copyright : Flipstone Technology Partners 2024+License   : MIT+Stability : Stable++@since 1.1.0.0+-}+module Orville.PostgreSQL.Expr.Filter+  ( FilterExpr+  , filterExpr+  ) where++import qualified Orville.PostgreSQL.Expr.WhereClause as WhereClause+import qualified Orville.PostgreSQL.Raw.RawSql as RawSql++{- | 'FilterExpr' represents the @FILTER@ subexpression E.G.++> FILTER (WHERE some_boolean_expr)+++'FilterExpr' provides a 'RawSql.SqlExpression' instance. See+'RawSql.unsafeSqlExpression' for how to construct a value with your own custom+SQL.++@since 1.1.0.0+-}+newtype FilterExpr = FilterExpr RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++{- | Builds a 'FilterExpr' with the given 'WhereClause.WhereClause'++@since 1.1.0.0+-}+filterExpr :: WhereClause.WhereClause -> FilterExpr+filterExpr whereClause =+  FilterExpr $+    RawSql.fromString "FILTER"+      <> RawSql.parenthesized (RawSql.toRawSql whereClause)
+ src/Orville/PostgreSQL/Expr/Function.hs view
@@ -0,0 +1,247 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++{- |+Copyright : Flipstone Technology Partners 2024+License   : MIT+Stability : Stable++@since 1.1.0.0+-}+module Orville.PostgreSQL.Expr.Function+  ( DropFunctionExpr+  , dropFunction+  , CreateFunctionExpr+  , createFunction+  , FunctionReturns+  , returns+  , ReturnType+  , returnTypeTrigger+  , FunctionLanguage+  , language+  , LanguageName+  , plpgsql+  , FunctionDefinition+  , asDefinition+  ) where++import qualified Data.ByteString.Char8 as BS8+import Data.Maybe (catMaybes)++import Orville.PostgreSQL.Expr.IfExists (IfExists)+import Orville.PostgreSQL.Expr.Name (FunctionName, QualifiedOrUnqualified)+import Orville.PostgreSQL.Expr.OrReplace (OrReplace)+import qualified Orville.PostgreSQL.Raw.RawSql as RawSql++{- | Type to represent a SQL "DROP FUNCTION" statement. E.G.++> DROP FUNCTION my_function++'DropFunctionExpr' provides a 'RawSql.SqlExpression' instance. See+'RawSql.unsafeSqlExpression' for how to construct a value with your own custom+SQL.++@since 1.1.0.0+-}+newtype DropFunctionExpr+  = DropFunctionExpr RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++{- | Constructs a SQL @DROP FUNCTION@ statement from a function name.++@since 1.1.0.0+-}+dropFunction :: Maybe IfExists -> QualifiedOrUnqualified FunctionName -> DropFunctionExpr+dropFunction maybeIfExists name =+  DropFunctionExpr $+    RawSql.intercalate+      RawSql.space+      ( catMaybes+          [ Just (RawSql.fromString "DROP FUNCTION")+          , fmap RawSql.toRawSql maybeIfExists+          , Just (RawSql.toRawSql name)+          ]+      )++{- | Type to represent a SQL "CREATE FUNCTION" statement. E.G.++> CREATE FUNCTION my_function RETURNS trigger AS '<definition>'++'CreateFunctionExpr' provides a 'RawSql.SqlExpression' instance. See+'RawSql.unsafeSqlExpression' for how to construct a value with your own custom+SQL.++@since 1.1.0.0+-}+newtype CreateFunctionExpr+  = CreateFunctionExpr RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++{- | Constructs a SQL @CREATE FUNCTION@ statement from a function name, return type,+language and definiton.++Note: Orville does not currently support creating functions with arguments.++@since 1.1.0.0+-}+createFunction ::+  Maybe OrReplace ->+  QualifiedOrUnqualified FunctionName ->+  FunctionReturns ->+  FunctionLanguage ->+  FunctionDefinition ->+  CreateFunctionExpr+createFunction maybeOrReplace name functionReturns functionLanguage definition =+  CreateFunctionExpr $+    RawSql.intercalate+      RawSql.space+      ( catMaybes+          [ Just $ RawSql.fromString "CREATE"+          , fmap RawSql.toRawSql maybeOrReplace+          , Just $ RawSql.fromString "FUNCTION"+          , Just $ RawSql.toRawSql name+          , Just $ RawSql.fromString "()" -- currently we don't support specifying arguments+          , Just $ RawSql.toRawSql functionReturns+          , Just $ RawSql.toRawSql functionLanguage+          , Just $ RawSql.toRawSql definition+          ]+      )++{- | Type to represent the return specifier given as part of a SQL "CREATE+FUNCTION" statement. E.G. the @RETURNS trigger@ in++> CREATE FUNCTION my_function RETURNS trigger++'FunctionReturns' provides a 'RawSql.SqlExpression' instance. See+'RawSql.unsafeSqlExpression' for how to construct a value with your own custom+SQL.++@since 1.1.0.0+-}+newtype FunctionReturns+  = FunctionReturns RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++{- | Constructs a @RETURNS@ specifier for a @CREATE FUNCTION@ statement with the+  given return type.++@since 1.1.0.0+-}+returns :: ReturnType -> FunctionReturns+returns returnType =+  FunctionReturns $ RawSql.fromString "RETURNS " <> RawSql.toRawSql returnType++{- | Type to represent the return type given as part of a SQL "CREATE+FUNCTION" statement. E.G. the @trigger@ in++> CREATE FUNCTION my_function RETURNS trigger++'ReturnType' provides a 'RawSql.SqlExpression' instance. See+'RawSql.unsafeSqlExpression' for how to construct a value with your own custom+SQL.++@since 1.1.0.0+-}+newtype ReturnType+  = ReturnType RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++{- | The @trigger@ return type.++@since 1.1.0.0+-}+returnTypeTrigger :: ReturnType+returnTypeTrigger =+  ReturnType (RawSql.fromString "trigger")++{- | Type to represent the language specifier given as part of a SQL "CREATE+FUNCTION" statement. E.G. the @LANGUAGE plpgsql@ in++> CREATE FUNCTION my_function LANGUAGE plpgsql++'FunctionLanguage' provides a 'RawSql.SqlExpression' instance. See+'RawSql.unsafeSqlExpression' for how to construct a value with your own custom+SQL.++@since 1.1.0.0+-}+newtype FunctionLanguage+  = FunctionLanguage RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++{- | Constructs a @LANGUAGE@ specifier for a @CREATE FUNCTION@ statement with the+  given language name as the language.++@since 1.1.0.0+-}+language :: LanguageName -> FunctionLanguage+language name =+  FunctionLanguage $ RawSql.fromString "LANGUAGE " <> RawSql.toRawSql name++{- | Type to represent the language that the function definition is given in as part+of a SQL "CREATE FUNCTION" statement. E.G. the @plpgsql@ in++> CREATE FUNCTION my_function LANGUAGE plpgsql++'LanguageName' provides a 'RawSql.SqlExpression' instance. See+'RawSql.unsafeSqlExpression' for how to construct a value with your own custom+SQL.++@since 1.1.0.0+-}+newtype LanguageName+  = LanguageName RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++{- | The @plpgsql@ language.++@since 1.1.0.0+-}+plpgsql :: LanguageName+plpgsql =+  LanguageName (RawSql.fromString "plpgsql")++{- | Type to represent the definition body of a a SQL "CREATE FUNCTION" statement. E.G. the+@AS <definition>@ in++> CREATE FUNCTION my_function AS <definition>++'FunctionDefinition' provides a 'RawSql.SqlExpression' instance. See+'RawSql.unsafeSqlExpression' for how to construct a value with your own custom+SQL.++@since 1.1.0.0+-}+newtype FunctionDefinition+  = FunctionDefinition RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++{- | Constructs a 'FunctionDefinition' from a 'String', which will be passed as an+  escaped string literal to PosgreSQL.++@since 1.1.0.0+-}+asDefinition :: String -> FunctionDefinition+asDefinition body =+  FunctionDefinition $+    RawSql.fromString "AS " <> RawSql.stringLiteral (BS8.pack body)
src/Orville/PostgreSQL/Expr/GroupBy.hs view
@@ -1,7 +1,7 @@ {-# LANGUAGE GeneralizedNewtypeDeriving #-}  {- |-Copyright : Flipstone Technology Partners 2023+Copyright : Flipstone Technology Partners 2023-2024 License   : MIT Stability : Stable @@ -18,11 +18,10 @@  import Data.List.NonEmpty (NonEmpty) -import Orville.PostgreSQL.Expr.Name (ColumnName)+import Orville.PostgreSQL.Expr.Name (ColumnName, QualifiedOrUnqualified) import qualified Orville.PostgreSQL.Raw.RawSql as RawSql -{- |-Type to represent a SQL group by clause. E.G.+{- | Type to represent a SQL group by clause. E.G.  > GROUP BY team_name @@ -46,8 +45,7 @@ groupByClause :: GroupByExpr -> GroupByClause groupByClause expr = GroupByClause (RawSql.fromString "GROUP BY " <> RawSql.toRawSql expr) -{- |-Type to represent a SQL group by expression (the part that follows the+{- | Type to represent a SQL group by expression (the part that follows the @GROUP BY@ in SQL). E.G.  > team_name@@ -83,6 +81,6 @@  @since 1.0.0.0 -}-groupByColumnsExpr :: NonEmpty ColumnName -> GroupByExpr+groupByColumnsExpr :: NonEmpty (QualifiedOrUnqualified ColumnName) -> GroupByExpr groupByColumnsExpr =   GroupByExpr . RawSql.intercalate RawSql.commaSpace
src/Orville/PostgreSQL/Expr/IfExists.hs view
@@ -15,8 +15,7 @@  import qualified Orville.PostgreSQL.Raw.RawSql as RawSql -{- |-Type to represent a SQL "IF EXISTS" expression. E.G.+{- | Type to represent a SQL "IF EXISTS" expression. E.G.  > IF EXISTS @@ -33,8 +32,7 @@       RawSql.SqlExpression     ) -{- |-A value of the SQL "IF EXISTS".+{- | A value of the SQL "IF EXISTS".  @since 1.0.0.0 -}
+ src/Orville/PostgreSQL/Expr/IfNotExists.hs view
@@ -0,0 +1,44 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++{- |+Copyright : Flipstone Technology Partners 2024+License   : MIT+Stability : Stable++@since 1.1.0.0+-}+module Orville.PostgreSQL.Expr.IfNotExists+  ( IfNotExists+  , ifNotExists+  )+where++import qualified Orville.PostgreSQL.Raw.RawSql as RawSql++{- | Type to represent a SQL "IF NOT EXISTS" expression. E.G.++> IF NOT EXISTS++It is notable that 'IfNotExists' and 'Orville.PostgreSQL.Expr.IfExists' are not usable in all the+same places.++'IfNotExists' provides a 'RawSql.SqlExpression' instance. See+'RawSql.unsafeSqlExpression' for how to construct a value with your own custom+SQL.++@since 1.1.0.0+-}+newtype IfNotExists+  = IfNotExists RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++{- | A value of the SQL "IF NOT EXISTS".++@since 1.1.0.0+-}+ifNotExists :: IfNotExists+ifNotExists =+  IfNotExists $ RawSql.fromString "IF NOT EXISTS"
src/Orville/PostgreSQL/Expr/Index.hs view
@@ -23,11 +23,10 @@  import Data.List.NonEmpty (NonEmpty) -import Orville.PostgreSQL.Expr.Name (ColumnName, IndexName, Qualified, TableName)+import Orville.PostgreSQL.Expr.Name (ColumnName, IndexName, QualifiedOrUnqualified, TableName) import qualified Orville.PostgreSQL.Raw.RawSql as RawSql -{- |-Type to represent a SQL "CREATE INDEX" statement. E.G.+{- | Type to represent a SQL "CREATE INDEX" statement. E.G.  > CREATE INDEX ON table (foo, bar, baz) @@ -44,8 +43,7 @@       RawSql.SqlExpression     ) -{- |-Construct a SQL CREATE INDEX from an indicator of if the index should be+{- | Construct a SQL CREATE INDEX from an indicator of if the index should be unique, a table, and corresponding collection of 'ColumnName's.  @since 1.0.0.0@@ -53,7 +51,7 @@ createIndexExpr ::   IndexUniqueness ->   Maybe ConcurrentlyExpr ->-  Qualified TableName ->+  QualifiedOrUnqualified TableName ->   NonEmpty ColumnName ->   CreateIndexExpr createIndexExpr uniqueness mbConcurrently tableName columns =@@ -61,14 +59,13 @@     RawSql.fromString "CREATE "       <> uniquenessToSql uniqueness       <> RawSql.fromString "INDEX "-      <> maybe mempty (<> RawSql.space) (fmap RawSql.toRawSql mbConcurrently)+      <> maybe mempty ((<> RawSql.space) . RawSql.toRawSql) mbConcurrently       <> RawSql.fromString "ON "       <> RawSql.toRawSql tableName       <> RawSql.space       <> RawSql.toRawSql (indexBodyColumns columns) -{- |-Construct a SQL CREATE INDEX from an indicator of if the index should be+{- | Construct a SQL CREATE INDEX from an indicator of if the index should be unique, a table, a name for the index, and some SQL representing the rest of the index creation. @@ -77,7 +74,7 @@ createNamedIndexExpr ::   IndexUniqueness ->   Maybe ConcurrentlyExpr ->-  Qualified TableName ->+  QualifiedOrUnqualified TableName ->   IndexName ->   IndexBodyExpr ->   CreateIndexExpr@@ -86,15 +83,14 @@     RawSql.fromString "CREATE "       <> uniquenessToSql uniqueness       <> RawSql.fromString "INDEX "-      <> maybe mempty (<> RawSql.space) (fmap RawSql.toRawSql mbConcurrently)+      <> maybe mempty ((<> RawSql.space) . RawSql.toRawSql) mbConcurrently       <> RawSql.toRawSql indexName       <> RawSql.fromString " ON "       <> RawSql.toRawSql tableName       <> RawSql.space       <> RawSql.toRawSql bodyExpr -{- |-Type to represent the @CONCURRENTLY@ keyword for index creation. E.G.+{- | Type to represent the @CONCURRENTLY@ keyword for index creation. E.G.  > CONCURRENTLY @@ -111,8 +107,7 @@       RawSql.SqlExpression     ) -{- |-The @CONCURRENTLY@ keyword indicates to PostgreSQL that an index should be+{- | The @CONCURRENTLY@ keyword indicates to PostgreSQL that an index should be created concurrently.  @since 1.0.0.0@@ -121,8 +116,7 @@ concurrently =   RawSql.unsafeSqlExpression "CONCURRENTLY" -{- |-Type to represent the body of an index definition E.G.+{- | Type to represent the body of an index definition E.G.  > (foo, bar) @@ -143,8 +137,7 @@       RawSql.SqlExpression     ) -{- |-Creates an 'IndexBodyExpr' for the given column names. The resulting+{- | Creates an 'IndexBodyExpr' for the given column names. The resulting SQL looks like @(column1, column2, ...)@.  @since 1.0.0.0@@ -158,8 +151,7 @@       <> RawSql.intercalate RawSql.comma columns       <> RawSql.rightParen -{- |-Type to represent if an index should be unique.+{- | Type to represent if an index should be unique.  @since 1.0.0.0 -}@@ -182,8 +174,7 @@     UniqueIndex -> RawSql.fromString "UNIQUE "     NonUniqueIndex -> mempty -{- |-Type to represent a SQL "DROP INDEX" statement. E.G.+{- | Type to represent a SQL "DROP INDEX" statement. E.G.  > DROP INDEX foo @@ -200,12 +191,11 @@       RawSql.SqlExpression     ) -{- |-Construct a SQL DROP INDEX for a given 'IndexName'.+{- | Construct a SQL DROP INDEX for a given 'IndexName'.  @since 1.0.0.0 -}-dropIndexExpr :: IndexName -> DropIndexExpr+dropIndexExpr :: QualifiedOrUnqualified IndexName -> DropIndexExpr dropIndexExpr indexName =   DropIndexExpr $     RawSql.fromString "DROP INDEX " <> RawSql.toRawSql indexName
src/Orville/PostgreSQL/Expr/Insert.hs view
@@ -1,7 +1,7 @@ {-# LANGUAGE GeneralizedNewtypeDeriving #-}  {- |-Copyright : Flipstone Technology Partners 2023+Copyright : Flipstone Technology Partners 2023-2024 License   : MIT Stability : Stable @@ -16,18 +16,21 @@   , insertSqlValues   , RowValues   , rowValues+  , valuesExprInsertSource   ) where  import Data.Maybe (catMaybes) -import Orville.PostgreSQL.Expr.Name (ColumnName, Qualified, TableName)+import qualified Data.List.NonEmpty as NE+import Orville.PostgreSQL.Expr.Name (ColumnName, QualifiedOrUnqualified, TableName)+import Orville.PostgreSQL.Expr.OnConflict (OnConflictExpr) import Orville.PostgreSQL.Expr.ReturningExpr (ReturningExpr)+import Orville.PostgreSQL.Expr.Values (ValuesExpr) import qualified Orville.PostgreSQL.Raw.RawSql as RawSql import Orville.PostgreSQL.Raw.SqlValue (SqlValue) -{- |-Type to represent a SQL "INSERT" statement. E.G.+{- | Type to represent a SQL "INSERT" statement. E.G.  > INSERT INTO foo (id) VALUES (1),(3),(3) @@ -52,12 +55,13 @@ @since 1.0.0.0 -} insertExpr ::-  Qualified TableName ->+  QualifiedOrUnqualified TableName ->   Maybe InsertColumnList ->   InsertSource ->+  Maybe OnConflictExpr ->   Maybe ReturningExpr ->   InsertExpr-insertExpr target maybeInsertColumns source maybeReturning =+insertExpr target maybeInsertColumns source maybeOnConflict maybeReturning =   InsertExpr     . RawSql.intercalate RawSql.space     $ catMaybes@@ -65,11 +69,11 @@       , Just $ RawSql.toRawSql target       , fmap RawSql.toRawSql maybeInsertColumns       , Just $ RawSql.toRawSql source+      , fmap RawSql.toRawSql maybeOnConflict       , fmap RawSql.toRawSql maybeReturning       ] -{- |-Type to represent the SQL columns list for an insert statement. E.G.+{- | Type to represent the SQL columns list for an insert statement. E.G.  > (foo,bar,baz) @@ -91,15 +95,15 @@  @since 1.0.0.0 -}-insertColumnList :: [ColumnName] -> InsertColumnList+insertColumnList :: [QualifiedOrUnqualified ColumnName] -> InsertColumnList insertColumnList columnNames =   InsertColumnList $-    RawSql.leftParen-      <> RawSql.intercalate RawSql.comma (fmap RawSql.toRawSql columnNames)-      <> RawSql.rightParen+    maybe+      mempty+      (RawSql.parenthesized . RawSql.intercalate RawSql.comma . fmap RawSql.toRawSql)+      (NE.nonEmpty columnNames) -{- |-Type to represent the SQL for the source of data for an insert statement. E.G.+{- | Type to represent the SQL for the source of data for an insert statement. E.G.  > VALUES ('Bob',32),('Cindy',33) @@ -116,6 +120,15 @@       RawSql.SqlExpression     ) +{- | Use a 'ValuesExpr' as an 'InsertSource'.++@since 1.1.0.0+-}+valuesExprInsertSource :: ValuesExpr -> InsertSource+valuesExprInsertSource = InsertSource . RawSql.toRawSql++{-# DEPRECATED insertRowValues "Use Orville.PostgreSQL.Expr.valuesExpr and Orville.PostgreSQL.Expr.valuesExprInsertSource" #-}+ {- | Create an 'InsertSource' for the given 'RowValues'. This ensures that all input values are used as parameters and comma-separated in the generated SQL. @@ -127,6 +140,8 @@     RawSql.fromString "VALUES "       <> RawSql.intercalate RawSql.comma (fmap RawSql.toRawSql rows) +{-# DEPRECATED insertSqlValues "Use Orville.PostgreSQL.Expr.ValuesExpr and Orivlle.PostgreSQL.Expr.valuesExprInsertSource" #-}+ {- | Create an 'InsertSource' for the given 'SqlValue's. This ensures that all input values are used as parameters and comma-separated in the generated SQL. @@ -136,8 +151,7 @@ insertSqlValues =   insertRowValues . fmap rowValues -{- |-Type to represent a SQL row literal. For example, a single row to insert+{- | Type to represent a SQL row literal. For example, a single row to insert in a @VALUES@ clause. E.G.  > ('Cindy',33)
+ src/Orville/PostgreSQL/Expr/Internal/Name/Alias.hs view
@@ -0,0 +1,46 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++{- |+Copyright : Flipstone Technology Partners 2024-2205+License   : MIT+Stability : Stable++@since 1.1.0.0+-}+module Orville.PostgreSQL.Expr.Internal.Name.Alias+  ( AliasExpr+  , stringToAliasExpr+  )+where++import Orville.PostgreSQL.Expr.Internal.Name.Identifier (Identifier, IdentifierExpression, identifier)+import qualified Orville.PostgreSQL.Raw.RawSql as RawSql++{- | Type to represent a SQL alias. 'AliasExpr' values constructed+via the 'alias' function will be properly escaped as part of the+generated SQL. E.G.++> "some_alias"++'AliasExpr' provides a 'RawSql.SqlExpression' instance. See+'RawSql.unsafeSqlExpression' for how to construct a value with your own custom+SQL.++@since 1.1.0.0+-}+newtype AliasExpr+  = AliasExpr Identifier+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    , -- | @since 1.1.0.0+      IdentifierExpression+    )++{- | Construct an 'AliasExpr' from a 'String' with proper escaping as part of the generated SQL.++@since 1.1.0.0+-}+stringToAliasExpr :: String -> AliasExpr+stringToAliasExpr =+  AliasExpr . identifier
src/Orville/PostgreSQL/Expr/Internal/Name/ColumnName.hs view
@@ -16,8 +16,7 @@ import Orville.PostgreSQL.Expr.Internal.Name.Identifier (Identifier, IdentifierExpression, identifier) import qualified Orville.PostgreSQL.Raw.RawSql as RawSql -{- |-Type to represent a SQL column name. 'ColumnName' values constructed via the+{- | Type to represent a SQL column name. 'ColumnName' values constructed via the 'columnName' function will be properly escaped as part of the generated SQL. E.G.  > "some_column_name"@@ -37,8 +36,7 @@       IdentifierExpression     ) -{- |-Construct a 'ColumnName' from a 'String' with proper escaping as part of the generated SQL.+{- | Construct a 'ColumnName' from a 'String' with proper escaping as part of the generated SQL.  @since 1.0.0.0 -}
src/Orville/PostgreSQL/Expr/Internal/Name/ConstraintName.hs view
@@ -16,8 +16,7 @@ import Orville.PostgreSQL.Expr.Internal.Name.Identifier (Identifier, IdentifierExpression, identifier) import qualified Orville.PostgreSQL.Raw.RawSql as RawSql -{- |-Type to represent a SQL constraint name. 'ConstraintName' values constructed+{- | Type to represent a SQL constraint name. 'ConstraintName' values constructed via the 'constraintName' function will be properly escaped as part of the generated SQL. E.G. @@ -38,8 +37,7 @@       IdentifierExpression     ) -{- |-Construct a 'ConstraintName' from a 'String' with proper escaping as part of the generated SQL.+{- | Construct a 'ConstraintName' from a 'String' with proper escaping as part of the generated SQL.  @since 1.0.0.0 -}
src/Orville/PostgreSQL/Expr/Internal/Name/CursorName.hs view
@@ -16,8 +16,7 @@ import Orville.PostgreSQL.Expr.Internal.Name.Identifier (Identifier, IdentifierExpression, identifier) import qualified Orville.PostgreSQL.Raw.RawSql as RawSql -{- |-Type to represent a SQL cursor name. 'CursorName' values constructed+{- | Type to represent a SQL cursor name. 'CursorName' values constructed via the 'cursorName' function will be properly escaped as part of the generated SQL. E.G. @@ -38,8 +37,7 @@       IdentifierExpression     ) -{- |-Construct a 'CursorName' from a 'String' with proper escaping as part of the generated SQL.+{- | Construct a 'CursorName' from a 'String' with proper escaping as part of the generated SQL.  @since 1.0.0.0 -}
+ src/Orville/PostgreSQL/Expr/Internal/Name/ExtensionName.hs view
@@ -0,0 +1,44 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++{- |+Copyright : Flipstone Technology Partners 2024+License   : MIT+Stability : Stable++@since 1.1.0.0+-}+module Orville.PostgreSQL.Expr.Internal.Name.ExtensionName+  ( ExtensionName+  , extensionName+  )+where++import Orville.PostgreSQL.Expr.Internal.Name.Identifier (Identifier, IdentifierExpression, identifier)+import qualified Orville.PostgreSQL.Raw.RawSql as RawSql++{- | Type to represent a PostgreSQL extension name. 'ExtensionName' values constructed via the+'extensionName' function will be properly escaped as part of the generated SQL. E.G.++> "some_extension_name"++'ExtensionName' provides a 'RawSql.SqlExpression' instance. See+'RawSql.unsafeSqlExpression' for how to construct a value with your own custom+SQL.++@since 1.1.0.0+-}+newtype ExtensionName+  = ExtensionName Identifier+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    , -- | @since 1.1.0.0+      IdentifierExpression+    )++{- | Construct an 'ExtensionName' from a 'String'.++@since 1.1.0.0+-}+extensionName :: String -> ExtensionName+extensionName = ExtensionName . identifier
src/Orville/PostgreSQL/Expr/Internal/Name/FunctionName.hs view
@@ -10,14 +10,37 @@ module Orville.PostgreSQL.Expr.Internal.Name.FunctionName   ( FunctionName   , functionName+  , arrayAggFunctionName+  , avgFunctionName+  , bitAndFunctionName+  , bitOrFunctionName+  , bitXorFunctionName+  , boolAndFunctionName+  , boolOrFunctionName+  , countFunctionName+  , everyFunctionName+  , jsonAggFunctionName+  , jsonObjectAggFunctionName+  , jsonbAggFunctionName+  , jsonbObjectAggFunctionName+  , maxFunctionName+  , minFunctionName+  , stringAggFunctionName+  , sumFunctionName+  , corFunctionName+  , covarPopFunctionName+  , covarSampFunctionName+  , stddevPopFunctionName+  , stddevSampFunctionName+  , varPopFunctionName+  , varSampFunctionName   ) where  import Orville.PostgreSQL.Expr.Internal.Name.Identifier (Identifier, IdentifierExpression, identifier) import qualified Orville.PostgreSQL.Raw.RawSql as RawSql -{- |-Type to represent a SQL function name. 'FunctionName' values constructed+{- | Type to represent a SQL function name. 'FunctionName' values constructed via the 'functionName' function will be properly escaped as part of the generated SQL. E.G. @@ -38,11 +61,178 @@       IdentifierExpression     ) -{- |-Construct a 'FunctionName' from a 'String' with proper escaping as part of the generated SQL.+{- | Construct a 'FunctionName' from a 'String' with proper escaping as part of the generated SQL.  @since 1.0.0.0 -} functionName :: String -> FunctionName functionName =   FunctionName . identifier++{- | The @array_agg@ window function name++@since 1.1.0.0+-}+arrayAggFunctionName :: FunctionName+arrayAggFunctionName = functionName "array_agg"++{- | The @avg@ window function name++@since 1.1.0.0+-}+avgFunctionName :: FunctionName+avgFunctionName = functionName "avg"++{- | The @bit_and@ window function name++@since 1.1.0.0+-}+bitAndFunctionName :: FunctionName+bitAndFunctionName = functionName "bit_and"++{- | The @bit_or@ window function name++@since 1.1.0.0+-}+bitOrFunctionName :: FunctionName+bitOrFunctionName = functionName "bit_or"++{- | The @bit_xor@ window function name++@since 1.1.0.0+-}+bitXorFunctionName :: FunctionName+bitXorFunctionName = functionName "bit_xor"++{- | The @bool_and@ window function name++@since 1.1.0.0+-}+boolAndFunctionName :: FunctionName+boolAndFunctionName = functionName "bool_and"++{- | The @bool_or@ window function name++@since 1.1.0.0+-}+boolOrFunctionName :: FunctionName+boolOrFunctionName = functionName "bool_or"++{- | The @count@ window function name++@since 1.1.0.0+-}+countFunctionName :: FunctionName+countFunctionName = functionName "count"++{- | The @every@ window function name++@since 1.1.0.0+-}+everyFunctionName :: FunctionName+everyFunctionName = functionName "every"++{- | The @json_agg@ window function name++@since 1.1.0.0+-}+jsonAggFunctionName :: FunctionName+jsonAggFunctionName = functionName "json_agg"++{- | The @json_object_agg@ window function name++@since 1.1.0.0+-}+jsonObjectAggFunctionName :: FunctionName+jsonObjectAggFunctionName = functionName "json_object_agg"++{- | The @jsonb_agg@ window function name++@since 1.1.0.0+-}+jsonbAggFunctionName :: FunctionName+jsonbAggFunctionName = functionName "jsonb_agg"++{- | The @jsonb_object_agg@ window function name++@since 1.1.0.0+-}+jsonbObjectAggFunctionName :: FunctionName+jsonbObjectAggFunctionName = functionName "jsonb_object_agg"++{- | The @max@ window function name++@since 1.1.0.0+-}+maxFunctionName :: FunctionName+maxFunctionName = functionName "max"++{- | The @min@ window function name++@since 1.1.0.0+-}+minFunctionName :: FunctionName+minFunctionName = functionName "min"++{- | The @string_agg@ window function name++@since 1.1.0.0+-}+stringAggFunctionName :: FunctionName+stringAggFunctionName = functionName "string_agg"++{- | The @sum@ window function name++@since 1.1.0.0+-}+sumFunctionName :: FunctionName+sumFunctionName = functionName "sum"++{- | The @cor@ window function name++@since 1.1.0.0+-}+corFunctionName :: FunctionName+corFunctionName = functionName "cor"++{- | The @covar_pop@ window function name++@since 1.1.0.0+-}+covarPopFunctionName :: FunctionName+covarPopFunctionName = functionName "covar_pop"++{- | The @covar_samp@ window function name++@since 1.1.0.0+-}+covarSampFunctionName :: FunctionName+covarSampFunctionName = functionName "covar_samp"++{- | The @stddev_pop@ window function name++@since 1.1.0.0+-}+stddevPopFunctionName :: FunctionName+stddevPopFunctionName = functionName "stddev_pop"++{- | The @stddev_samp@ window function name++@since 1.1.0.0+-}+stddevSampFunctionName :: FunctionName+stddevSampFunctionName = functionName "stddev_samp"++{- | The @var_pop@ window function name++@since 1.1.0.0+-}+varPopFunctionName :: FunctionName+varPopFunctionName = functionName "var_pop"++{- | The @var_samp@ window function name++@since 1.1.0.0+-}+varSampFunctionName :: FunctionName+varSampFunctionName = functionName "var_samp"
src/Orville/PostgreSQL/Expr/Internal/Name/Identifier.hs view
@@ -18,8 +18,7 @@ import qualified Data.ByteString.Char8 as B8 import qualified Orville.PostgreSQL.Raw.RawSql as RawSql -{- |-Type to represent a SQL identifier. 'Identifier' values constructed via the+{- | Type to represent a SQL identifier. 'Identifier' values constructed via the 'identifier' function will be properly escaped as part of the generated SQL. E.G. @@ -38,8 +37,7 @@       RawSql.SqlExpression     ) -{- |-Construct an 'Identifier' from a 'String' with proper escaping as part of the generated SQL.+{- | Construct an 'Identifier' from a 'String' with proper escaping as part of the generated SQL.  @since 1.0.0.0 -}@@ -47,8 +45,7 @@ identifier =   identifierFromBytes . B8.pack -{- |-Construct an 'Identifier' from a 'B8.ByteString' with proper escaping as part of the generated SQL.+{- | Construct an 'Identifier' from a 'B8.ByteString' with proper escaping as part of the generated SQL.  @since 1.0.0.0 -}
src/Orville/PostgreSQL/Expr/Internal/Name/IndexName.hs view
@@ -16,8 +16,7 @@ import Orville.PostgreSQL.Expr.Internal.Name.Identifier (Identifier, IdentifierExpression, identifier) import qualified Orville.PostgreSQL.Raw.RawSql as RawSql -{- |-Type to represent a SQL index name. 'IndexName' values constructed via the+{- | Type to represent a SQL index name. 'IndexName' values constructed via the 'indexName' function will be properly escaped as part of the generated SQL. E.G.  > "some_index_name"@@ -37,8 +36,7 @@       IdentifierExpression     ) -{- |-Construct an 'IndexName' from a 'String' with proper escaping as part of the generated SQL.+{- | Construct an 'IndexName' from a 'String' with proper escaping as part of the generated SQL.  @since 1.0.0.0 -}
src/Orville/PostgreSQL/Expr/Internal/Name/Qualified.hs view
@@ -1,7 +1,7 @@ {-# LANGUAGE GeneralizedNewtypeDeriving #-}  {- |-Copyright : Flipstone Technology Partners 2023+Copyright : Flipstone Technology Partners 2023-2025 License   : MIT Stability : Stable @@ -11,22 +11,69 @@   ( Qualified   , qualifyTable   , qualifySequence+  , qualifyFunction+  , qualifyIndex   , qualifyColumn+  , aliasQualifyColumn+  , QualifiedOrUnqualified+  , untrackQualified+  , unqualified   ) where +import Orville.PostgreSQL.Expr.Internal.Name.Alias (AliasExpr) import Orville.PostgreSQL.Expr.Internal.Name.ColumnName (ColumnName)+import Orville.PostgreSQL.Expr.Internal.Name.FunctionName (FunctionName) import Orville.PostgreSQL.Expr.Internal.Name.Identifier (IdentifierExpression (toIdentifier))+import Orville.PostgreSQL.Expr.Internal.Name.IndexName (IndexName) import Orville.PostgreSQL.Expr.Internal.Name.SchemaName (SchemaName) import Orville.PostgreSQL.Expr.Internal.Name.SequenceName (SequenceName) import Orville.PostgreSQL.Expr.Internal.Name.TableName (TableName) import qualified Orville.PostgreSQL.Raw.RawSql as RawSql -{- |-Type to represent a qualified SQL name. E.G.+{- | Type to represent a SQL name that could be qualified or not. E.G.  > "some_schema_name"."some_table_name" +or++> "some_table_name"++'QualifiedOrUnqualified' provides a 'RawSql.SqlExpression' instance. See+'RawSql.unsafeSqlExpression' for how to construct a value with your own custom+SQL.++@since 1.1.0.0+-}+newtype QualifiedOrUnqualified name+  = QualifiedOrUnqualified RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++{- | 'untrackQualified' loses the information of the value certainly having a qualification. This is+   useful when working with functionality that takes a 'QualifiedOrUnqualifed name'.++@since 1.1.0.0+-}+untrackQualified :: Qualified name -> QualifiedOrUnqualified name+untrackQualified =+  QualifiedOrUnqualified . RawSql.toRawSql++{- | 'unqualified' loses the information of the value not having a qualification. This is+   useful when working with functionality that takes a 'QualifiedOrUnqualifed name'.++@since 1.1.0.0+-}+unqualified :: RawSql.SqlExpression name => name -> QualifiedOrUnqualified name+unqualified =+  QualifiedOrUnqualified . RawSql.toRawSql++{- | Type to represent a qualified SQL name. E.G.++> "some_schema_name"."some_table_name"+ 'Qualified' provides a 'RawSql.SqlExpression' instance. See 'RawSql.unsafeSqlExpression' for how to construct a value with your own custom SQL.@@ -40,8 +87,7 @@       RawSql.SqlExpression     ) -{- |-Optionally qualifies a 'TableName' with a 'SchemaName'.+{- | Qualifies a 'TableName' with a 'SchemaName'.  Note: If you already have a 'Orville.PostgreSQL.Schema.TableIdentifier' in hand you should probably use@@ -49,13 +95,12 @@ @since 1.0.0.0 -} qualifyTable ::-  Maybe SchemaName ->+  SchemaName ->   TableName ->   Qualified TableName qualifyTable = unsafeSchemaQualify -{- |-Optionally qualifies a 'SequenceName' with a 'SchemaName'.+{- | Qualifies a 'SequenceName' with a 'SchemaName'.  Note: If you already have a 'Orville.PostgreSQL.Schema.SequenceIdentifier' in hand you should probably use@@ -64,13 +109,35 @@ @since 1.0.0.0 -} qualifySequence ::-  Maybe SchemaName ->+  SchemaName ->   SequenceName ->   Qualified SequenceName qualifySequence = unsafeSchemaQualify -{- |-Qualifies a 'ColumnName' with a 'TableName' and, optionally, a 'SchemaName'.+{- | Qualifies a 'FunctionName' with a 'SchemaName'.++Note: If you already have a 'Orville.PostgreSQL.Schema.FunctionIdentifier' in+hand you should probably use+'Orville.PostgreSQL.Schema.functionIdQualifiedName' instead.++@since 1.1.0.0+-}+qualifyFunction ::+  SchemaName ->+  FunctionName ->+  Qualified FunctionName+qualifyFunction = unsafeSchemaQualify++{- | Qualifies an 'IndexName' with a 'SchemaName'.+@since 1.1.0.0+-}+qualifyIndex ::+  SchemaName ->+  IndexName ->+  Qualified IndexName+qualifyIndex = unsafeSchemaQualify++{- | Qualifies a 'ColumnName' with a 'TableName' and, optionally, a 'SchemaName'. This should be used to refer to the column in SQL queries where a qualified reference is appropriate. @@ -78,25 +145,47 @@ -} qualifyColumn :: Maybe SchemaName -> TableName -> ColumnName -> Qualified ColumnName qualifyColumn mbSchemaName tableName unqualifiedName =-  unsafeSchemaQualify mbSchemaName-    . RawSql.unsafeFromRawSql-    $ RawSql.toRawSql (toIdentifier tableName) <> RawSql.dot <> RawSql.toRawSql (toIdentifier unqualifiedName)+  let+    tableQualifiedColumn =+      rawQualify tableName unqualifiedName+  in+    case mbSchemaName of+      Nothing ->+        Qualified tableQualifiedColumn+      Just schemaName ->+        Qualified (rawQualify schemaName tableQualifiedColumn) +{- | Qualifies a 'ColumnName' with an 'AliasExpr'. This should be used to refer to the column+in SQL queries where an aliased reference is appropriate.++@since 1.1.0.0+-}+aliasQualifyColumn :: AliasExpr -> ColumnName -> Qualified ColumnName+aliasQualifyColumn aliasName =+  Qualified . rawQualify aliasName+ -- Note: Not everything actually makes sense to be qualified by _only_ a schema name, such as -- columns, as in 'qualifyColumn'. But this does give us a nice uniform way to provide the -- functionality in those more type restricted scenarios. unsafeSchemaQualify ::   IdentifierExpression name =>-  Maybe SchemaName ->+  SchemaName ->   name ->   Qualified name-unsafeSchemaQualify mbSchemaName unqualifiedName =-  case mbSchemaName of-    Nothing ->-      Qualified . RawSql.toRawSql . toIdentifier $ unqualifiedName-    Just schemaName ->-      Qualified-        ( RawSql.toRawSql schemaName-            <> RawSql.dot-            <> RawSql.toRawSql (toIdentifier unqualifiedName)-        )+unsafeSchemaQualify schemaName =+  Qualified . rawQualify schemaName . toIdentifier++{- |+  An internal helper to qualify any sql expressions as raw sql. The types for this+  function do not enforce any relationship between the qualifier and the qualified+  name, and the result is 'RawSql' rather than any more specific expression type.+-}+rawQualify ::+  (RawSql.SqlExpression qualifier, RawSql.SqlExpression name) =>+  qualifier ->+  name ->+  RawSql.RawSql+rawQualify qualifier name =+  RawSql.toRawSql qualifier+    <> RawSql.dot+    <> RawSql.toRawSql name
src/Orville/PostgreSQL/Expr/Internal/Name/SavepointName.hs view
@@ -16,8 +16,7 @@ import Orville.PostgreSQL.Expr.Internal.Name.Identifier (Identifier, IdentifierExpression, identifier) import qualified Orville.PostgreSQL.Raw.RawSql as RawSql -{- |-Type to represent a SQL savepoint name. 'SavepointName' values constructed via+{- | Type to represent a SQL savepoint name. 'SavepointName' values constructed via the 'savepointName' function will be properly escaped as part of the generated SQL. E.G. @@ -38,8 +37,7 @@       IdentifierExpression     ) -{- |-Construct a 'SavepointName' from a 'String' with proper escaping as part of the generated SQL.+{- | Construct a 'SavepointName' from a 'String' with proper escaping as part of the generated SQL.  @since 1.0.0.0 -}
src/Orville/PostgreSQL/Expr/Internal/Name/SchemaName.hs view
@@ -16,8 +16,7 @@ import Orville.PostgreSQL.Expr.Internal.Name.Identifier (Identifier, IdentifierExpression, identifier) import qualified Orville.PostgreSQL.Raw.RawSql as RawSql -{- |-Type to represent a SQL schema name. 'SchemaName' values constructed via the+{- | Type to represent a SQL schema name. 'SchemaName' values constructed via the 'schemaName' function will be properly escaped as part of the generated SQL. E.G. @@ -38,8 +37,7 @@       IdentifierExpression     ) -{- |-Construct a 'SchemaName' from a 'String' with proper escaping as part of the generated SQL.+{- | Construct a 'SchemaName' from a 'String' with proper escaping as part of the generated SQL.  @since 1.0.0.0 -}
src/Orville/PostgreSQL/Expr/Internal/Name/SequenceName.hs view
@@ -16,8 +16,7 @@ import Orville.PostgreSQL.Expr.Internal.Name.Identifier (Identifier, IdentifierExpression, identifier) import qualified Orville.PostgreSQL.Raw.RawSql as RawSql -{- |-Type to represent a SQL sequence name. 'SequenceName' values constructed via+{- | Type to represent a SQL sequence name. 'SequenceName' values constructed via the 'sequenceName' function will be properly escaped as part of the generated SQL. E.G. @@ -38,8 +37,7 @@       IdentifierExpression     ) -{- |-Construct a 'SequenceName' from a 'String' with proper escaping as part of the generated SQL.+{- | Construct a 'SequenceName' from a 'String' with proper escaping as part of the generated SQL.  @since 1.0.0.0 -}
src/Orville/PostgreSQL/Expr/Internal/Name/TableName.hs view
@@ -16,8 +16,7 @@ import Orville.PostgreSQL.Expr.Internal.Name.Identifier (Identifier, IdentifierExpression, identifier) import qualified Orville.PostgreSQL.Raw.RawSql as RawSql -{- |-Type to represent a SQL table name. 'TableName' values constructed via the+{- | Type to represent a SQL table name. 'TableName' values constructed via the 'tableName' function will be properly escaped as part of the generated SQL. E.G. @@ -38,8 +37,7 @@       IdentifierExpression     ) -{- |-Construct a 'TableName' from a 'String' with proper escaping as part of the generated SQL.+{- | Construct a 'TableName' from a 'String' with proper escaping as part of the generated SQL.  @since 1.0.0.0 -}
+ src/Orville/PostgreSQL/Expr/Internal/Name/TriggerName.hs view
@@ -0,0 +1,46 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++{- |+Copyright : Flipstone Technology Partners 2024+License   : MIT+Stability : Stable++@since 1.1.0.0+-}+module Orville.PostgreSQL.Expr.Internal.Name.TriggerName+  ( TriggerName+  , triggerName+  )+where++import Orville.PostgreSQL.Expr.Internal.Name.Identifier (Identifier, IdentifierExpression, identifier)+import qualified Orville.PostgreSQL.Raw.RawSql as RawSql++{- | Type to represent a SQL trigger name. 'TriggerName' values constructed via the+'triggerName' function will be properly escaped as part of the generated SQL.+E.G.++> "some_trigger_name"++'TriggerName' provides a 'RawSql.SqlExpression' instance. See+'RawSql.unsafeSqlExpression' for how to construct a value with your own custom+SQL.++@since 1.1.0.0+-}+newtype TriggerName+  = TriggerName Identifier+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    , -- | @since 1.1.0.0+      IdentifierExpression+    )++{- | Construct a 'TriggerName' from a 'String' with proper escaping as part of the generated SQL.++@since 1.1.0.0+-}+triggerName :: String -> TriggerName+triggerName =+  TriggerName . identifier
+ src/Orville/PostgreSQL/Expr/Internal/Name/WindowName.hs view
@@ -0,0 +1,46 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++{- |+Copyright : Flipstone Technology Partners 2024+License   : MIT+Stability : Stable++@since 1.1.0.0+-}+module Orville.PostgreSQL.Expr.Internal.Name.WindowName+  ( WindowName+  , windowName+  )+where++import Orville.PostgreSQL.Expr.Internal.Name.Identifier (Identifier, IdentifierExpression, identifier)+import qualified Orville.PostgreSQL.Raw.RawSql as RawSql++{- | Type to represent a SQL window name. 'WindowName' values constructed+via the 'windowName' window will be properly escaped as part of the+generated SQL. E.G.++> "some_window_name"++'WindowName' provides a 'RawSql.SqlExpression' instance. See+'RawSql.unsafeSqlExpression' for how to construct a value with your own custom+SQL.++@since 1.1.0.0+-}+newtype WindowName+  = WindowName Identifier+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    , -- | @since 1.1.0.0+      IdentifierExpression+    )++{- | Construct a 'WindowName' from a 'String' with proper escaping as part of the generated SQL.++@since 1.1.0.0+-}+windowName :: String -> WindowName+windowName =+  WindowName . identifier
+ src/Orville/PostgreSQL/Expr/Join.hs view
@@ -0,0 +1,189 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++{- |+Copyright : Flipstone Technology Partners 2024-2025+License   : MIT+Stability : Stable++@since 1.1.0.0+-}+module Orville.PostgreSQL.Expr.Join+  ( JoinType+  , innerJoinType+  , leftJoinType+  , rightJoinType+  , fullJoinType+  , leftLateralJoinType+  , innerLateralJoinType+  , JoinConstraint+  , joinOnConstraint+  , joinedTable+  , join+  , joining+  ) where++import Orville.PostgreSQL.Expr.TableReferenceList (TableReference)+import Orville.PostgreSQL.Expr.WhereClause (BooleanExpr)+import qualified Orville.PostgreSQL.Raw.RawSql as RawSql++{- | Representation of what kind of 'JOIN' to perform.++  From the documentation at https://www.postgresql.org/docs/15/sql-select.html there are 4 basic+  types of join.++  - Inner join+    Use 'innerJoinType' for constructing+  - Left join+    Sometimes called a left "outer" join. Use 'leftJoinType' for construction.+  - Right join+    Sometimes called a right "outer" join. use 'rightJoinType' for construction.+  - Full join+    Sometimes called a full "outer" join. Use 'fullJoinType' for construction.++@since 1.1.0.0+-}+newtype JoinType = JoinType RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++{- | Constructs a 'JoinType' for an INNER JOIN.++@since 1.1.0.0+-}+innerJoinType :: JoinType+innerJoinType = JoinType $ RawSql.fromString "INNER JOIN"++{- | Constructs a 'JoinType' for a LEFT JOIN.++  Note that the documentation at https://www.postgresql.org/docs/15/sql-select.html describes the+  keyword OUTER as optional, and the sql generated from here omits it for brevity of query.++@since 1.1.0.0+-}+leftJoinType :: JoinType+leftJoinType = JoinType $ RawSql.fromString "LEFT JOIN"++{- | Constructs a 'JoinType' for a RIGHT JOIN.++  Note that the documentation at https://www.postgresql.org/docs/15/sql-select.html describes the+  keyword OUTER as optional, and the sql generated from here omits it for brevity of query.++@since 1.1.0.0+-}+rightJoinType :: JoinType+rightJoinType = JoinType $ RawSql.fromString "RIGHT JOIN"++{- | Constructs a 'JoinType' for a FULL JOIN.++  Note that the documentation at https://www.postgresql.org/docs/15/sql-select.html describes the+  keyword OUTER as optional, and the sql generated from here omits it for brevity of query.++@since 1.1.0.0+-}+fullJoinType :: JoinType+fullJoinType = JoinType $ RawSql.fromString "FULL JOIN"++{- | Constructs a 'JoinType' for a LEFT JOIN LATERAL.++  Note that the documentation at https://www.postgresql.org/docs/15/sql-select.html describes the+  keyword OUTER as optional, and the sql generated from here omits it for brevity of query.++@since 1.1.0.0+-}+leftLateralJoinType :: JoinType+leftLateralJoinType = JoinType $ RawSql.fromString "LEFT JOIN LATERAL"++{- | Constructs a 'JoinType' for an INNER JOIN.++@since 1.1.0.0+-}+innerLateralJoinType :: JoinType+innerLateralJoinType = JoinType $ RawSql.fromString "JOIN LATERAL"++{- | Representation of the "ON" part of a JOIN in sql.++@since 1.1.0.0+-}+newtype JoinConstraint = JoinConstraint RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++{- | Constructs a 'JoinConstraint' from a given 'BooleanExpr' that specifies which rows in the JOIN are+   considering to match.++@since 1.1.0.0+-}+joinOnConstraint :: BooleanExpr -> JoinConstraint+joinOnConstraint booleanExpr =+  JoinConstraint $+    RawSql.fromString "ON " <> RawSql.toRawSql booleanExpr++{- | Constructs a 'TableReference' by creating a join expression from two existing table references.+  The result is an @n+m@ way join, where @n@ is the number of tables referenced in the first table+  reference expression (which itself may be a join) and @m@ is the number of tables referenced by+  the second table reference expression.++  See also: 'join', 'joining'++@since 1.1.0.0+-}+joinedTable ::+  TableReference ->+  JoinType ->+  TableReference ->+  JoinConstraint ->+  TableReference+joinedTable tableRefA joinType tableRefB joinOn =+  RawSql.unsafeFromRawSql $+    RawSql.toRawSql tableRefA+      <> RawSql.space+      <> RawSql.toRawSql joinType+      <> RawSql.space+      <> RawSql.toRawSql tableRefB+      <> RawSql.space+      <> RawSql.toRawSql joinOn++{- | A flipped version of 'joinedTable' that allows joined tables to be constructed in a syntax more+  similar to SQL. For example:++  @@+    fooTableRef+      & join leftJoinType barTableRef someJoinCondition+      & join leftJoinType bazTableRef someOtherJoinCondition+  @@++  See also: 'joining', 'joinedTable'++  @since 1.1.0.0+-}+join ::+  JoinType ->+  TableReference ->+  JoinConstraint ->+  TableReference ->+  TableReference+join joinType tableRefB joinOn tableRefA =+  joinedTable tableRefA joinType tableRefB joinOn++{- | A convenience function for constructing joins by tracking the tables to be joined in a list+  instead of using the '(&)' operator. The tables will be joined in a left associative manner,+  matching the associativity of the analogous SQL expression.++  @@+    joining fooTableRef+      [ join leftJoinType barTableRef someJoinCondition+      , join leftJoinType bazTableRef someOtherJoinCondition+      ]+  @@++  See also: 'join', 'joinedTable'++  @since 1.1.0.0+-}+joining :: TableReference -> [TableReference -> TableReference] -> TableReference+joining tableRef joinList =+  foldr (flip (.)) id joinList tableRef
src/Orville/PostgreSQL/Expr/LimitExpr.hs view
@@ -16,8 +16,7 @@ import qualified Orville.PostgreSQL.Raw.RawSql as RawSql import qualified Orville.PostgreSQL.Raw.SqlValue as SqlValue -{- |-Type to represent a SQL limit expression. E.G.+{- | Type to represent a SQL limit expression. E.G.  > LIMIT 10 
src/Orville/PostgreSQL/Expr/Name.hs view
@@ -1,7 +1,7 @@ {-# OPTIONS_GHC -Wno-missing-import-lists #-}  {- |-Copyright : Flipstone Technology Partners 2023+Copyright : Flipstone Technology Partners 2023-2024 License   : MIT Stability : Stable @@ -12,9 +12,11 @@   ) where +import Orville.PostgreSQL.Expr.Internal.Name.Alias as Export import Orville.PostgreSQL.Expr.Internal.Name.ColumnName as Export import Orville.PostgreSQL.Expr.Internal.Name.ConstraintName as Export import Orville.PostgreSQL.Expr.Internal.Name.CursorName as Export+import Orville.PostgreSQL.Expr.Internal.Name.ExtensionName as Export import Orville.PostgreSQL.Expr.Internal.Name.FunctionName as Export import Orville.PostgreSQL.Expr.Internal.Name.Identifier as Export import Orville.PostgreSQL.Expr.Internal.Name.IndexName as Export@@ -23,3 +25,5 @@ import Orville.PostgreSQL.Expr.Internal.Name.SchemaName as Export import Orville.PostgreSQL.Expr.Internal.Name.SequenceName as Export import Orville.PostgreSQL.Expr.Internal.Name.TableName as Export+import Orville.PostgreSQL.Expr.Internal.Name.TriggerName as Export+import Orville.PostgreSQL.Expr.Internal.Name.WindowName as Export
src/Orville/PostgreSQL/Expr/OffsetExpr.hs view
@@ -16,8 +16,7 @@ import qualified Orville.PostgreSQL.Raw.RawSql as RawSql import qualified Orville.PostgreSQL.Raw.SqlValue as SqlValue -{- |-Type to represent a SQL offset expression. E.G.+{- | Type to represent a SQL offset expression. E.G.  > OFFSET 10 
+ src/Orville/PostgreSQL/Expr/OnConflict.hs view
@@ -0,0 +1,193 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++{- |+Copyright : Flipstone Technology Partners 2024+License   : MIT+Stability : Stable++@since 1.1.0.0+-}+module Orville.PostgreSQL.Expr.OnConflict+  ( OnConflictExpr+  , onConflict+  , onConflictDoNothing+  , onConflictDoUpdate+  , ConflictTargetExpr+  , conflictTargetForIndexColumn+  , conflictTargetForIndexExpr+  , conflictTargetForConstraint+  , ConflictActionExpr+  , ConflictSetItemExpr+  , setColumnNameExcluded+  )+where++import Data.List.NonEmpty (NonEmpty)++import Orville.PostgreSQL.Expr.Index (IndexBodyExpr)+import Orville.PostgreSQL.Expr.Internal.Name.ConstraintName (ConstraintName)+import Orville.PostgreSQL.Expr.Name (ColumnName, QualifiedOrUnqualified)+import Orville.PostgreSQL.Expr.WhereClause (WhereClause)+import qualified Orville.PostgreSQL.Raw.RawSql as RawSql++{- | Type to represent the SQL for the 'ON CONFLICT' clause.++'OnConflict' provides a 'RawSql.SqlExpression' instance. See 'RawSql.unsafeSqlExpression' for how to+construct a value with your own custom SQL.++@since 1.1.0.0+-}+newtype OnConflictExpr+  = OnConflictExpr RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++{- | Create an 'OnConflict' that performs the specified action to be taken during a conflicting insert.++@since 1.1.0.0+-}+onConflict :: Maybe ConflictTargetExpr -> ConflictActionExpr -> OnConflictExpr+onConflict mbTarget action =+  OnConflictExpr $+    RawSql.fromString "ON CONFLICT "+      <> case mbTarget of+        Nothing ->+          RawSql.toRawSql action+        Just target ->+          RawSql.toRawSql target <> RawSql.space <> RawSql.toRawSql action++{- | A wrapper around 'onConflict' for no specific target and an explicit no action to perform.++@since 1.1.0.0+-}+onConflictDoNothing :: OnConflictExpr+onConflictDoNothing =+  onConflict Nothing doNothingActionExpr++{- | A wrapper around 'onConflict' to perform an update, with optional target and where clause. That+   will use the given 'ConflictSetItemExpr's as the update body.++@since 1.1.0.0+-}+onConflictDoUpdate :: Maybe ConflictTargetExpr -> NonEmpty ConflictSetItemExpr -> Maybe WhereClause -> OnConflictExpr+onConflictDoUpdate mbTarget setItems =+  onConflict mbTarget . doUpdateSetActionExpr setItems++{- | Type to represent the target portion of the SQL 'ON CONFLICT target action'++@since 1.1.0.0+-}+newtype ConflictTargetExpr+  = ConflictTargetExpr RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++{- | Build a 'ConflictTargetExpr' that would conflict on a single 'ColumnName'. PostgreSQL will use+   the column to infer which index to check for conflicts.++@since 1.1.0.0+-}+conflictTargetForIndexColumn :: QualifiedOrUnqualified ColumnName -> Maybe WhereClause -> ConflictTargetExpr+conflictTargetForIndexColumn colName mbWhere =+  let+    parensCol = RawSql.parenthesized $ RawSql.toRawSql colName+  in+    ConflictTargetExpr $+      case mbWhere of+        Nothing -> parensCol+        Just whereClause -> parensCol <> RawSql.space <> RawSql.toRawSql whereClause++{- | Build a 'ConflictTargetExpr' that would conflict on an 'IndexBodyExpr'. This can be used to check+   for more than a single column. PostgreSQL will use the expression to infer which index to check for+   conflicts.++   Note that this function assumes that the 'IndexBodyExpr' is parenthesized.+@since 1.1.0.0+-}+conflictTargetForIndexExpr :: IndexBodyExpr -> Maybe WhereClause -> ConflictTargetExpr+conflictTargetForIndexExpr idxBody mbWhere =+  let+    rawIdxBody = RawSql.toRawSql idxBody+  in+    ConflictTargetExpr $+      case mbWhere of+        Nothing -> rawIdxBody+        Just whereClause -> rawIdxBody <> RawSql.space <> RawSql.toRawSql whereClause++{- | Build a 'ConflictTargetExpr' for conflicting on the given 'ConstraintName'. For conflicing on an+   index, see 'conflictTargetForIndexColumn' or 'conflictTargetForIndexExpr' instead.++@since 1.1.0.0+-}+conflictTargetForConstraint :: ConstraintName -> ConflictTargetExpr+conflictTargetForConstraint constraintName =+  ConflictTargetExpr $ RawSql.fromString "ON CONSTRAINT " <> RawSql.toRawSql constraintName++{- | Type to represent the action portion of the SQL 'ON CONFLICT target action'++@since 1.1.0.0+-}+newtype ConflictActionExpr+  = ConflictActionExpr RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++{- | A 'ConflictActionExpr' for the SQL 'DO NOTHING'.++@since 1.1.0.0+-}+doNothingActionExpr :: ConflictActionExpr+doNothingActionExpr =+  ConflictActionExpr $ RawSql.fromString "DO NOTHING"++{- | A 'ConflictActionExpr' for the SQL 'DO UPDATE SET', that will use the given 'ConflictSetItemExpr'+   to determine which columns to update and the optional 'WhereClause' to peform filtering.++@since 1.1.0.0+-}+doUpdateSetActionExpr :: NonEmpty ConflictSetItemExpr -> Maybe WhereClause -> ConflictActionExpr+doUpdateSetActionExpr setItems mbWhere =+  let+    commaSeparatedItems = RawSql.intercalate RawSql.comma setItems+  in+    ConflictActionExpr $+      RawSql.fromString "DO UPDATE SET "+        <> case mbWhere of+          Nothing -> commaSeparatedItems+          Just whereClause -> commaSeparatedItems <> RawSql.space <> RawSql.toRawSql whereClause++{- | Type to represent the item to be set in an 'ON CONFLICT DO UPDATE'. E.G.++> ON CONFLICT DO UPDATE SET item++@since 1.1.0.0+-}+newtype ConflictSetItemExpr+  = ConflictSetItemExpr RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++{- | Builds a 'ConflictSetItemExpr' that will set the given 'ColumnName' to the excluded value using+the special pseudo-table 'EXCLUDED'.++@since 1.1.0.0+-}+setColumnNameExcluded :: QualifiedOrUnqualified ColumnName -> ConflictSetItemExpr+setColumnNameExcluded colName =+  let+    rawName = RawSql.toRawSql colName+  in+    ConflictSetItemExpr $+      rawName+        <> RawSql.fromString " = "+        <> RawSql.fromString "EXCLUDED"+        <> RawSql.dot+        <> rawName
+ src/Orville/PostgreSQL/Expr/OrReplace.hs view
@@ -0,0 +1,41 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++{- |+Copyright : Flipstone Technology Partners 2024+License   : MIT+Stability : Stable++@since 1.1.0.0+-}+module Orville.PostgreSQL.Expr.OrReplace+  ( OrReplace+  , orReplace+  )+where++import qualified Orville.PostgreSQL.Raw.RawSql as RawSql++{- | Type to represent a SQL "OR REPLACE" expression. E.G.++> OR REPLACE++'OrReplace' provides a 'RawSql.SqlExpression' instance. See+'RawSql.unsafeSqlExpression' for how to construct a value with your own custom+SQL.++@since 1.1.0.0+-}+newtype OrReplace+  = OrReplace RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++{- | A value of the SQL "OR REPLACE".++@since 1.1.0.0+-}+orReplace :: OrReplace+orReplace =+  OrReplace $ RawSql.fromString "OR REPLACE"
src/Orville/PostgreSQL/Expr/OrderBy.hs view
@@ -1,7 +1,7 @@ {-# LANGUAGE GeneralizedNewtypeDeriving #-}  {- |-Copyright : Flipstone Technology Partners 2023+Copyright : Flipstone Technology Partners 2023-2025 License   : MIT Stability : Stable @@ -14,6 +14,7 @@   , appendOrderByExpr   , orderByColumnName   , orderByColumnsExpr+  , orderByValueExpression   , OrderByDirection   , NullsOrder (NullsFirst, NullsLast)   , ascendingOrder@@ -25,11 +26,12 @@  import qualified Data.List.NonEmpty as NEL -import Orville.PostgreSQL.Expr.Name (ColumnName)+import Orville.PostgreSQL.Expr.Name (ColumnName, QualifiedOrUnqualified)+import qualified Orville.PostgreSQL.Expr.ValueExpression as ValueExpression+import qualified Orville.PostgreSQL.Internal.Extra.NonEmpty as ExtraNonEmpty import qualified Orville.PostgreSQL.Raw.RawSql as RawSql -{- |-Type to represent a SQL order by clause. E.G.+{- | Type to represent a SQL order by clause. E.G.  > ORDER BY foo, bar @@ -41,13 +43,19 @@ -} newtype OrderByClause   = OrderByClause RawSql.RawSql-  deriving (RawSql.SqlExpression)+  deriving+    ( -- | @since 1.0.0.0+      RawSql.SqlExpression+    ) +{- | Builds a full 'OrderByClause' with the given ordering described in the 'OrderByExpr'++@since 1.0.0.0+-} orderByClause :: OrderByExpr -> OrderByClause orderByClause expr = OrderByClause (RawSql.fromString "ORDER BY " <> RawSql.toRawSql expr) -{- |-Type to represent a SQL order by expression (the part that follows the @ORDER+{- | Type to represent a SQL order by expression (the part that follows the @ORDER BY@ in SQL). E.G.  > foo, bar@@ -64,9 +72,7 @@       RawSql.SqlExpression     ) -{- |-@since 1.0.0.0--}+-- | @since 1.0.0.0 instance Semigroup OrderByExpr where   (<>) = appendOrderByExpr @@ -83,26 +89,26 @@  @since 1.0.0.0 -}-orderByColumnsExpr :: NEL.NonEmpty (ColumnName, OrderByDirection) -> OrderByExpr-orderByColumnsExpr =-  OrderByExpr . RawSql.intercalate RawSql.commaSpace . fmap columnOrdering- where-  columnOrdering :: (ColumnName, OrderByDirection) -> RawSql.RawSql-  columnOrdering (columnName, orderByDirection) =-    RawSql.toRawSql columnName <> RawSql.space <> RawSql.toRawSql orderByDirection+orderByColumnsExpr :: NEL.NonEmpty (QualifiedOrUnqualified ColumnName, OrderByDirection) -> OrderByExpr+orderByColumnsExpr = ExtraNonEmpty.foldMap1' (uncurry orderByColumnName) -{-- |-  Orders a query by the given column name in the given order direction.+{- | Orders a query by the given column name in the given order direction.  @since 1.0.0.0 -}-orderByColumnName :: ColumnName -> OrderByDirection -> OrderByExpr-orderByColumnName =-  curry (orderByColumnsExpr . pure)+orderByColumnName :: QualifiedOrUnqualified ColumnName -> OrderByDirection -> OrderByExpr+orderByColumnName = orderByValueExpression . ValueExpression.columnReference -{- |-Type to represent a SQL order by direction expression. E.G.+{- | Create an 'OrderByExpr' for the given 'ValueExpression.ValueExpression' and 'OrderByDirection'. The caller must ensure that the 'ValueExpression.ValueExpression' is valid as an order by expression. +@since 1.1.0.0+-}+orderByValueExpression :: ValueExpression.ValueExpression -> OrderByDirection -> OrderByExpr+orderByValueExpression value direction =+  OrderByExpr $ RawSql.toRawSql value <> RawSql.space <> RawSql.toRawSql direction++{- | Type to represent a SQL order by direction expression. E.G.+ > ASC  'OrderByDirection' provides a 'RawSql.SqlExpression' instance. See@@ -112,10 +118,12 @@ @since 1.0.0.0 -} newtype OrderByDirection = OrderByDirection RawSql.RawSql-  deriving (RawSql.SqlExpression)+  deriving+    ( -- | @since 1.0.0.0+      RawSql.SqlExpression+    ) -{- |-Type to represent the ordering of Null, intended to be used with 'OrderByDirection'.+{- | Type to represent the ordering of Null, intended to be used with 'OrderByDirection'.  @since 1.0.0.0 -}
src/Orville/PostgreSQL/Expr/Query.hs view
@@ -1,7 +1,7 @@ {-# LANGUAGE GeneralizedNewtypeDeriving #-}  {- |-Copyright : Flipstone Technology Partners 2023+Copyright : Flipstone Technology Partners 2023-2025 License   : MIT Stability : Stable @@ -10,6 +10,13 @@ module Orville.PostgreSQL.Expr.Query   ( QueryExpr   , queryExpr+  , existsSubquery+  , queryValueExpression+  , inSubquery+  , notInSubquery+  , anySubquery+  , allSubquery+  , subQueryTableReference   , SelectList   , selectColumns   , DerivedColumn@@ -24,21 +31,24 @@  import Data.Maybe (catMaybes, fromMaybe) +import Orville.PostgreSQL.Expr.BinaryOperator (BinaryOperator)+import Orville.PostgreSQL.Expr.FetchClause (FetchClause) import Orville.PostgreSQL.Expr.GroupBy (GroupByClause) import Orville.PostgreSQL.Expr.LimitExpr (LimitExpr)-import Orville.PostgreSQL.Expr.Name (ColumnName)+import Orville.PostgreSQL.Expr.Name (AliasExpr, ColumnName, QualifiedOrUnqualified) import Orville.PostgreSQL.Expr.OffsetExpr (OffsetExpr) import Orville.PostgreSQL.Expr.OrderBy (OrderByClause)+import Orville.PostgreSQL.Expr.RowLocking (RowLockingClause) import Orville.PostgreSQL.Expr.Select (SelectClause)-import Orville.PostgreSQL.Expr.TableReferenceList (TableReferenceList)+import Orville.PostgreSQL.Expr.TableReferenceList (TableReference, TableReferenceList) import Orville.PostgreSQL.Expr.ValueExpression (ValueExpression, columnReference)-import Orville.PostgreSQL.Expr.WhereClause (WhereClause)+import Orville.PostgreSQL.Expr.WhereClause (BooleanExpr, InValuePredicate, WhereClause, inPredicate, notInPredicate)+import Orville.PostgreSQL.Expr.Window (WindowClause) import qualified Orville.PostgreSQL.Raw.RawSql as RawSql  -- This is a rough model of "query specification" see https://jakewheat.github.io/sql-overview/sql-2016-foundation-grammar.html#_7_16_query_specification for more detail than you probably want -{- |-Type to represent a SQL query, E.G.+{- | Type to represent a SQL query, E.G.  > SELECT id FROM some_table @@ -50,10 +60,12 @@ -} newtype QueryExpr   = QueryExpr RawSql.RawSql-  deriving (RawSql.SqlExpression)+  deriving+    ( -- | @since 1.0.0.0+      RawSql.SqlExpression+    ) -{- |-  Builds a 'QueryExpr' from the given 'SelectClause', 'SelectList' and+{- | Builds a 'QueryExpr' from the given 'SelectClause', 'SelectList' and   'TableExpr'. The resulting 'QueryExpr' is suitable for execution via the SQL   execution functions in "Orville.PostgreSQL.Execution" and   "Orville.PostgreSQL.Raw.RawSql".@@ -74,8 +86,91 @@         , fromMaybe (RawSql.fromString "") maybeFromClause         ] -{- |-Type to represent the list of items to be selected in a @SELECT@ clause.+{- | The SQL @EXISTS@ subquery expression. This builds a 'BooleanExpr' that checks if the given+   'QueryExpr' returns at least one row. The resuling SQL will correctly be parenthesized.++@since 1.1.0.0+-}+existsSubquery :: QueryExpr -> BooleanExpr+existsSubquery (QueryExpr queryRawSql) =+  RawSql.unsafeFromRawSql $+    RawSql.fromString "EXISTS "+      <> RawSql.parenthesized queryRawSql++{- | Treat a 'QueryExpr' as a 'ValueExpression' to make it suitable for use as subquery.++@since 1.1.0.0+-}+queryValueExpression :: QueryExpr -> ValueExpression+queryValueExpression =+  RawSql.unsafeFromRawSql . RawSql.toRawSql++-- Internal helper to treat a 'QueryExpr' as an 'InValuePredicate' for building @IN@ and @NOT IN@+-- subquery expressions.+queryInValuePredicate :: QueryExpr -> InValuePredicate+queryInValuePredicate =+  RawSql.unsafeFromRawSql . RawSql.parenthesized . RawSql.toRawSql++{- | The SQL @IN@ subquery expression. It is up to the caller to ensure that the given 'QueryExpr'+   returns exactly one column.++@since 1.1.0.0+-}+inSubquery :: ValueExpression -> QueryExpr -> BooleanExpr+inSubquery valExpr =+  inPredicate valExpr . queryInValuePredicate++{- | The SQL @NOT IN@ subquery expression. It is up to the caller to ensure that the given 'QueryExpr'+   returns exactly one column.++@since 1.1.0.0+-}+notInSubquery :: ValueExpression -> QueryExpr -> BooleanExpr+notInSubquery valExpr =+  notInPredicate valExpr . queryInValuePredicate++{- | The SQL @ANY@ subquery expression. It is up to the caller to ensure that the given 'QueryExpr'+   returns exactly one column and that the operator results in a boolean.++@since 1.1.0.0+-}+anySubquery :: BinaryOperator -> ValueExpression -> QueryExpr -> BooleanExpr+anySubquery =+  operatorSubquery (RawSql.fromString " ANY ")++{- | The SQL @ALL@ subquery expression. It is up to the caller to ensure that the given 'QueryExpr'+   returns exactly one column and that the operator results in a boolean.++@since 1.1.0.0+-}+allSubquery :: BinaryOperator -> ValueExpression -> QueryExpr -> BooleanExpr+allSubquery =+  operatorSubquery (RawSql.fromString " ALL ")++-- Internal helper to build any/all subquery+operatorSubquery :: RawSql.RawSql -> BinaryOperator -> ValueExpression -> QueryExpr -> BooleanExpr+operatorSubquery querySpecificRawSql binOp valExpr (QueryExpr queryRawSql) =+  RawSql.unsafeFromRawSql $+    RawSql.parenthesized valExpr+      <> RawSql.space+      <> RawSql.toRawSql binOp+      <> querySpecificRawSql+      <> RawSql.parenthesized queryRawSql++{- | Make a 'QueryExpr' into a 'TableReference' using the specified alias.+  This is the way to use a 'QueryExpr' as a subquery that can be joined+  against and referenced as if it were a table.++@since 1.1.0.0+-}+subQueryTableReference :: QueryExpr -> AliasExpr -> TableReference+subQueryTableReference query alias =+  RawSql.unsafeFromRawSql $+    RawSql.parenthesized (RawSql.toRawSql query)+      <> RawSql.fromString " AS "+      <> RawSql.toRawSql alias++{- | Type to represent the list of items to be selected in a @SELECT@ clause. E.G. the  > foo, bar, baz@@ -91,10 +186,17 @@ @since 1.0.0.0 -} newtype SelectList = SelectList RawSql.RawSql-  deriving (RawSql.SqlExpression)+  deriving+    ( -- | @since 1.0.0.0+      RawSql.SqlExpression+    ) -{- |-  Constructs a 'SelectList' that will select all colums (i.e. the @*@ in+-- | @since 1.1.0.0+instance Semigroup SelectList where+  SelectList a <> SelectList b =+    SelectList $ RawSql.appendWithCommaSpace a b++{- | Constructs a 'SelectList' that will select all colums (i.e. the @*@ in   @SELECT *@").    @since 1.0.0.0@@ -103,19 +205,17 @@ selectStar =   SelectList (RawSql.fromString "*") -{- |-  Constructs a 'SelectList' that will select the specified column names. This+{- | Constructs a 'SelectList' that will select the specified column names. This   is a special case of 'selectDerivedColumns' where all the items to be   selected are simple column references.    @since 1.0.0.0 -}-selectColumns :: [ColumnName] -> SelectList+selectColumns :: [QualifiedOrUnqualified ColumnName] -> SelectList selectColumns =-  selectDerivedColumns . map (deriveColumn . columnReference)+  selectDerivedColumns . fmap (deriveColumn . columnReference) -{- |-Type to represent an individual item in a list of selected items. E.G.+{- | Type to represent an individual item in a list of selected items. E.G.  > now() as current_time @@ -126,10 +226,12 @@ @since 1.0.0.0 -} newtype DerivedColumn = DerivedColumn RawSql.RawSql-  deriving (RawSql.SqlExpression)+  deriving+    ( -- | @since 1.0.0.0+      RawSql.SqlExpression+    ) -{- |-  Constructs a 'SelectList' that will select the specified items, which may be+{- | Constructs a 'SelectList' that will select the specified items, which may be   column references or other expressions as allowed by 'DerivedColumn'. See   also 'selectColumns' the simpler case of selecting a list of column names. @@ -139,8 +241,7 @@ selectDerivedColumns =   SelectList . RawSql.intercalate RawSql.comma -{- |-  Constructs a 'DerivedColumn' that will select the given value. No name will+{- | Constructs a 'DerivedColumn' that will select the given value. No name will   be given to the value in the result set. See 'deriveColumnAs' to give the   value a name in the result set. @@ -150,8 +251,7 @@ deriveColumn =   DerivedColumn . RawSql.toRawSql -{- |-  Constructs a 'DerivedColumn' that will select the given value and give it+{- | Constructs a 'DerivedColumn' that will select the given value and give it   the specified column name in the result set.  @since 1.0.0.0@@ -164,8 +264,7 @@         <> RawSql.toRawSql asColumn     ) -{- |-Type to represent a table expression (including its associated options) in a+{- | Type to represent a table expression (including its associated options) in a @SELECT@. This is the part that would appear *after* the word @FROM@. E.G.  > foo@@ -182,15 +281,17 @@ -} newtype TableExpr   = TableExpr RawSql.RawSql-  deriving (RawSql.SqlExpression)+  deriving+    ( -- | @since 1.0.0.0+      RawSql.SqlExpression+    ) -{- |-  Constructs a 'TableExpr' with the given options.+{- | Constructs a 'TableExpr' with the given options.  @since 1.0.0.0 -} tableExpr ::-  -- | The list of tables to query from.+  -- | The item(s) to query from.   TableReferenceList ->   -- | An optional @WHERE@ clause to limit the results returned.   Maybe WhereClause ->@@ -202,6 +303,12 @@   Maybe LimitExpr ->   -- | An optional @OFFSET@ to apply to the result set.   Maybe OffsetExpr ->+  -- | An optional locking clause to apply to the result set.+  Maybe RowLockingClause ->+  -- | An optional @WINDOW@ clause to apply to the result set.+  Maybe WindowClause ->+  -- | An optional @FETCH@ clause to apply to the result set.+  Maybe FetchClause ->   TableExpr tableExpr   tableReferenceList@@ -209,14 +316,20 @@   maybeGroupByClause   maybeOrderByClause   maybeLimitExpr-  maybeOffsetExpr =+  maybeOffsetExpr+  maybeRowLockingClause+  maybeWindowClause+  maybeFetchClause =     TableExpr       . RawSql.intercalate RawSql.space       $ RawSql.toRawSql tableReferenceList         : catMaybes           [ RawSql.toRawSql <$> maybeWhereClause           , RawSql.toRawSql <$> maybeGroupByClause+          , RawSql.toRawSql <$> maybeWindowClause           , RawSql.toRawSql <$> maybeOrderByClause           , RawSql.toRawSql <$> maybeLimitExpr           , RawSql.toRawSql <$> maybeOffsetExpr+          , RawSql.toRawSql <$> maybeFetchClause+          , RawSql.toRawSql <$> maybeRowLockingClause           ]
src/Orville/PostgreSQL/Expr/ReturningExpr.hs view
@@ -16,8 +16,7 @@ import Orville.PostgreSQL.Expr.Query (SelectList) import qualified Orville.PostgreSQL.Raw.RawSql as RawSql -{- |-Type to represent a @RETURNING@ clause in a SQL @SELECT@ statement. E.G.+{- | Type to represent a @RETURNING@ clause in a SQL @SELECT@ statement. E.G.  > RETURNING (id) @@ -29,10 +28,12 @@ -} newtype ReturningExpr   = ReturningExpr RawSql.RawSql-  deriving (RawSql.SqlExpression)+  deriving+    ( -- | @since 1.0.0.0+      RawSql.SqlExpression+    ) -{- |-  Constructs a 'ReturningExpr' that returns the items given in the+{- | Constructs a 'ReturningExpr' that returns the items given in the   'SelectList'. Essentialy this retults @RETURNING <SelectList items>@.  @since 1.0.0.0
+ src/Orville/PostgreSQL/Expr/RowLocking.hs view
@@ -0,0 +1,150 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++{- |+Copyright : Flipstone Technology Partners 2024+License   : MIT+Stability : Stable++@since 1.1.0.0+-}+module Orville.PostgreSQL.Expr.RowLocking+  ( RowLockingClause+  , rowLockingClause+  , RowLockingStrengthExpr+  , updateStrength+  , noKeyUpdateStrength+  , shareStrength+  , keyShareStrength+  , RowLockingOptionExpr+  , noWaitRowLockingOption+  , skipLockedRowLockingOption+  )+where++import qualified Data.List.NonEmpty as NEL++import Orville.PostgreSQL.Expr.Internal.Name.TableName (TableName)+import qualified Orville.PostgreSQL.Raw.RawSql as RawSql++{- | Type to represent the row locking part of a SQL @SELECT@ query. E.G.++> FOR UPDATE++'RowLockingClause' provides a 'RawSql.SqlExpression' instance. See+'RawSql.unsafeSqlExpression' for how to construct a value with your own custom SQL.++@since 1.1.0.0+-}+newtype RowLockingClause+  = RowLockingClause RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++{- | Builds a 'RowLockingClause' with the given strength. If provided the optional 'NEL.NonEmpty+   TableName' and 'RowLockingOptionExpr' will scope and set the lock option respectively.++@since 1.1.0.0+-}+rowLockingClause ::+  RowLockingStrengthExpr ->+  Maybe (NEL.NonEmpty TableName) ->+  Maybe RowLockingOptionExpr ->+  RowLockingClause+rowLockingClause strength mbTables mbOpt =+  RowLockingClause $+    RawSql.fromString "FOR "+      <> RawSql.toRawSql strength+      <> maybe+        mempty+        ( \tables ->+            RawSql.fromString " OF "+              <> RawSql.intercalate+                RawSql.commaSpace+                (fmap RawSql.toRawSql tables)+        )+        mbTables+      <> maybe mempty (\opt -> RawSql.space <> RawSql.toRawSql opt) mbOpt++{- | Type to represent the lock strength part of a row locking sub expression in a SQL @SELECT@+query. E.G.++> NO KEY UPDATE++'RowLockingStrengthExpr' provides a 'RawSql.SqlExpression' instance. See+'RawSql.unsafeSqlExpression' for how to construct a value with your own custom SQL.++@since 1.1.0.0+-}+newtype RowLockingStrengthExpr+  = RowLockingStrengthExpr RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++{- | The @UPDATE@ lock strength++@since 1.1.0.0+-}+updateStrength :: RowLockingStrengthExpr+updateStrength =+  RowLockingStrengthExpr $ RawSql.fromString "UPDATE"++{- | The @NO KEY UPDATE@ lock strength++@since 1.1.0.0+-}+noKeyUpdateStrength :: RowLockingStrengthExpr+noKeyUpdateStrength =+  RowLockingStrengthExpr $ RawSql.fromString "NO KEY UPDATE"++{- | The @SHARE@ lock strength++@since 1.1.0.0+-}+shareStrength :: RowLockingStrengthExpr+shareStrength =+  RowLockingStrengthExpr $ RawSql.fromString "SHARE"++{- | The @KEY SHARE@ lock strength++@since 1.1.0.0+-}+keyShareStrength :: RowLockingStrengthExpr+keyShareStrength =+  RowLockingStrengthExpr $ RawSql.fromString "KEY SHARE"++{- | Type to represent the lock option part of a row locking sub expression in a SQL @SELECT@+query. E.G.++> SKIP LOCKED++'RowLockingOptionExpr' provides a 'RawSql.SqlExpression' instance. See+'RawSql.unsafeSqlExpression' for how to construct a value with your own custom SQL.++@since 1.1.0.0+-}+newtype RowLockingOptionExpr+  = RowLockingOptionExpr RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++{- | The @NO WAIT@ lock option++@since 1.1.0.0+-}+noWaitRowLockingOption :: RowLockingOptionExpr+noWaitRowLockingOption =+  RowLockingOptionExpr $ RawSql.fromString "NO WAIT"++{- | The @SKIP LOCKED@ lock option++@since 1.1.0.0+-}+skipLockedRowLockingOption :: RowLockingOptionExpr+skipLockedRowLockingOption =+  RowLockingOptionExpr $ RawSql.fromString "SKIP LOCKED"
src/Orville/PostgreSQL/Expr/Select.hs view
@@ -13,13 +13,16 @@   , SelectExpr   , selectExpr   , Distinct (Distinct)+  , DistinctOnExpr+  , selectDistinctOnExpr   ) where +import qualified Data.List.NonEmpty as NEL+ import qualified Orville.PostgreSQL.Raw.RawSql as RawSql -{- |-Type to represent the @SELECT@ part of a SQL query. E.G.+{- | Type to represent the @SELECT@ part of a SQL query. E.G.  > SELECT @@ -34,10 +37,12 @@ -} newtype SelectClause   = SelectClause RawSql.RawSql-  deriving (RawSql.SqlExpression)+  deriving+    ( -- | @since 1.0.0.0+      RawSql.SqlExpression+    ) -{- |-  Constructs a 'SelectClause' using the given 'SelectExpr', which may indicate+{- | Constructs a 'SelectClause' using the given 'SelectExpr', which may indicate   that this is a @DISTINCT@ select.  @since 1.0.0.0@@ -45,8 +50,7 @@ selectClause :: SelectExpr -> SelectClause selectClause expr = SelectClause (RawSql.fromString "SELECT " <> RawSql.toRawSql expr) -{- |-Type to represent any expression modifying the @SELECT@ part of a SQL. E.G.+{- | Type to represent any expression modifying the @SELECT@ part of a SQL. E.G.  > DISTINCT @@ -56,18 +60,19 @@ @since 1.0.0.0 -} newtype SelectExpr = SelectExpr RawSql.RawSql-  deriving (RawSql.SqlExpression)+  deriving+    ( -- | @since 1.0.0.0+      RawSql.SqlExpression+    ) -{- |-  A simple value type used to indicate that a @SELECT@ should be distinct when+{- | A simple value type used to indicate that a @SELECT@ should be distinct when   constructing a 'SelectExpr'.  @since 1.0.0.0 -} data Distinct = Distinct -{- |-  Constructs a 'SelectExpr' that may or may not make the @SELECT@ distinct,+{- | Constructs a 'SelectExpr' that may or may not make the @SELECT@ distinct,   depending on whether 'Just Distinct' is passed or not.  @since 1.0.0.0@@ -78,3 +83,33 @@     case mbDistinct of       Just Distinct -> "DISTINCT "       Nothing -> ""++{- | Type to represent a expression to be used as part of a @DISTINCT@ modifer of a @SELECT@. E.G. the+foo in++> DISTINCT ON (foo)++'SelectExpr' provides a 'RawSql.SqlExpression' instance. See+'RawSql.unsafeSqlExpression' for how to construct a value with your own custom SQL.++@since 1.1.0.0+-}+newtype DistinctOnExpr = DistinctOnExpr RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++{- | Constructs a 'SelectExpr' that may or may not make the @SELECT@ distinct on the given expression.++Note that PostgreSQL states the distinct expressions must match the leftmost order by expressions if+they are both used, but this function does not enforce that.++@since 1.1.0.0+-}+selectDistinctOnExpr :: Maybe (NEL.NonEmpty DistinctOnExpr) -> SelectExpr+selectDistinctOnExpr mbDistinctOns =+  SelectExpr $+    case mbDistinctOns of+      Just ons -> RawSql.fromString "DISTINCT ON " <> RawSql.parenthesized (RawSql.intercalate RawSql.commaSpace ons)+      Nothing -> mempty
src/Orville/PostgreSQL/Expr/SequenceDefinition.hs view
@@ -46,7 +46,7 @@ import Data.Maybe (catMaybes)  import Orville.PostgreSQL.Expr.IfExists (IfExists)-import Orville.PostgreSQL.Expr.Name (FunctionName, Qualified, SequenceName, functionName)+import Orville.PostgreSQL.Expr.Name (FunctionName, QualifiedOrUnqualified, SequenceName, functionName) import Orville.PostgreSQL.Expr.ValueExpression (ValueExpression, functionCall, valueExpression) import qualified Orville.PostgreSQL.Raw.RawSql as RawSql import qualified Orville.PostgreSQL.Raw.SqlValue as SqlValue@@ -64,8 +64,7 @@    @@ -} -{- |-Type to represent a @CREATE SEQUENCE@ statement. E.G.+{- | Type to represent a @CREATE SEQUENCE@ statement. E.G.  > CREATE SEQUENCE foo INCREMENT 2 @@ -77,16 +76,18 @@ -} newtype CreateSequenceExpr   = CreateSequenceExpr RawSql.RawSql-  deriving (RawSql.SqlExpression)+  deriving+    ( -- | @since 1.0.0.0+      RawSql.SqlExpression+    ) -{- |-  Constructs a 'CreateSequenceExpr' with the given sequence options.+{- | Constructs a 'CreateSequenceExpr' with the given sequence options.    @since 1.0.0.0 -} createSequenceExpr ::   -- | The name to be used for the sequence.-  Qualified SequenceName ->+  QualifiedOrUnqualified SequenceName ->   -- | An optional @INCREMENT@ expression.   Maybe IncrementByExpr ->   -- | An optional @MINVALUE@ expression.@@ -133,8 +134,7 @@   @@ -} -{- |-Type to represent a @CREATE SEQUENCE@ statement. E.G.+{- | Type to represent a @CREATE SEQUENCE@ statement. E.G.  > ALTER SEQUENCE foo START WITH 0 @@ -146,16 +146,18 @@ -} newtype AlterSequenceExpr   = AlterSequenceExpr RawSql.RawSql-  deriving (RawSql.SqlExpression)+  deriving+    ( -- | @since 1.0.0.0+      RawSql.SqlExpression+    ) -{- |-  Constructs an 'AlterSequenceExpr' with the given sequence options.+{- | Constructs an 'AlterSequenceExpr' with the given sequence options.    @since 1.0.0.0 -} alterSequenceExpr ::   -- | The name of the sequence to alter-  Qualified SequenceName ->+  QualifiedOrUnqualified SequenceName ->   -- | An optional @INCREMENT@ expression   Maybe IncrementByExpr ->   -- | An optional @MINVALUE@ expression@@ -183,8 +185,7 @@       , fmap RawSql.toRawSql mbCycle       ] -{- |-Type to represent an @INCREMENT BY@ expression for sequences. E.G.+{- | Type to represent an @INCREMENT BY@ expression for sequences. E.G.  > INCREMENT BY 0 @@ -196,10 +197,12 @@ -} newtype IncrementByExpr   = IncrementByExpr RawSql.RawSql-  deriving (RawSql.SqlExpression)+  deriving+    ( -- | @since 1.0.0.0+      RawSql.SqlExpression+    ) -{- |-  Constructs an 'IncrementByExpr' that will make the sequence increment by+{- | Constructs an 'IncrementByExpr' that will make the sequence increment by   the given value.    @since 1.0.0.0@@ -210,8 +213,7 @@     RawSql.fromString "INCREMENT BY "       <> RawSql.int64DecLiteral n -{- |-Type to represent a @MINVALUE@ expression for sequences. E.G.+{- | Type to represent a @MINVALUE@ expression for sequences. E.G.  > MINVALUE 0 @@ -223,10 +225,12 @@ -} newtype MinValueExpr   = MinValueExpr RawSql.RawSql-  deriving (RawSql.SqlExpression)+  deriving+    ( -- | @since 1.0.0.0+      RawSql.SqlExpression+    ) -{- |-  Constructs a 'MinValueExpr' which gives the sequence the specified minimum+{- | Constructs a 'MinValueExpr' which gives the sequence the specified minimum   value.    @since 1.0.0.0@@ -237,8 +241,7 @@     RawSql.fromString "MINVALUE "       <> RawSql.int64DecLiteral n -{- |-  Constructs a 'MinValueExpr' which gives the sequence no minimum value (i.e.+{- | Constructs a 'MinValueExpr' which gives the sequence no minimum value (i.e.   @NO MINVALUE@).    @since 1.0.0.0@@ -247,8 +250,7 @@ noMinValue =   MinValueExpr . RawSql.fromString $ "NO MINVALUE" -{- |-Type to represent a @MAXVALUE@ expression for sequences. E.G.+{- | Type to represent a @MAXVALUE@ expression for sequences. E.G.  > MAXVALUE 1000000 @@ -260,10 +262,12 @@ -} newtype MaxValueExpr   = MaxValueExpr RawSql.RawSql-  deriving (RawSql.SqlExpression)+  deriving+    ( -- | @since 1.0.0.0+      RawSql.SqlExpression+    ) -{- |-  Constructs a 'MaxValueExpr' which gives the sequence the specified maximum+{- | Constructs a 'MaxValueExpr' which gives the sequence the specified maximum   value.    @since 1.0.0.0@@ -274,8 +278,7 @@     RawSql.fromString "MAXVALUE "       <> RawSql.int64DecLiteral n -{- |-  Constructs a 'MaxValueExpr' which gives the sequence no maximum value (i.e.+{- | Constructs a 'MaxValueExpr' which gives the sequence no maximum value (i.e.   @NO MAXVALUE@).    @since 1.0.0.0@@ -284,8 +287,7 @@ noMaxValue =   MaxValueExpr . RawSql.fromString $ "NO MAXVALUE" -{- |-Type to represent a @START WITH@ expression for sequences. E.G.+{- | Type to represent a @START WITH@ expression for sequences. E.G.  > START WITH 1 @@ -297,10 +299,12 @@ -} newtype StartWithExpr   = StartWithExpr RawSql.RawSql-  deriving (RawSql.SqlExpression)+  deriving+    ( -- | @since 1.0.0.0+      RawSql.SqlExpression+    ) -{- |-  Constructs a 'StartWithExpr' which gives the sequence the specified start+{- | Constructs a 'StartWithExpr' which gives the sequence the specified start   value.    @since 1.0.0.0@@ -311,8 +315,7 @@     RawSql.fromString "START WITH "       <> RawSql.int64DecLiteral n -{- |-Type to represent a @CACHE@ expression for sequences. E.G.+{- | Type to represent a @CACHE@ expression for sequences. E.G.  > CACHE 16 @@ -324,10 +327,12 @@ -} newtype CacheExpr   = CacheExpr RawSql.RawSql-  deriving (RawSql.SqlExpression)+  deriving+    ( -- | @since 1.0.0.0+      RawSql.SqlExpression+    ) -{- |-  Constructs a 'CacheExpr' that will make the sequence pre-allocate the+{- | Constructs a 'CacheExpr' that will make the sequence pre-allocate the   specified number of sequence values.    @since 1.0.0.0@@ -338,8 +343,7 @@     RawSql.fromString "CACHE "       <> RawSql.int64DecLiteral n -{- |-Type to represent a @CYCLE@ expression for sequences. E.G.+{- | Type to represent a @CYCLE@ expression for sequences. E.G.  > CYCLE @@ -355,26 +359,26 @@ -} newtype CycleExpr   = CycleExpr RawSql.RawSql-  deriving (RawSql.SqlExpression)+  deriving+    ( -- | @since 1.0.0.0+      RawSql.SqlExpression+    ) -{- |-  Constructs a 'CycleExpr' that indicates that the sequence should cycle.+{- | Constructs a 'CycleExpr' that indicates that the sequence should cycle.    @since 1.0.0.0 -} cycle :: CycleExpr cycle = CycleExpr $ RawSql.fromString "CYCLE" -{- |-  Constructs a 'CycleExpr' that indicates that the sequence should not cycle.+{- | Constructs a 'CycleExpr' that indicates that the sequence should not cycle.    @since 1.0.0.0 -} noCycle :: CycleExpr noCycle = CycleExpr $ RawSql.fromString "NO CYCLE" -{- |-  Constructs a 'CycleExpr' that will cause the sequence to cycle if the flag+{- | Constructs a 'CycleExpr' that will cause the sequence to cycle if the flag   passed is @True@.    @since 1.0.0.0@@ -385,8 +389,7 @@     then cycle     else noCycle -{- |-Type to represent a @DROP SEQUENCE@ statement. E.G.+{- | Type to represent a @DROP SEQUENCE@ statement. E.G.  > DROP SEQUENCE foo @@ -398,16 +401,18 @@ -} newtype DropSequenceExpr   = DropSequenceExpr RawSql.RawSql-  deriving (RawSql.SqlExpression)+  deriving+    ( -- | @since 1.0.0.0+      RawSql.SqlExpression+    ) -{- |-  Constructs a 'DropSequenceExpr' that will drop sequence with the given name.+{- | Constructs a 'DropSequenceExpr' that will drop sequence with the given name.   You may specify an 'IfExists' argument if you want to include an @IF EXISTS@   condition in the statement.    @since 1.0.0.0 -}-dropSequenceExpr :: Maybe IfExists -> Qualified SequenceName -> DropSequenceExpr+dropSequenceExpr :: Maybe IfExists -> QualifiedOrUnqualified SequenceName -> DropSequenceExpr dropSequenceExpr maybeIfExists sequenceName =   DropSequenceExpr $     RawSql.intercalate@@ -419,8 +424,7 @@           ]       ) -{- |-  Constructs a 'ValueExpression' that will use the @nextval@ PostgreSQL+{- | Constructs a 'ValueExpression' that will use the @nextval@ PostgreSQL   function to get the next value from the given sequence. If you're trying to   construct your own @SELECT@ to get the value of the sequence, you can use the   constructed 'ValueExpression' with 'Orville.PostgreSQL.Expr.deriveColumnAs'@@ -428,14 +432,13 @@    @since 1.0.0.0 -}-nextVal :: Qualified SequenceName -> ValueExpression+nextVal :: QualifiedOrUnqualified SequenceName -> ValueExpression nextVal sequenceName =   functionCall     nextValFunction     [valueExpression . SqlValue.fromRawBytes . RawSql.toExampleBytes $ sequenceName] -{- |-  The @nextval@ PostgreSQL function.+{- | The @nextval@ PostgreSQL function.    @since 1.0.0.0 -}@@ -443,8 +446,7 @@ nextValFunction =   functionName "nextval" -{- |-  Constructs a 'ValueExpression' that will use the @currval@ PostgreSQL+{- | Constructs a 'ValueExpression' that will use the @currval@ PostgreSQL   function to get the current value from the given sequence. If you're trying to   construct your own @SELECT@ to get the value of the sequence, you can use the   constructed 'ValueExpression' with 'Orville.PostgreSQL.Expr.deriveColumnAs'@@ -452,14 +454,13 @@    @since 1.0.0.0 -}-currVal :: Qualified SequenceName -> ValueExpression+currVal :: QualifiedOrUnqualified SequenceName -> ValueExpression currVal sequenceName =   functionCall     currValFunction     [valueExpression . SqlValue.fromRawBytes . RawSql.toExampleBytes $ sequenceName] -{- |-  The @currval@ PostgreSQL function.+{- | The @currval@ PostgreSQL function.    @since 1.0.0.0 -}@@ -467,8 +468,7 @@ currValFunction =   functionName "currval" -{- |-  Constructs a 'ValueExpression' that will use the @setval@ PostgreSQL function+{- | Constructs a 'ValueExpression' that will use the @setval@ PostgreSQL function   to set the value from the given sequence. If you're trying to construct your   own @SELECT@ to set the value of the sequence, you can use the constructed   'ValueExpression' with 'Orville.PostgreSQL.Expr.deriveColumnAs' to build the@@ -476,7 +476,7 @@    @since 1.0.0.0 -}-setVal :: Qualified SequenceName -> Int64 -> ValueExpression+setVal :: QualifiedOrUnqualified SequenceName -> Int64 -> ValueExpression setVal sequenceName newValue =   functionCall     setValFunction@@ -484,8 +484,7 @@     , valueExpression . SqlValue.fromInt64 $ newValue     ] -{- |-  The @setval@ PostgreSQL function.+{- | The @setval@ PostgreSQL function.    @since 1.0.0.0 -}
src/Orville/PostgreSQL/Expr/TableConstraint.hs view
@@ -25,11 +25,10 @@  import Data.List.NonEmpty (NonEmpty) -import Orville.PostgreSQL.Expr.Name (ColumnName, Qualified, TableName)+import Orville.PostgreSQL.Expr.Name (ColumnName, QualifiedOrUnqualified, TableName) import qualified Orville.PostgreSQL.Raw.RawSql as RawSql -{- |-Type to represent a table constraint that would be part of a @CREATE TABLE@ or+{- | Type to represent a table constraint that would be part of a @CREATE TABLE@ or @ALTER TABLE@ statement. For instance, the @UNIQUE@ constraint in  > CREATE TABLE FOO@@ -46,10 +45,12 @@ -} newtype TableConstraint   = TableConstraint RawSql.RawSql-  deriving (RawSql.SqlExpression)+  deriving+    ( -- | @since 1.0.0.0+      RawSql.SqlExpression+    ) -{- |-  Constructs a 'TableConstraint' will create a @UNIQUE@ constraint on the+{- | Constructs a 'TableConstraint' will create a @UNIQUE@ constraint on the   given columns.    @since 1.0.0.0@@ -62,8 +63,7 @@       <> RawSql.intercalate RawSql.comma columnNames       <> RawSql.rightParen -{- |-Type to represent a foreign key action on a @FOREIGN KEY@ constraint. E.G.+{- | Type to represent a foreign key action on a @FOREIGN KEY@ constraint. E.G. the @CASCADE@ in  > FOREIGN KEY (foo_id) REFERENCES foo (id) ON DELETE CASCADE@@ -76,42 +76,40 @@ -} newtype ForeignKeyActionExpr   = ForeignKeyActionExpr RawSql.RawSql-  deriving (RawSql.SqlExpression)+  deriving+    ( -- | @since 1.0.0.0+      RawSql.SqlExpression+    ) -{- |-  The foreign key action @RESTRICT@.+{- | The foreign key action @RESTRICT@.    @since 1.0.0.0 -} restrictExpr :: ForeignKeyActionExpr restrictExpr = ForeignKeyActionExpr $ RawSql.fromString "RESTRICT" -{- |-  The foreign key action @CASCADE@.+{- | The foreign key action @CASCADE@.    @since 1.0.0.0 -} cascadeExpr :: ForeignKeyActionExpr cascadeExpr = ForeignKeyActionExpr $ RawSql.fromString "CASCADE" -{- |-  The foreign key action @SET NULL@.+{- | The foreign key action @SET NULL@.    @since 1.0.0.0 -} setNullExpr :: ForeignKeyActionExpr setNullExpr = ForeignKeyActionExpr $ RawSql.fromString "SET NULL" -{- |-  The foreign key action @SET DEFAULT@.+{- | The foreign key action @SET DEFAULT@.    @since 1.0.0.0 -} setDefaultExpr :: ForeignKeyActionExpr setDefaultExpr = ForeignKeyActionExpr $ RawSql.fromString "SET DEFAULT" -{- |-Type to represent a foreign key update action on a @FOREIGN KEY@ constraint. E.G.+{- | Type to represent a foreign key update action on a @FOREIGN KEY@ constraint. E.G. the @ON UPDATE RESTRICT@ in  > FOREIGN KEY (foo_id) REFERENCES foo (id) ON UPDATE RESTRICT@@ -124,10 +122,12 @@ -} newtype ForeignKeyUpdateActionExpr   = ForeignKeyUpdateActionExpr RawSql.RawSql-  deriving (RawSql.SqlExpression)+  deriving+    ( -- | @since 1.0.0.0+      RawSql.SqlExpression+    ) -{- |-  Constructs a 'ForeignKeyActionExpr' that uses the given 'ForeignKeyActionExpr'+{- | Constructs a 'ForeignKeyActionExpr' that uses the given 'ForeignKeyActionExpr'   in an @ON UPDATE@ clause for a foreign key.    @since 1.0.0.0@@ -139,8 +139,7 @@       <> RawSql.space       <> RawSql.toRawSql action -{- |-Type to represent a foreign key update action on a @FOREIGN KEY@ constraint. E.G.+{- | Type to represent a foreign key update action on a @FOREIGN KEY@ constraint. E.G. the @ON DELETE RESTRICT@ in  > FOREIGN KEY (foo_id) REFERENCES foo (id) ON DELETE RESTRICT@@ -153,10 +152,12 @@ -} newtype ForeignKeyDeleteActionExpr   = ForeignKeyDeleteActionExpr RawSql.RawSql-  deriving (RawSql.SqlExpression)+  deriving+    ( -- | @since 1.0.0.0+      RawSql.SqlExpression+    ) -{- |-  Constructs a 'ForeignKeyActionExpr' that uses the given 'ForeignKeyActionExpr'+{- | Constructs a 'ForeignKeyActionExpr' that uses the given 'ForeignKeyActionExpr'   in an @ON UPDATE@ clause for a foreign key.    @since 1.0.0.0@@ -168,8 +169,7 @@       <> RawSql.space       <> RawSql.toRawSql action -{- |-  Constructs a 'TableConstraint' that represent a @FOREIGN KEY@ constraint+{- | Constructs a 'TableConstraint' that represent a @FOREIGN KEY@ constraint    @since 1.0.0.0 -}@@ -177,7 +177,7 @@   -- | The names of the columns in the source table that form the foreign key.   NonEmpty ColumnName ->   -- | The name of the table that the foreign key references.-  Qualified TableName ->+  QualifiedOrUnqualified TableName ->   -- | The names of the columns in the foreign table that the foreign key references.   NonEmpty ColumnName ->   -- | An optional @ON UPDATE@ foreign key action to perform.
src/Orville/PostgreSQL/Expr/TableDefinition.hs view
@@ -1,7 +1,7 @@ {-# LANGUAGE GeneralizedNewtypeDeriving #-}  {- |-Copyright : Flipstone Technology Partners 2023+Copyright : Flipstone Technology Partners 2023-2025 License   : MIT Stability : Stable @@ -14,6 +14,7 @@   , primaryKeyExpr   , AlterTableExpr   , alterTableExpr+  , renameTableExpr   , AlterTableAction   , addColumn   , dropColumn@@ -22,6 +23,8 @@   , alterColumnType   , alterColumnSetDefault   , alterColumnDropDefault+  , alterColumnAddIdentity+  , alterColumnDropIdentity   , UsingClause   , usingCast   , alterColumnNullability@@ -30,21 +33,22 @@   , dropNotNull   , DropTableExpr   , dropTableExpr+  , TruncateTableExpr+  , truncateTablesExpr   ) where  import Data.List.NonEmpty (NonEmpty) import Data.Maybe (catMaybes, maybeToList) -import Orville.PostgreSQL.Expr.ColumnDefinition (ColumnDefinition)+import Orville.PostgreSQL.Expr.ColumnDefinition (ColumnDefinition, ColumnIdentityGeneration) import Orville.PostgreSQL.Expr.DataType (DataType) import Orville.PostgreSQL.Expr.IfExists (IfExists)-import Orville.PostgreSQL.Expr.Name (ColumnName, ConstraintName, Qualified, TableName)+import Orville.PostgreSQL.Expr.Name (ColumnName, ConstraintName, QualifiedOrUnqualified, TableName) import Orville.PostgreSQL.Expr.TableConstraint (TableConstraint) import qualified Orville.PostgreSQL.Raw.RawSql as RawSql -{- |-Type to represent a @CREATE TABLE@ statement. E.G.+{- | Type to represent a @CREATE TABLE@ statement. E.G.  > CREATE TABLE foo (id integer) @@ -56,16 +60,18 @@ -} newtype CreateTableExpr   = CreateTableExpr RawSql.RawSql-  deriving (RawSql.SqlExpression)+  deriving+    ( -- | @since 1.0.0.0+      RawSql.SqlExpression+    ) -{- |-  Constructs a 'CreateTableExpr' with the given options.+{- | Constructs a 'CreateTableExpr' with the given options.    @since 1.0.0.0 -} createTableExpr ::   -- | The name to be used for the table.-  Qualified TableName ->+  QualifiedOrUnqualified TableName ->   -- | The columns to include in the table.   [ColumnDefinition] ->   -- | A primary key expression for the table.@@ -76,10 +82,10 @@ createTableExpr tableName columnDefs mbPrimaryKey constraints =   let     columnDefsSql =-      map RawSql.toRawSql columnDefs+      fmap RawSql.toRawSql columnDefs      constraintsSql =-      map RawSql.toRawSql constraints+      fmap RawSql.toRawSql constraints      tableElementsSql =       case mbPrimaryKey of@@ -98,8 +104,7 @@         , RawSql.rightParen         ] -{- |-Type to represent the primary key of a table. E.G.+{- | Type to represent the primary key of a table. E.G.  > PRIMARY KEY (id) @@ -111,10 +116,12 @@ -} newtype PrimaryKeyExpr   = PrimaryKeyExpr RawSql.RawSql-  deriving (RawSql.SqlExpression)+  deriving+    ( -- | @since 1.0.0.0+      RawSql.SqlExpression+    ) -{- |-  Constructs a 'PrimaryKeyExpr' with the given columns.+{- | Constructs a 'PrimaryKeyExpr' with the given columns.    @since 1.0.0.0 -}@@ -128,8 +135,7 @@       , RawSql.rightParen       ] -{- |-Type to represent an @ALTER TABLE@ statement. E.G.+{- | Type to represent an @ALTER TABLE@ statement. E.G.  > ALTER TABLE foo ADD COLUMN bar integer @@ -141,14 +147,16 @@ -} newtype AlterTableExpr   = AlterTableExpr RawSql.RawSql-  deriving (RawSql.SqlExpression)+  deriving+    ( -- | @since 1.0.0.0+      RawSql.SqlExpression+    ) -{- |-  Constructs an 'AlterTableExpr' with the given alter table actions.+{- | Constructs an 'AlterTableExpr' with the given alter table actions.    @since 1.0.0.0 -}-alterTableExpr :: Qualified TableName -> NonEmpty AlterTableAction -> AlterTableExpr+alterTableExpr :: QualifiedOrUnqualified TableName -> NonEmpty AlterTableAction -> AlterTableExpr alterTableExpr tableName actions =   AlterTableExpr $     RawSql.fromString "ALTER TABLE "@@ -156,9 +164,16 @@       <> RawSql.space       <> RawSql.intercalate RawSql.commaSpace actions -{- |-Type to represent an action as part of an @ALTER TABLE@ statement. E.G.+{- | Given an existing table name and the desired name, construct an 'AlterTableExpr' that will rename to desired. +  @since 1.1.0.0+-}+renameTableExpr :: QualifiedOrUnqualified TableName -> QualifiedOrUnqualified TableName -> AlterTableExpr+renameTableExpr existingTableName newTableName =+  alterTableExpr existingTableName . pure . AlterTableAction $ RawSql.fromString "RENAME TO " <> RawSql.toRawSql newTableName++{- | Type to represent an action as part of an @ALTER TABLE@ statement. E.G.+ > ADD COLUMN bar integer  'AlterTableAction' provides a 'RawSql.SqlExpression' instance. See@@ -169,10 +184,12 @@ -} newtype AlterTableAction   = AlterTableAction RawSql.RawSql-  deriving (RawSql.SqlExpression)+  deriving+    ( -- | @since 1.0.0.0+      RawSql.SqlExpression+    ) -{- |-  Constructs an 'AlterTableAction' that will add the specified column to the+{- | Constructs an 'AlterTableAction' that will add the specified column to the   table.    @since 1.0.0.0@@ -182,8 +199,7 @@   AlterTableAction $     RawSql.fromString "ADD COLUMN " <> RawSql.toRawSql columnDef -{- |-  Constructs an 'AlterTableAction' that will drop the specified column from the+{- | Constructs an 'AlterTableAction' that will drop the specified column from the   table.    @since 1.0.0.0@@ -193,8 +209,7 @@   AlterTableAction $     RawSql.fromString "DROP COLUMN " <> RawSql.toRawSql columnName -{- |-  Constructs an 'AlterTableAction' that will add the specified constraint to the+{- | Constructs an 'AlterTableAction' that will add the specified constraint to the   table.    @since 1.0.0.0@@ -204,8 +219,7 @@   AlterTableAction $     RawSql.fromString "ADD " <> RawSql.toRawSql constraint -{- |-  Constructs an 'AlterTableAction' that will drop the specified constraint from the+{- | Constructs an 'AlterTableAction' that will drop the specified constraint from the   table.    @since 1.0.0.0@@ -215,8 +229,7 @@   AlterTableAction $     RawSql.fromString "DROP CONSTRAINT " <> RawSql.toRawSql constraintName -{- |-  Constructs an 'AlterTableAction' that will alter the type of the specified+{- | Constructs an 'AlterTableAction' that will alter the type of the specified   column.    @since 1.0.0.0@@ -241,8 +254,7 @@           : maybeToList (fmap RawSql.toRawSql maybeUsingClause)       ) -{- |-Type to represent a @USING@ clause as part of an @ALTER COLUMN@ when changing+{- | Type to represent a @USING@ clause as part of an @ALTER COLUMN@ when changing the type of a column. E.G.  > USING id :: integer@@ -255,10 +267,12 @@ -} newtype UsingClause   = UsingClause RawSql.RawSql-  deriving (RawSql.SqlExpression)+  deriving+    ( -- | @since 1.0.0.0+      RawSql.SqlExpression+    ) -{- |-  Constructs a 'UsingClause' that will cast the column to the specified type.+{- | Constructs a 'UsingClause' that will cast the column to the specified type.    @since 1.0.0.0 -}@@ -270,8 +284,7 @@       <> RawSql.doubleColon       <> RawSql.toRawSql dataType -{- |-  Constructs an 'AlterTableAction' that will alter the nullability of the+{- | Constructs an 'AlterTableAction' that will alter the nullability of the   column.    @since 1.0.0.0@@ -286,8 +299,7 @@       , RawSql.toRawSql alterNotNull       ] -{- |-Type to represent an action to alter the nullability of a column. E.G.+{- | Type to represent an action to alter the nullability of a column. E.G.  > SET NOT NULL @@ -299,10 +311,12 @@ -} newtype AlterNotNull   = AlterNotNull RawSql.RawSql-  deriving (RawSql.SqlExpression)+  deriving+    ( -- | @since 1.0.0.0+      RawSql.SqlExpression+    ) -{- |-  Sets the column to not null via @SET NOT NULL@.+{- | Sets the column to not null via @SET NOT NULL@.    @since 1.0.0.0 -}@@ -310,8 +324,7 @@ setNotNull =   AlterNotNull $ RawSql.fromString "SET NOT NULL" -{- |-  Sets the column to allow null via @DROP NOT NULL@.+{- | Sets the column to allow null via @DROP NOT NULL@.    @since 1.0.0.0 -}@@ -319,8 +332,7 @@ dropNotNull =   AlterNotNull $ RawSql.fromString "DROP NOT NULL" -{- |-  Constructs an 'AlterTableAction' that will use @DROP DEFAULT@ to drop the+{- | Constructs an 'AlterTableAction' that will use @DROP DEFAULT@ to drop the   default value of the specified column.    @since 1.0.0.0@@ -335,8 +347,7 @@       , RawSql.fromString "DROP DEFAULT"       ] -{- |-  Constructs an 'AlterTableAction' that will use @SET DEFAULT@ to set the+{- | Constructs an 'AlterTableAction' that will use @SET DEFAULT@ to set the   default value of the specified column.    @since 1.0.0.0@@ -356,9 +367,40 @@       , RawSql.toRawSql defaultValue       ] -{- |-Type to represent a @DROP TABLE@ statement. E.G.+{- | Constructs an 'AlterTableAction' that will use @ADD GENERATED .. AS IDENTITY@ to set the+   specified column to be an identity column. +  @since 1.1.0.0+-}+alterColumnAddIdentity ::+  ColumnName ->+  ColumnIdentityGeneration ->+  AlterTableAction+alterColumnAddIdentity columnName columnIdentityGeneration =+  AlterTableAction $+    RawSql.fromString "ALTER COLUMN "+      <> RawSql.toRawSql columnName+      <> RawSql.fromString " ADD GENERATED "+      <> RawSql.toRawSql columnIdentityGeneration+      <> RawSql.fromString " AS IDENTITY"++{- | Constructs an 'AlterTableAction' that will drop the identity requirement of a column++  @since 1.1.0.0+-}+alterColumnDropIdentity ::+  ColumnName ->+  Maybe IfExists ->+  AlterTableAction+alterColumnDropIdentity columnName maybeIfExists =+  AlterTableAction $+    RawSql.fromString "ALTER COLUMN "+      <> RawSql.toRawSql columnName+      <> RawSql.fromString " DROP IDENTITY"+      <> maybe mempty (\i -> RawSql.space <> RawSql.toRawSql i) maybeIfExists++{- | Type to represent a @DROP TABLE@ statement. E.G.+ > DROP TABLE FOO  'DropTableExpr' provides a 'RawSql.SqlExpression' instance. See@@ -369,14 +411,16 @@ -} newtype DropTableExpr   = DropTableExpr RawSql.RawSql-  deriving (RawSql.SqlExpression)+  deriving+    ( -- | @since 1.0.0.0+      RawSql.SqlExpression+    ) -{- |-  Constructs a 'DropTableExpr' that will drop the specified table.+{- | Constructs a 'DropTableExpr' that will drop the specified table.    @since 1.0.0.0 -}-dropTableExpr :: Maybe IfExists -> Qualified TableName -> DropTableExpr+dropTableExpr :: Maybe IfExists -> QualifiedOrUnqualified TableName -> DropTableExpr dropTableExpr maybeIfExists tableName =   DropTableExpr $     RawSql.intercalate@@ -387,3 +431,29 @@           , Just (RawSql.toRawSql tableName)           ]       )++{- | Type to represent a @TRUNCATE TABLE@ statement. E.G.++> TRUNCATE TABLE FOO++'TruncateTableExpr' provides a 'RawSql.SqlExpression' instance. See+'RawSql.unsafeSqlExpression' for how to construct a value with your own custom+SQL.++@since 1.1.0.0+-}+newtype TruncateTableExpr+  = TruncateTableExpr RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++{- | Constructs a 'TruncateTableExpr' that will truncate the specified tables.++  @since 1.1.0.0+-}+truncateTablesExpr :: NonEmpty (QualifiedOrUnqualified TableName) -> TruncateTableExpr+truncateTablesExpr tableNames =+  TruncateTableExpr $+    RawSql.fromString "TRUNCATE TABLE " <> RawSql.intercalate RawSql.commaSpace tableNames
src/Orville/PostgreSQL/Expr/TableReferenceList.hs view
@@ -1,7 +1,7 @@ {-# LANGUAGE GeneralizedNewtypeDeriving #-}  {- |-Copyright : Flipstone Technology Partners 2023+Copyright : Flipstone Technology Partners 2023-2025 License   : MIT Stability : Stable @@ -9,22 +9,23 @@ -} module Orville.PostgreSQL.Expr.TableReferenceList   ( TableReferenceList-  , referencesTable+  , tableReferenceList+  , singleTableReferenceList+  , TableReference+  , tableNameReference   ) where -import Orville.PostgreSQL.Expr.Name (Qualified, TableName)+import Orville.PostgreSQL.Expr.Name (AliasExpr, QualifiedOrUnqualified, TableName) import qualified Orville.PostgreSQL.Raw.RawSql as RawSql -{- |-Type to represent the table's references in the @FROM@ clause of a @SELECT-statement. E.G. just the+{- | Type to represent the table references in the @FROM@ clause of a @SELECT statement. E.G. just the -> foo+> foo, bar  in -> FROM foo+> FROM foo, bar  'TableReferenceList' provides a 'RawSql.SqlExpression' instance. See 'RawSql.unsafeSqlExpression' for how to construct a value with your own custom@@ -36,13 +37,56 @@   = TableReferenceList RawSql.RawSql   deriving (RawSql.SqlExpression) -{- |-  Constructs a 'TableReferenceList' consisting of just the specified table-  name.+{- | Constructs a 'TableReferenceList' from a list of 'TableReference' values. The table references+  will be separated by commas in the resulting list -  @since 1.0.0.0+@since 1.1.0.0 -}-referencesTable :: Qualified TableName -> TableReferenceList-referencesTable qualifiedTableName =-  TableReferenceList $+tableReferenceList :: [TableReference] -> TableReferenceList+tableReferenceList =+  TableReferenceList . RawSql.intercalate RawSql.commaSpace++{- | A convenience function for constructing a 'TableReferenceList' that references just a single+  table with no alias.++  See also 'tableReferenceList' and 'tableNameReference'.++@since 1.1.0.0+-}+singleTableReferenceList :: QualifiedOrUnqualified TableName -> TableReferenceList+singleTableReferenceList name =+  tableReferenceList [tableNameReference name Nothing]++{- | Type to represent a single table references in the @FROM@ clause of a @SELECT@+   statement. E.G. just the++> foo++in++> FROM foo, bar++'TableReferenceList' provides a 'RawSql.SqlExpression' instance. See+'RawSql.unsafeSqlExpression' for how to construct a value with your own custom+SQL.++@since 1.1.0.0+-}+newtype TableReference+  = TableReference RawSql.RawSql+  deriving (RawSql.SqlExpression)++{- | Constructs a 'TableReference' for the specified table name.++@since 1.1.0.0+-}+tableNameReference ::+  QualifiedOrUnqualified TableName ->+  Maybe AliasExpr ->+  TableReference+tableNameReference qualifiedTableName mbAliasExpr =+  TableReference $     RawSql.toRawSql qualifiedTableName+      <> case mbAliasExpr of+        Nothing -> mempty+        Just aliasExpr -> RawSql.fromString " AS " <> RawSql.toRawSql aliasExpr
+ src/Orville/PostgreSQL/Expr/TextSearch.hs view
@@ -0,0 +1,466 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++{- |+Copyright : Flipstone Technology Partners 2024-2025+License   : MIT+Stability : Stable++@since 1.1.0.0+-}+module Orville.PostgreSQL.Expr.TextSearch+  ( TSVector+  , tsVectorToValueExpression+  , TSQuery+  , tsQueryToValueExpression+  , TSWeight+  , tsMatch+  , tsMatchTSQueryTSVector+  , tsVectorConcat+  , toTSVector+  , toTSQuery+  , plainToTSQuery+  , toTSRank+  , setTSWeight+  , tsWeightToValueExpression+  , tsWeightA+  , tsWeightB+  , tsWeightC+  , tsWeightD+  , RegConfig+  , arabicRegConfig+  , danishRegConfig+  , dutchRegConfig+  , englishRegConfig+  , finnishRegConfig+  , frenchRegConfig+  , germanRegConfig+  , greekRegConfig+  , hungarianRegConfig+  , indonesianRegConfig+  , irishRegConfig+  , italianRegConfig+  , lithuanianRegConfig+  , nepaliRegConfig+  , norwegianRegConfig+  , portugueseRegConfig+  , romanianRegConfig+  , russianRegConfig+  , simpleRegConfig+  , spanishRegConfig+  , swedishRegConfig+  , tamilRegConfig+  , turkishRegConfig+  ) where++import Orville.PostgreSQL.Expr.BinaryOperator (BinaryOperator, binaryOpExpression, binaryOperator)+import Orville.PostgreSQL.Expr.Name (FunctionName, functionName)+import Orville.PostgreSQL.Expr.ValueExpression (ValueExpression, functionCall)+import Orville.PostgreSQL.Expr.WhereClause (BooleanExpr)+import qualified Orville.PostgreSQL.Raw.RawSql as RawSql++{- | Type to represent a PostgreSQL @tsvector@.++'TSVector' provides a 'RawSql.SqlExpression' instance. See+'RawSql.unsafeSqlExpression' for how to construct a value with your own custom+SQL.++ @since 1.1.0.0+-}+newtype TSVector+  = TSVector RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++{- | Type to represent a PostgreSQL @tsquery@.++'TSQuery' provides a 'RawSql.SqlExpression' instance. See+'RawSql.unsafeSqlExpression' for how to construct a value with your own custom+SQL.++ @since 1.1.0.0+-}+newtype TSQuery+  = TSQuery RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++{- | A type to represent the weight to be given to elements of a 'TSVector'++'TSWeight' provides a 'RawSql.SqlExpression' instance. See+'RawSql.unsafeSqlExpression' for how to construct a value with your own custom SQL.++  @since 1.1.0.0+-}+newtype TSWeight+  = TSWeight RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++tsMatchOp :: BinaryOperator+tsMatchOp = binaryOperator "@@"++tsVectorConcatOp :: BinaryOperator+tsVectorConcatOp = binaryOperator "||"++{- | Matches a 'TSVector' against a 'TSQuery'. This function is an alias for the '@@' operator.++@since 1.1.0.0+-}+tsMatch :: TSVector -> TSQuery -> BooleanExpr+tsMatch tsVector tsQuery =+  binaryOpExpression+    tsMatchOp+    (tsVectorToValueExpression tsVector)+    (tsQueryToValueExpression tsQuery)++{- | Matches a 'TSQuery' against a 'TSVector'. This function is an alias for the '@@' operator.+    Filped version of 'tsMatch'++@since 1.1.0.0+-}+tsMatchTSQueryTSVector :: TSQuery -> TSVector -> BooleanExpr+tsMatchTSQueryTSVector = flip tsMatch++{- | Concatenates two 'TSVector' values.++@since 1.1.0.0+-}+tsVectorConcat :: TSVector -> TSVector -> TSVector+tsVectorConcat tsVector1 tsVector2 =+  binaryOpExpression+    tsVectorConcatOp+    (tsVectorToValueExpression tsVector1)+    (tsVectorToValueExpression tsVector2)++{- | A constant representing the "A" weight for a 'TSVector'.++@since 1.1.0.0+-}+tsWeightA :: TSWeight+tsWeightA = TSWeight $ RawSql.fromString "\'A\'"++{- | A constant representing the "B" weight for a 'TSVector'.++@since 1.1.0.0+-}+tsWeightB :: TSWeight+tsWeightB = TSWeight $ RawSql.fromString "\'B\'"++{- | A constant representing the "C" weight for a 'TSVector'.++@since 1.1.0.0+-}+tsWeightC :: TSWeight+tsWeightC = TSWeight $ RawSql.fromString "\'C\'"++{- | A constant representing the "D" weight for a 'TSVector'.++@since 1.1.0.0+-}+tsWeightD :: TSWeight+tsWeightD = TSWeight $ RawSql.fromString "\'D\'"++toTSVectorFunction :: FunctionName+toTSVectorFunction = functionName "to_tsvector"++toTSQueryFunction :: FunctionName+toTSQueryFunction = functionName "to_tsquery"++toTSRankFunction :: FunctionName+toTSRankFunction = functionName "ts_rank"++setTSWeightFunction :: FunctionName+setTSWeightFunction = functionName "setweight"++plaintoTSQueryFunction :: FunctionName+plaintoTSQueryFunction = functionName "plainto_tsquery"++{- | Converts a 'RegConfig' to a 'ValueExpression'.++@since 1.1.0.0+-}+regConfigToValueExpression :: RegConfig -> ValueExpression+regConfigToValueExpression (RegConfig regConfig) =+  RawSql.unsafeFromRawSql+    ( RawSql.fromString "\'"+        <> regConfig+        <> RawSql.fromString "\'"+    )++{- | Converts a 'ValueExpression' to a 'TSVector', optionally using a specified 'RegConfig'.++The provided 'ValueExpression' must adhere to the following limitations:+  * It must evaluate to one of the following types: @text@, @json@, or @jsonb@.+  * Behavior may differ depending on whether @json@ or @jsonb@ is used.+  * Word normalization will be applied during the conversion process.++@since 1.1.0.0+-}+toTSVector :: ValueExpression -> Maybe RegConfig -> TSVector+toTSVector val mbRegConfig =+  TSVector+    . RawSql.toRawSql+    . functionCall toTSVectorFunction+    $ case mbRegConfig of+      Nothing -> [val]+      Just regConfig -> [regConfigToValueExpression regConfig, val]++{- | Converts a 'ValueExpression' to a 'TSQuery', optionally using a specified 'RegConfig'.++The provided 'ValueExpression' must adhere to the following limitations:+  * It must evaluate to one of the following types: @text@, @json@, or @jsonb@.+  * Behavior may differ depending on whether @json@ or @jsonb@ is used.+  * Word normalization will be applied during the conversion process.++@since 1.1.0.0+-}+toTSQuery :: ValueExpression -> Maybe RegConfig -> TSQuery+toTSQuery val mbRegConfig =+  TSQuery+    . RawSql.toRawSql+    . functionCall toTSQueryFunction+    $ case mbRegConfig of+      Nothing -> [val]+      Just regConfig -> [regConfigToValueExpression regConfig, val]++{- | Converts a 'TSQuery' to a 'ValueExpression'.++@since 1.1.0.0+-}+tsQueryToValueExpression :: TSQuery -> ValueExpression+tsQueryToValueExpression (TSQuery rawSql) = RawSql.unsafeFromRawSql rawSql++{- | Converts a 'TSVector' to a 'ValueExpression'.++@since 1.1.0.0+-}+tsVectorToValueExpression :: TSVector -> ValueExpression+tsVectorToValueExpression (TSVector rawSql) = RawSql.unsafeFromRawSql rawSql++{- | Perform a text search ranking based on how well the 'TSVector' and 'TSQuery' match.++@since 1.1.0.0+-}+toTSRank :: TSVector -> TSQuery -> ValueExpression+toTSRank tsVector tsQuery =+  functionCall+    toTSRankFunction+    [ tsVectorToValueExpression tsVector+    , tsQueryToValueExpression tsQuery+    ]++{- | Assigns the given weight to each element of a 'TSVector'.++@since 1.1.0.0+-}+setTSWeight :: TSVector -> TSWeight -> TSVector+setTSWeight tsVector tsWeight =+  TSVector . RawSql.toRawSql $+    functionCall+      setTSWeightFunction+      [tsVectorToValueExpression tsVector, tsWeightToValueExpression tsWeight]++{- | Converts a 'TSWeight' to a 'ValueExpression'.++@since 1.1.0.0+-}+tsWeightToValueExpression :: TSWeight -> ValueExpression+tsWeightToValueExpression (TSWeight rawSql) = RawSql.unsafeFromRawSql rawSql++{- | Converts a 'ValueExpression' into a 'TSQuery', optionally using a specified 'RegConfig'.  The+     'ValueExpression' must be text. All punctuation in the given 'ValueExpression' will be ignored+     and individual words will be combined with a logical AND. This results in a 'TSQuery' that+     matches when all words are present.++@since 1.1.0.0+-}+plainToTSQuery :: ValueExpression -> Maybe RegConfig -> TSQuery+plainToTSQuery val mbRegConfig =+  TSQuery+    . RawSql.toRawSql+    . functionCall plaintoTSQueryFunction+    $ case mbRegConfig of+      Nothing -> [val]+      Just regConfig -> [regConfigToValueExpression regConfig, val]++{- | Type to represent a text search configuration.++'RegConfig' provides a 'RawSql.SqlExpression' instance. See+'RawSql.unsafeSqlExpression' for how to construct a value with your own custom+SQL.++ @since 1.1.0.0+-}+newtype RegConfig+  = RegConfig RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++{- | The "arabic" text search configuration.++@since 1.1.0.0+-}+arabicRegConfig :: RegConfig+arabicRegConfig = RegConfig $ RawSql.fromString "arabic"++{- | The "danish" text search configuration.++@since 1.1.0.0+-}+danishRegConfig :: RegConfig+danishRegConfig = RegConfig $ RawSql.fromString "danish"++{- | The "dutch" text search configuration.++@since 1.1.0.0+-}+dutchRegConfig :: RegConfig+dutchRegConfig = RegConfig $ RawSql.fromString "dutch"++{- | The "english" text search configuration.++@since 1.1.0.0+-}+englishRegConfig :: RegConfig+englishRegConfig = RegConfig $ RawSql.fromString "english"++{- | The "finnish" text search configuration.++@since 1.1.0.0+-}+finnishRegConfig :: RegConfig+finnishRegConfig = RegConfig $ RawSql.fromString "finnish"++{- | The "french" text search configuration.++@since 1.1.0.0+-}+frenchRegConfig :: RegConfig+frenchRegConfig = RegConfig $ RawSql.fromString "french"++{- | The "german" text search configuration.++@since 1.1.0.0+-}+germanRegConfig :: RegConfig+germanRegConfig = RegConfig $ RawSql.fromString "german"++{- | The "greek" text search configuration.++@since 1.1.0.0+-}+greekRegConfig :: RegConfig+greekRegConfig = RegConfig $ RawSql.fromString "greek"++{- | The "hungarian" text search configuration.++@since 1.1.0.0+-}+hungarianRegConfig :: RegConfig+hungarianRegConfig = RegConfig $ RawSql.fromString "hungarian"++{- | The "indonesian" text search configuration.++@since 1.1.0.0+-}+indonesianRegConfig :: RegConfig+indonesianRegConfig = RegConfig $ RawSql.fromString "indonesian"++{- | The "irish" text search configuration.++@since 1.1.0.0+-}+irishRegConfig :: RegConfig+irishRegConfig = RegConfig $ RawSql.fromString "irish"++{- | The "italian" text search configuration.++@since 1.1.0.0+-}+italianRegConfig :: RegConfig+italianRegConfig = RegConfig $ RawSql.fromString "italian"++{- | The "lithuanian" text search configuration.++@since 1.1.0.0+-}+lithuanianRegConfig :: RegConfig+lithuanianRegConfig = RegConfig $ RawSql.fromString "lithuanian"++{- | The "nepali" text search configuration.++@since 1.1.0.0+-}+nepaliRegConfig :: RegConfig+nepaliRegConfig = RegConfig $ RawSql.fromString "nepali"++{- | The "norwegian" text search configuration.++@since 1.1.0.0+-}+norwegianRegConfig :: RegConfig+norwegianRegConfig = RegConfig $ RawSql.fromString "norwegian"++{- | The "portuguese" text search configuration.++@since 1.1.0.0+-}+portugueseRegConfig :: RegConfig+portugueseRegConfig = RegConfig $ RawSql.fromString "portuguese"++{- | The "romanian" text search configuration.++@since 1.1.0.0+-}+romanianRegConfig :: RegConfig+romanianRegConfig = RegConfig $ RawSql.fromString "romanian"++{- | The "russian" text search configuration.++@since 1.1.0.0+-}+russianRegConfig :: RegConfig+russianRegConfig = RegConfig $ RawSql.fromString "russian"++{- | The "simple" text search configuration.++@since 1.1.0.0+-}+simpleRegConfig :: RegConfig+simpleRegConfig = RegConfig $ RawSql.fromString "simple"++{- | The "spanish" text search configuration.++@since 1.1.0.0+-}+spanishRegConfig :: RegConfig+spanishRegConfig = RegConfig $ RawSql.fromString "spanish"++{- | The "swedish" text search configuration.++@since 1.1.0.0+-}+swedishRegConfig :: RegConfig+swedishRegConfig = RegConfig $ RawSql.fromString "swedish"++{- | The "tamil" text search configuration.++@since 1.1.0.0+-}+tamilRegConfig :: RegConfig+tamilRegConfig = RegConfig $ RawSql.fromString "tamil"++{- | The "turkish" text search configuration.++@since 1.1.0.0+-}+turkishRegConfig :: RegConfig+turkishRegConfig = RegConfig $ RawSql.fromString "turkish"
src/Orville/PostgreSQL/Expr/Time.hs view
@@ -25,16 +25,14 @@ import Orville.PostgreSQL.Expr.ValueExpression (ParameterName, ValueExpression, functionCall, functionCallNamedParams) import qualified Orville.PostgreSQL.Raw.RawSql as RawSql -{- |-  The value of the current time as returned by the PostgreSQL function @now()@.+{- | The value of the current time as returned by the PostgreSQL function @now()@.    @since 1.0.0.0 -} now :: ValueExpression now = functionCall (functionName "now") [] -{- |-  Constructs a 'ValueExpression' whose value in PostgreSQL is the result of+{- | Constructs a 'ValueExpression' whose value in PostgreSQL is the result of   calling @make_interval@ with the specified time intervals passed as named   arguments. @@ -46,8 +44,7 @@     (functionName "make_interval")     (fmap (\(IntervalArgument paramName, value) -> (paramName, value)) args) -{- |-Type to represent the name of a time interval argument to the PostgreSQL+{- | Type to represent the name of a time interval argument to the PostgreSQL @make_interval@ function. E.G.  > years@@ -60,10 +57,12 @@ -} newtype IntervalArgument   = IntervalArgument ParameterName-  deriving (RawSql.SqlExpression)+  deriving+    ( -- | @since 1.0.0.0+      RawSql.SqlExpression+    ) -{- |-  Constructs an arbitrary 'IntervalArgument' with whatever name you specify. It+{- | Constructs an arbitrary 'IntervalArgument' with whatever name you specify. It   is up to you to ensure that name a valid argument name for @make_interval@.    @since 1.0.0.0@@ -72,56 +71,49 @@ unsafeIntervalArg =   IntervalArgument . RawSql.unsafeSqlExpression -{- |-  The @years@ argument to @make_interval@.+{- | The @years@ argument to @make_interval@.    @since 1.0.0.0 -} years :: IntervalArgument years = unsafeIntervalArg "years" -{- |-  The @months@ argument to @make_interval@.+{- | The @months@ argument to @make_interval@.    @since 1.0.0.0 -} months :: IntervalArgument months = unsafeIntervalArg "months" -{- |-  The @weeks@ argument to @make_interval@.+{- | The @weeks@ argument to @make_interval@.    @since 1.0.0.0 -} weeks :: IntervalArgument weeks = unsafeIntervalArg "weeks" -{- |-  The @days@ argument to @make_interval@.+{- | The @days@ argument to @make_interval@.    @since 1.0.0.0 -} days :: IntervalArgument days = unsafeIntervalArg "days" -{- |-  The @hours@ argument to @make_interval@.+{- | The @hours@ argument to @make_interval@.    @since 1.0.0.0 -} hours :: IntervalArgument hours = unsafeIntervalArg "hours" -{- |-  The @mins@ argument to @make_interval@.+{- | The @mins@ argument to @make_interval@.    @since 1.0.0.0 -} minutes :: IntervalArgument minutes = unsafeIntervalArg "mins" -{- |-  The @secs@ argument to @make_interval@.+{- | The @secs@ argument to @make_interval@.    @since 1.0.0.0 -}
src/Orville/PostgreSQL/Expr/Transaction.hs view
@@ -38,8 +38,7 @@ import qualified Orville.PostgreSQL.Expr.Name as Name import qualified Orville.PostgreSQL.Raw.RawSql as RawSql -{- |-Type to represent the name of a begin transaction statement. E.G.+{- | Type to represent the name of a begin transaction statement. E.G.  > BEGIN TRANSACTION @@ -51,24 +50,24 @@ -} newtype BeginTransactionExpr   = BeginTransactionExpr RawSql.RawSql-  deriving (RawSql.SqlExpression)+  deriving+    ( -- | @since 1.0.0.0+      RawSql.SqlExpression+    ) -{- |-  Constructs a 'BeginTransactionExpr' that will begin a transaction using+{- | Constructs a 'BeginTransactionExpr' that will begin a transaction using   the specified mode, if any.    @since 1.0.0.0 -} beginTransaction :: Maybe TransactionMode -> BeginTransactionExpr beginTransaction maybeTransactionMode =-  BeginTransactionExpr $-    RawSql.intercalate RawSql.space $-      ( RawSql.fromString "BEGIN TRANSACTION"-          : maybeToList (RawSql.toRawSql <$> maybeTransactionMode)-      )+  BeginTransactionExpr+    . RawSql.intercalate RawSql.space+    $ RawSql.fromString "BEGIN TRANSACTION"+      : maybeToList (RawSql.toRawSql <$> maybeTransactionMode) -{- |-Type to represent the transaction mode. E.G.+{- | Type to represent the transaction mode. E.G.  > ISOLATION LEVEL SERIALIZABLE @@ -80,10 +79,12 @@ -} newtype TransactionMode   = TransactionMode RawSql.RawSql-  deriving (RawSql.SqlExpression)+  deriving+    ( -- | @since 1.0.0.0+      RawSql.SqlExpression+    ) -{- |-  The @READ WRITE@ transaction mode.+{- | The @READ WRITE@ transaction mode.    @since 1.0.0.0 -}@@ -91,8 +92,7 @@ readWrite =   TransactionMode (RawSql.fromString "READ WRITE") -{- |-  The @READ ONLY@ transaction mode.+{- | The @READ ONLY@ transaction mode.    @since 1.0.0.0 -}@@ -100,8 +100,7 @@ readOnly =   TransactionMode (RawSql.fromString "READ ONLY") -{- |-  The @DEFERRABLE@ transaction mode.+{- | The @DEFERRABLE@ transaction mode.    @since 1.0.0.0 -}@@ -109,8 +108,7 @@ deferrable =   TransactionMode (RawSql.fromString "DEFERRABLE") -{- |-  The @NOT DEFERRABLE@ transaction mode.+{- | The @NOT DEFERRABLE@ transaction mode.    @since 1.0.0.0 -}@@ -118,18 +116,16 @@ notDeferrable =   TransactionMode (RawSql.fromString "NOT DEFERRABLE") -{- |-  An @ISOLATION LEVEL@ transaction mode with the given 'IsolationLevel'.+{- | An @ISOLATION LEVEL@ transaction mode with the given 'IsolationLevel'.    @since 1.0.0.0 -} isolationLevel :: IsolationLevel -> TransactionMode isolationLevel level =-  TransactionMode $+  TransactionMode     (RawSql.fromString "ISOLATION LEVEL " <> RawSql.toRawSql level) -{- |-Type to represent the transaction isolation level. E.G.+{- | Type to represent the transaction isolation level. E.G.  > SERIALIZABLE @@ -141,10 +137,12 @@ -} newtype IsolationLevel   = IsolationLevel RawSql.RawSql-  deriving (RawSql.SqlExpression)+  deriving+    ( -- | @since 1.0.0.0+      RawSql.SqlExpression+    ) -{- |-  The @SERIALIZABLE@ isolation level.+{- | The @SERIALIZABLE@ isolation level.    @since 1.0.0.0 -}@@ -152,8 +150,7 @@ serializable =   IsolationLevel (RawSql.fromString "SERIALIZABLE") -{- |-  The @REPEATABLE READ@ isolation level.+{- | The @REPEATABLE READ@ isolation level.    @since 1.0.0.0 -}@@ -161,8 +158,7 @@ repeatableRead =   IsolationLevel (RawSql.fromString "REPEATABLE READ") -{- |-  The @READ COMMITTED@ isolation level.+{- | The @READ COMMITTED@ isolation level.    @since 1.0.0.0 -}@@ -170,8 +166,7 @@ readCommitted =   IsolationLevel (RawSql.fromString "READ COMMITTED") -{- |-  The @READ UNCOMMITTED@ isolation level.+{- | The @READ UNCOMMITTED@ isolation level.    @since 1.0.0.0 -}@@ -179,8 +174,7 @@ readUncommitted =   IsolationLevel (RawSql.fromString "READ UNCOMMITTED") -{- |-Type to represent the transaction commit statement. E.G.+{- | Type to represent the transaction commit statement. E.G.  > COMMIT @@ -192,10 +186,12 @@ -} newtype CommitExpr   = CommitExpr RawSql.RawSql-  deriving (RawSql.SqlExpression)+  deriving+    ( -- | @since 1.0.0.0+      RawSql.SqlExpression+    ) -{- |-  A @COMMIT@ transaction statement.+{- | A @COMMIT@ transaction statement.    @since 1.0.0.0 -}@@ -203,8 +199,7 @@ commit =   CommitExpr (RawSql.fromString "COMMIT") -{- |-Type to represent the transaction rollback statement. E.G.+{- | Type to represent the transaction rollback statement. E.G.  > ROLLBACK @@ -216,10 +211,12 @@ -} newtype RollbackExpr   = RollbackExpr RawSql.RawSql-  deriving (RawSql.SqlExpression)+  deriving+    ( -- | @since 1.0.0.0+      RawSql.SqlExpression+    ) -{- |-  A @ROLLBACK@ transaction statement.+{- | A @ROLLBACK@ transaction statement.    @since 1.0.0.0 -}@@ -227,8 +224,7 @@ rollback =   RollbackExpr (RawSql.fromString "ROLLBACK") -{- |-  A @ROLLBACK TO@ transaction statement that will rollback to the specified+{- | A @ROLLBACK TO@ transaction statement that will rollback to the specified   savepoint.    @since 1.0.0.0@@ -238,8 +234,7 @@   RollbackExpr $     RawSql.fromString "ROLLBACK TO SAVEPOINT " <> RawSql.toRawSql savepointName -{- |-Type to represent the transaction savepoint statement. E.G.+{- | Type to represent the transaction savepoint statement. E.G.  > SAVEPOINT foo @@ -251,10 +246,12 @@ -} newtype SavepointExpr   = SavepointExpr RawSql.RawSql-  deriving (RawSql.SqlExpression)+  deriving+    ( -- | @since 1.0.0.0+      RawSql.SqlExpression+    ) -{- |-  A @SAVEPOINT@ statement that will create a savepoint with the given name.+{- | A @SAVEPOINT@ statement that will create a savepoint with the given name.    @since 1.0.0.0 -}@@ -263,8 +260,7 @@   SavepointExpr $     RawSql.fromString "SAVEPOINT " <> RawSql.toRawSql savepointName -{- |-Type to represent the transaction release savepoint statement. E.G.+{- | Type to represent the transaction release savepoint statement. E.G.  > RELEASE SAVEPOINT foo @@ -276,10 +272,12 @@ -} newtype ReleaseSavepointExpr   = ReleaseSavepontExpr RawSql.RawSql-  deriving (RawSql.SqlExpression)+  deriving+    ( -- | @since 1.0.0.0+      RawSql.SqlExpression+    ) -{- |-  A @RELEASE SAVEPOINT@ statement that will release the specified savepoint.+{- | A @RELEASE SAVEPOINT@ statement that will release the specified savepoint.    @since 1.0.0.0 -}
+ src/Orville/PostgreSQL/Expr/Trigger.hs view
@@ -0,0 +1,270 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++{- |+Copyright : Flipstone Technology Partners 2024+License   : MIT+Stability : Stable++@since 1.1.0.0+-}+module Orville.PostgreSQL.Expr.Trigger+  ( DropTriggerExpr+  , dropTrigger+  , CreateTriggerExpr+  , createTrigger+  , TriggerTiming+  , triggerBefore+  , triggerAfter+  , triggerInsteadOf+  , TriggerEvent+  , triggerOnInsert+  , triggerOnUpdate+  , triggerOnUpdateOf+  , triggerOnDelete+  , triggerOnTruncate+  , TriggerFireScope+  , triggerForEachRow+  , triggerForEachStatement+  ) where++import qualified Data.List.NonEmpty as NEL+import Data.Maybe (catMaybes)++import Orville.PostgreSQL.Expr.IfExists (IfExists)+import Orville.PostgreSQL.Expr.Name (ColumnName, FunctionName, QualifiedOrUnqualified, TableName, TriggerName)+import Orville.PostgreSQL.Expr.OrReplace (OrReplace)+import qualified Orville.PostgreSQL.Raw.RawSql as RawSql++{- | Type to represent a SQL "DROP TRIGGER" statement. E.G.++> DROP TRIGGER my_trigger ON my_table++'DropTriggerExpr' provides a 'RawSql.SqlExpression' instance. See+'RawSql.unsafeSqlExpression' for how to construct a value with your own custom+SQL.++@since 1.1.0.0+-}+newtype DropTriggerExpr+  = DropTriggerExpr RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++{- | Constructs a SQL @DROP TRIGGER@ statement from the trigger name and table name++@since 1.1.0.0+-}+dropTrigger ::+  Maybe IfExists ->+  TriggerName ->+  QualifiedOrUnqualified TableName ->+  DropTriggerExpr+dropTrigger maybeIfExists name tableName =+  DropTriggerExpr $+    RawSql.intercalate+      RawSql.space+      ( catMaybes+          [ Just (RawSql.fromString "DROP TRIGGER")+          , fmap RawSql.toRawSql maybeIfExists+          , Just (RawSql.toRawSql name)+          , Just (RawSql.fromString "ON")+          , Just (RawSql.toRawSql tableName)+          ]+      )++{- | Type to represent a SQL "CREATE TRIGGER" statement. E.G.++> CREATE TRIGGER my_trigger BEFORE UPDATE ON my_table EXECUTE PROCEDURE my_trigger_function++'CreateTriggerExpr' provides a 'RawSql.SqlExpression' instance. See+'RawSql.unsafeSqlExpression' for how to construct a value with your own custom+SQL.++@since 1.1.0.0+-}+newtype CreateTriggerExpr+  = CreateTriggerExpr RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++{- | Constructs a SQL @CREATE TRIGGER@ statement from the trigger name and its defining+attributes.++Note: Orville does not currently support creating triggers with arguments.++Note: Before PostgreSQL 14, there is no @CREATE OR REPLACE TRIGGER@ syntax in+PsotgreSQL. If you're using a version of PostgreSQL prior to 14 then you must+specify 'Nothing' for the 'OrReplace' property.++@since 1.1.0.0+-}+createTrigger ::+  Maybe OrReplace ->+  TriggerName ->+  TriggerTiming ->+  NEL.NonEmpty TriggerEvent ->+  QualifiedOrUnqualified TableName ->+  TriggerFireScope ->+  QualifiedOrUnqualified FunctionName ->+  CreateTriggerExpr+createTrigger maybeOrReplace name timing events tableName fireScope functionName =+  CreateTriggerExpr $+    RawSql.intercalate+      RawSql.space+      ( catMaybes+          [ Just $ RawSql.fromString "CREATE"+          , RawSql.toRawSql <$> maybeOrReplace+          , Just $ RawSql.fromString "TRIGGER"+          , Just $ RawSql.toRawSql name+          , Just $ RawSql.toRawSql timing+          , Just $ RawSql.intercalate (RawSql.fromString " OR ") events+          , Just $ RawSql.fromString "ON"+          , Just $ RawSql.toRawSql tableName+          , Just $ RawSql.toRawSql fireScope+          , Just $ RawSql.fromString "EXECUTE FUNCTION"+          , Just $ RawSql.toRawSql functionName+          , Just $ RawSql.fromString "()" -- we don't currently support arguments+          ]+      )++{- | Type to represent the time at which a trigger will fire in releation to the+event that caused it. E.G. the @BEFORE@ in++> CREATE TRIGGER my_trigger BEFORE UPDATE ON my_table FOR EACH ROW EXECUTE PROCEDURE my_trigger_function++'TriggerTiming' provides a 'RawSql.SqlExpression' instance. See+'RawSql.unsafeSqlExpression' for how to construct a value with your own custom+SQL.++@since 1.1.0.0+-}+newtype TriggerTiming+  = TriggerTiming RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++{- | The @BEFORE@ 'TriggerTiming'.++@since 1.1.0.0+-}+triggerBefore :: TriggerTiming+triggerBefore =+  TriggerTiming (RawSql.fromString "BEFORE")++{- | The @AFTER@ 'TriggerTiming'.++@since 1.1.0.0+-}+triggerAfter :: TriggerTiming+triggerAfter =+  TriggerTiming (RawSql.fromString "AFTER")++{- | The @INSTEAD OF@ 'TriggerTiming'.++@since 1.1.0.0+-}+triggerInsteadOf :: TriggerTiming+triggerInsteadOf =+  TriggerTiming (RawSql.fromString "INSTEAD OF")++{- | Type to represent the an event that will cause a trigger to fire in a SQL "CREATE+TRIGGER" statement. E.G. the @UPDATE@ in++> CREATE TRIGGER my_trigger BEFORE UPDATE ON my_table FOR EACH ROW EXECUTE PROCEDURE my_trigger_function++'TriggerEvent' provides a 'RawSql.SqlExpression' instance. See+'RawSql.unsafeSqlExpression' for how to construct a value with your own custom+SQL.++@since 1.1.0.0+-}+newtype TriggerEvent+  = TriggerEvent RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++{- | The @INSERT@ 'TriggerEvent'++@since 1.1.0.0+-}+triggerOnInsert :: TriggerEvent+triggerOnInsert =+  TriggerEvent (RawSql.fromString "INSERT")++{- | The @UPDATE@ 'TriggerEvent'++@since 1.1.0.0+-}+triggerOnUpdate :: TriggerEvent+triggerOnUpdate =+  TriggerEvent (RawSql.fromString "UPDATE")++{- | The @UPDATE OF@ 'TriggerEvent'. This causes the trigger only to fire when on of+the specified columns is mentioned of affected by an update.++@since 1.1.0.0+-}+triggerOnUpdateOf :: NEL.NonEmpty ColumnName -> TriggerEvent+triggerOnUpdateOf columnNames =+  TriggerEvent $+    RawSql.fromString "UPDATE OF"+      <> RawSql.intercalate RawSql.comma (fmap RawSql.toRawSql columnNames)++{- | The @DELETE@ 'TriggerEvent'++@since 1.1.0.0+-}+triggerOnDelete :: TriggerEvent+triggerOnDelete =+  TriggerEvent (RawSql.fromString "DELETE")++{- | The @TRUNCATE@ 'TriggerEvent'++@since 1.1.0.0+-}+triggerOnTruncate :: TriggerEvent+triggerOnTruncate =+  TriggerEvent (RawSql.fromString "TRUNCATE")++{- | Type to represent the part of a SQL "CREATE TRIGGER" statement that indicates whether+the trigger will be fired once for each row or once for each statement. E.G. the+@FOR EACH ROW@ in++> CREATE TRIGGER my_trigger BEFORE UPDATE ON my_table FOR EACH ROW EXECUTE PROCEDURE my_trigger_function++'TriggerFireScope' provides a 'RawSql.SqlExpression' instance. See+'RawSql.unsafeSqlExpression' for how to construct a value with your own custom+SQL.++@since 1.1.0.0+-}+newtype TriggerFireScope+  = TriggerFireScope RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++{- | The @FOR EACH ROW@ 'TriggerFireScope'++@since 1.1.0.0+-}+triggerForEachRow :: TriggerFireScope+triggerForEachRow =+  TriggerFireScope (RawSql.fromString "FOR EACH ROW")++{- | The @FOR EACH STATEMENT@ 'TriggerFireScope'++@since 1.1.0.0+-}+triggerForEachStatement :: TriggerFireScope+triggerForEachStatement =+  TriggerFireScope (RawSql.fromString "FOR EACH STATEMENT")
src/Orville/PostgreSQL/Expr/Update.hs view
@@ -1,7 +1,7 @@ {-# LANGUAGE GeneralizedNewtypeDeriving #-}  {- |-Copyright : Flipstone Technology Partners 2023+Copyright : Flipstone Technology Partners 2023-2025 License   : MIT Stability : Stable @@ -14,20 +14,24 @@   , setClauseList   , SetClause   , setColumn+  , setColumnExpression+  , UpdateNamedOnly+  , onlyUpdateNamedTable   ) where  import Data.List.NonEmpty (NonEmpty) import Data.Maybe (catMaybes) -import Orville.PostgreSQL.Expr.Name (ColumnName, Qualified, TableName)+import Orville.PostgreSQL.Expr.Name (ColumnName, QualifiedOrUnqualified, TableName) import Orville.PostgreSQL.Expr.ReturningExpr (ReturningExpr)+import Orville.PostgreSQL.Expr.TableReferenceList (TableReferenceList)+import Orville.PostgreSQL.Expr.ValueExpression (ValueExpression) import Orville.PostgreSQL.Expr.WhereClause (WhereClause) import qualified Orville.PostgreSQL.Raw.RawSql as RawSql import qualified Orville.PostgreSQL.Raw.SqlValue as SqlValue -{- |-Type to represent a SQL @UPDATE@ statement. E.G.+{- | Type to represent a SQL @UPDATE@ statement. E.G.  > UPDATE foo > SET id = 1@@ -41,38 +45,78 @@ -} newtype UpdateExpr   = UpdateExpr RawSql.RawSql-  deriving (RawSql.SqlExpression)+  deriving+    ( -- | @since 1.0.0.0+      RawSql.SqlExpression+    ) -{- |-  Constructs an 'UpdateExpr' with the given options.+{- | Constructs an 'UpdateExpr' with the given options. -  @since 1.0.0.0+@since 1.0.0.0 -} updateExpr ::   -- | The name of the table to be updated.-  Qualified TableName ->+  QualifiedOrUnqualified TableName ->+  -- | Optionally update only the given table name+  Maybe UpdateNamedOnly ->   -- | The updates to be made to the table.   SetClauseList ->+  -- | An optional 'TableReferenceList' to allow columns from other tables to be included in the+  -- 'SetClauseList' and 'WhereClause'+  --+  -- @since 1.1.0.0+  Maybe TableReferenceList ->   -- | An optional where clause to limit the rows updated.   Maybe WhereClause ->   -- | An optional returning clause to return data from the updated rows.   Maybe ReturningExpr ->   UpdateExpr-updateExpr tableName setClause maybeWhereClause maybeReturningExpr =-  UpdateExpr $-    RawSql.intercalate RawSql.space $-      catMaybes+updateExpr tableName maybeUpdateNamedOnly setClause maybeTableRefs maybeWhereClause maybeReturningExpr =+  let+    buildTableRefs :: TableReferenceList -> RawSql.RawSql+    buildTableRefs tableRefs =+      RawSql.fromString "FROM " <> RawSql.toRawSql tableRefs+  in+    UpdateExpr+      . RawSql.intercalate RawSql.space+      $ catMaybes         [ Just $ RawSql.fromString "UPDATE"+        , fmap RawSql.toRawSql maybeUpdateNamedOnly         , Just $ RawSql.toRawSql tableName         , Just $ RawSql.fromString "SET"         , Just $ RawSql.toRawSql setClause+        , fmap buildTableRefs maybeTableRefs         , fmap RawSql.toRawSql maybeWhereClause         , fmap RawSql.toRawSql maybeReturningExpr         ] -{- |-Type to represent the list of updates to be made in an @UPDATE@ statement. E.G.+{- | Type to represent the option to update only the named table and not any descendants. E.G. +> ONLY++'UpdateNamedOnly' provides a 'RawSql.SqlExpression' instance. See+'RawSql.unsafeSqlExpression' for how to construct a value with your own custom+SQL.++@since 1.1.0.0+-}+newtype UpdateNamedOnly+  = UpdateNamedOnly RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++{- | The @ONLY@ modifier to an @UPDATE@ to not update any descendant tables.++@since 1.1.0.0+-}+onlyUpdateNamedTable :: UpdateNamedOnly+onlyUpdateNamedTable =+  UpdateNamedOnly $ RawSql.fromString "ONLY"++{- | Type to represent the list of updates to be made in an @UPDATE@ statement. E.G.+ > foo = 1, > bar = 2 @@ -84,10 +128,12 @@ -} newtype SetClauseList   = SetClauseList RawSql.RawSql-  deriving (RawSql.SqlExpression)+  deriving+    ( -- | @since 1.0.0.0+      RawSql.SqlExpression+    ) -{- |-  Constructs a 'SetClauseList' with the specified set clauses.+{- | Constructs a 'SetClauseList' with the specified set clauses.    @since 1.0.0.0 -}@@ -95,8 +141,7 @@ setClauseList =   SetClauseList . RawSql.intercalate RawSql.comma -{- |-Type to represent a single update to be made in an @UPDATE@ statement. E.G.+{- | Type to represent a single update to be made in an @UPDATE@ statement. E.G.  > foo = 1 @@ -108,17 +153,33 @@ -} newtype SetClause   = SetClause RawSql.RawSql-  deriving (RawSql.SqlExpression)+  deriving+    ( -- | @since 1.0.0.0+      RawSql.SqlExpression+    ) -{- |-  Constructs a 'SetClause' that will set the specified column to the specified+{- | Constructs a 'SetClause' that will set the specified column to the specified   value.    @since 1.0.0.0 -}-setColumn :: ColumnName -> SqlValue.SqlValue -> SetClause+setColumn :: QualifiedOrUnqualified ColumnName -> SqlValue.SqlValue -> SetClause setColumn columnName value =   SetClause $     RawSql.toRawSql columnName       <> RawSql.fromString "="       <> RawSql.parameter value++{- | Constructs a 'SetClause' that will set the specified columns to the expression.++@since 1.1.0.0+-}+setColumnExpression ::+  NonEmpty (QualifiedOrUnqualified ColumnName) ->+  ValueExpression ->+  SetClause+setColumnExpression columnNames value =+  SetClause $+    RawSql.intercalate RawSql.comma (fmap RawSql.toRawSql columnNames)+      <> RawSql.fromString "="+      <> RawSql.parenthesized (RawSql.toRawSql value)
+ src/Orville/PostgreSQL/Expr/Vacuum.hs view
@@ -0,0 +1,117 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++{- |+Copyright : Flipstone Technology Partners 2024+License   : MIT+Stability : Stable++@since 1.1.0.0+-}+module Orville.PostgreSQL.Expr.Vacuum+  ( VacuumExpr+  , vacuumExpr+  , VacuumOption+  , vacuumFull+  , vacuumFreeze+  , vacuumVerbose+  , vacuumAnalyze+  )+where++import Data.List.NonEmpty (NonEmpty)++import Orville.PostgreSQL.Expr.Name (QualifiedOrUnqualified, TableName)+import qualified Orville.PostgreSQL.Raw.RawSql as RawSql++{- | Type to represent a @VACUUM@ statement. E.G.++> VACUUM foo++'VacuumExpr' provides a 'RawSql.SqlExpression' instance. See+'RawSql.unsafeSqlExpression' for how to construct a value with your own custom+SQL.++@since 1.1.0.0+-}+newtype VacuumExpr+  = VacuumExpr RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++{- | Constructs a 'VacuumExpr' with the given vacuum options on the given tables.++  @since 1.1.0.0+-}+vacuumExpr :: [VacuumOption] -> NonEmpty (QualifiedOrUnqualified TableName) -> VacuumExpr+vacuumExpr vacuumOptions tables =+  let+    optionsWithSpaceRawSql =+      case vacuumOptions of+        [] ->+          RawSql.space+        opts ->+          RawSql.space+            <> RawSql.parenthesized (RawSql.intercalate RawSql.commaSpace opts)+            <> RawSql.space+  in+    VacuumExpr $+      RawSql.fromString "VACUUM"+        <> optionsWithSpaceRawSql+        <> RawSql.intercalate RawSql.commaSpace tables++{- | Type to represent the option(s) to a @VACUUM@ statement. E.G.++> FULL TRUE++'VacuumExpr' provides a 'RawSql.SqlExpression' instance. See+'RawSql.unsafeSqlExpression' for how to construct a value with your own custom+SQL.++@since 1.1.0.0+-}+newtype VacuumOption+  = VacuumOption RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++{- | Constructs a 'VaccumOption' that will instruct if the vacuum should be "full".++  @since 1.1.0.0+-}+vacuumFull :: Bool -> VacuumOption+vacuumFull bool =+  VacuumOption $ RawSql.fromString "FULL " <> boolRawSql bool++{- | Constructs a 'VaccumOption' that will instruct if the vacuum should be agressive in "freezing" of+  tuples.++  @since 1.1.0.0+-}+vacuumFreeze :: Bool -> VacuumOption+vacuumFreeze bool =+  VacuumOption $ RawSql.fromString "FREEZE " <> boolRawSql bool++{- | Constructs a 'VaccumOption' that will instruct if the vacuum should be verbose.++  @since 1.1.0.0+-}+vacuumVerbose :: Bool -> VacuumOption+vacuumVerbose bool =+  VacuumOption $ RawSql.fromString "VERBOSE " <> boolRawSql bool++{- | Constructs a 'VaccumOption' that will instruct if the vacuum should "analyze" to update statitics+  used by the PostgreSQL query planner.++  @since 1.1.0.0+-}+vacuumAnalyze :: Bool -> VacuumOption+vacuumAnalyze bool =+  VacuumOption $ RawSql.fromString "ANALYZE " <> boolRawSql bool++boolRawSql :: Bool -> RawSql.RawSql+boolRawSql bool =+  if bool then RawSql.fromString "TRUE" else RawSql.fromString "FALSE"
src/Orville/PostgreSQL/Expr/ValueExpression.hs view
@@ -1,7 +1,7 @@ {-# LANGUAGE GeneralizedNewtypeDeriving #-}  {- |-Copyright : Flipstone Technology Partners 2023+Copyright : Flipstone Technology Partners 2023-2025 License   : MIT Stability : Stable @@ -22,12 +22,11 @@ import qualified Data.List.NonEmpty as NE  import Orville.PostgreSQL.Expr.DataType (DataType)-import Orville.PostgreSQL.Expr.Name (ColumnName, FunctionName)+import Orville.PostgreSQL.Expr.Name (ColumnName, FunctionName, QualifiedOrUnqualified) import qualified Orville.PostgreSQL.Raw.RawSql as RawSql import Orville.PostgreSQL.Raw.SqlValue (SqlValue) -{- |-Type to represent an arbitrary value in a SQL expression. This could be a+{- | Type to represent an arbitrary value in a SQL expression. This could be a constant value, a column reference or any arbitrary calculated expression. E.G. @@ -40,10 +39,12 @@ @since 1.0.0.0 -} newtype ValueExpression = ValueExpression RawSql.RawSql-  deriving (RawSql.SqlExpression)+  deriving+    ( -- | @since 1.0.0.0+      RawSql.SqlExpression+    ) -{- |-Performs a SQL type cast to the specified type on the given 'ValueExpression'.+{- | Performs a SQL type cast to the specified type on the given 'ValueExpression'. E.G.  > foo :: integer@@ -57,19 +58,17 @@       <> RawSql.fromString "::"       <> RawSql.toRawSql dataType -{- |-Uses a 'ColumnName' to reference a column as a 'ValueExpression'. This+{- | Uses a 'ColumnName' to reference a column as a 'ValueExpression'. This is the equivalent of simply writing the column name as the expression. E.G.  > foo  @since 1.0.0.0 -}-columnReference :: ColumnName -> ValueExpression+columnReference :: QualifiedOrUnqualified ColumnName -> ValueExpression columnReference = ValueExpression . RawSql.toRawSql -{- |-  Uses the given 'SqlValue' as a constant expression. The value will be passed+{- | Uses the given 'SqlValue' as a constant expression. The value will be passed   as a statement parameter, not as a literal expression, so there is not need   to worry about escaping. However, there are a few places (usually in DDL)   where PostgreSQL does not support values passed as parameters where this@@ -80,8 +79,7 @@ valueExpression :: SqlValue -> ValueExpression valueExpression = ValueExpression . RawSql.parameter -{- |-Constructs a PostgreSQL row value expression from the given list of+{- | Constructs a PostgreSQL row value expression from the given list of expressions. E.G.  > (foo, bar, now())@@ -95,8 +93,7 @@       <> RawSql.intercalate RawSql.comma elements       <> RawSql.rightParen -{- |-Constructs a 'ValueExpression' that will call the specified PostgreSQL+{- | Constructs a 'ValueExpression' that will call the specified PostgreSQL function with the given arguments passed as position parameters. E.G.  > nextval(sequence_name)@@ -111,8 +108,7 @@       <> RawSql.intercalate RawSql.comma parameters       <> RawSql.rightParen -{- |-Type to represent the name of a name parameter in a PostgreSQL function call.+{- | Type to represent the name of a name parameter in a PostgreSQL function call. E.G.  > foo@@ -128,10 +124,12 @@ @since 1.0.0.0 -} newtype ParameterName = ParameterName RawSql.RawSql-  deriving (RawSql.SqlExpression)+  deriving+    ( -- | @since 1.0.0.0+      RawSql.SqlExpression+    ) -{- |-Constructs a 'ValueExpression' that will call the specified PostgreSQL+{- | Constructs a 'ValueExpression' that will call the specified PostgreSQL function with the given arguments passed as named parameters. E.G.  > make_interval(years => 1)@@ -146,8 +144,7 @@       <> RawSql.intercalate RawSql.comma (fmap (uncurry namedParameterArgument) parameters)       <> RawSql.rightParen -{- |-  Constructs a sql fragment that will pass the given named argument with the+{- | Constructs a sql fragment that will pass the given named argument with the   specified value.    @since 1.0.0.0
+ src/Orville/PostgreSQL/Expr/Values.hs view
@@ -0,0 +1,137 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++{- |+Copyright : Flipstone Technology Partners 2024+License   : MIT+Stability : Stable++@since 1.1.0.0+-}+module Orville.PostgreSQL.Expr.Values+  ( ValuesExpr+  , valuesExpr+  , valuesExprFromValueExpressions+  , ValuesExprRow+  , valuesExprRow+  , ValuesExprValue+  , valuesExprValue+  , valuesExprDefaultValue+  , valuesQueryExpr+  ) where++import Data.List.NonEmpty (NonEmpty)+import Data.Maybe (catMaybes)++import Orville.PostgreSQL.Expr.FetchClause (FetchClause)+import Orville.PostgreSQL.Expr.LimitExpr (LimitExpr)+import Orville.PostgreSQL.Expr.OffsetExpr (OffsetExpr)+import Orville.PostgreSQL.Expr.OrderBy (OrderByExpr)+import Orville.PostgreSQL.Expr.Query (QueryExpr)+import Orville.PostgreSQL.Expr.ValueExpression (ValueExpression)+import qualified Orville.PostgreSQL.Raw.RawSql as RawSql++{- | Type to represent a @VALUES@ statement, e.g.++> VALUES ('Bob',32),('Cindy',33)++@since 1.1.0.0+-}+newtype ValuesExpr = ValuesExpr RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++{- | Construct a 'ValuesExpr' for the given 'ValuesExprRow's, and any of the supported+  optional clauses.++@since 1.1.0.0+-}+valuesExpr ::+  NonEmpty ValuesExprRow ->+  Maybe OrderByExpr ->+  Maybe LimitExpr ->+  Maybe OffsetExpr ->+  Maybe FetchClause ->+  ValuesExpr+valuesExpr vals mbOrderBy mbLimit mbOffset mbFetch =+  let+    opts =+      RawSql.intercalate RawSql.space $+        catMaybes+          [ fmap RawSql.toRawSql mbOrderBy+          , fmap RawSql.toRawSql mbLimit+          , fmap RawSql.toRawSql mbOffset+          , fmap RawSql.toRawSql mbFetch+          ]+  in+    ValuesExpr $+      RawSql.fromString "VALUES "+        <> RawSql.intercalate RawSql.comma (fmap RawSql.toRawSql vals)+        <> opts++{- | A helper function to construct a 'ValuesExpr' from a non-empty lists of 'ValueExpression's,+  useful for conveinently constructing a 'ValuesExpr' in contexts where you don't need to use+  @DEFAULT@ values or additional clauses.++@since 1.1.0.0+-}+valuesExprFromValueExpressions :: NonEmpty (NonEmpty ValueExpression) -> ValuesExpr+valuesExprFromValueExpressions valExprs =+  let+    rows =+      fmap (valuesExprRow . fmap valuesExprValue) valExprs+  in+    valuesExpr rows Nothing Nothing Nothing Nothing++{- | A non-emtpy row of values or @DEFAULT@s used to construct the rows for a 'ValueExpr'.++@since 1.1.0.0+-}+newtype ValuesExprRow = ValuesExprRow RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++{- | Construct a 'ValuesExprRow' from a non-empty list of 'ValuesExprValue's.++@since 1.1.0.0+-}+valuesExprRow :: NonEmpty ValuesExprValue -> ValuesExprRow+valuesExprRow vals =+  ValuesExprRow+    . RawSql.parenthesized+    $ RawSql.intercalate RawSql.comma (fmap RawSql.toRawSql vals)++{- | A value used to construct a 'ValuesExprRow'.++@since 1.1.0.0+-}+newtype ValuesExprValue = ValuesExprValue RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++{- | Construct a 'ValuesExprValue' from a 'ValueExpression'.++@since 1.1.0.0+-}+valuesExprValue :: ValueExpression -> ValuesExprValue+valuesExprValue = ValuesExprValue . RawSql.toRawSql++{- | Construct a @DEFAULT@ 'ValuesExprValue'. Only valid in the context of a 'ValuesExpr' used for an+  @INSERT@ statement, where it indicates that the default value should be used for a column.++@since 1.1.0.0+-}+valuesExprDefaultValue :: ValuesExprValue+valuesExprDefaultValue = ValuesExprValue $ RawSql.fromString "DEFAULT"++{- | Use a 'ValuesExpr' as a 'QueryExpr'.++@since 1.1.0.0+-}+valuesQueryExpr :: ValuesExpr -> QueryExpr+valuesQueryExpr = RawSql.unsafeFromRawSql . RawSql.toRawSql
src/Orville/PostgreSQL/Expr/WhereClause.hs view
@@ -1,7 +1,7 @@ {-# LANGUAGE GeneralizedNewtypeDeriving #-}  {- |-Copyright : Flipstone Technology Partners 2023+Copyright : Flipstone Technology Partners 2023-2024 License   : MIT Stability : Stable @@ -16,9 +16,12 @@   , (.&&)   , orExpr   , (.||)+  , notExpr   , parenthesized   , equals   , notEquals+  , isDistinctFrom+  , isNotDistinctFrom   , greaterThan   , lessThan   , greaterThanOrEqualTo@@ -40,12 +43,11 @@  import qualified Data.List.NonEmpty as NE -import Orville.PostgreSQL.Expr.BinaryOperator (andOp, binaryOpExpression, equalsOp, greaterThanOp, greaterThanOrEqualsOp, iLikeOp, lessThanOp, lessThanOrEqualsOp, likeOp, notEqualsOp, orOp)+import Orville.PostgreSQL.Expr.BinaryOperator (andOp, binaryOpExpression, equalsOp, greaterThanOp, greaterThanOrEqualsOp, iLikeOp, isDistinctFromOp, isNotDistinctFromOp, lessThanOp, lessThanOrEqualsOp, likeOp, notEqualsOp, orOp) import Orville.PostgreSQL.Expr.ValueExpression (ValueExpression, rowValueConstructor) import qualified Orville.PostgreSQL.Raw.RawSql as RawSql -{- |-Type to represent a @WHERE@ clause restriction on a @SELECT@, @UPDATE@ or+{- | Type to represent a @WHERE@ clause restriction on a @SELECT@, @UPDATE@ or @DELETE@ statement. E.G.  > WHERE (foo > 10)@@ -58,10 +60,12 @@ -} newtype WhereClause   = WhereClause RawSql.RawSql-  deriving (RawSql.SqlExpression)+  deriving+    ( -- | @since 1.0.0.0+      RawSql.SqlExpression+    ) -{- |-Constructs a @WHERE@ clause from the given 'BooleanExpr'. E.G.+{- | Constructs a @WHERE@ clause from the given 'BooleanExpr'. E.G.  > WHERE <boolean expr> @@ -72,8 +76,7 @@   WhereClause $     RawSql.fromString "WHERE " <> RawSql.toRawSql booleanExpr -{- |-Type to represent a SQL value expression that evaluates to a boolean and therefore+{- | Type to represent a SQL value expression that evaluates to a boolean and therefore can used with boolean logic functions. E.G.  > foo > 10@@ -86,10 +89,12 @@ -} newtype BooleanExpr   = BooleanExpr RawSql.RawSql-  deriving (RawSql.SqlExpression)+  deriving+    ( -- | @since 1.0.0.0+      RawSql.SqlExpression+    ) -{- |-  Constructs a 'BooleanExpr' whose value is the SQL literal @TRUE@ or @FALSE@+{- | Constructs a 'BooleanExpr' whose value is the SQL literal @TRUE@ or @FALSE@   depending on the argument given.    @since 1.0.0.0@@ -101,8 +106,7 @@       False -> "FALSE"       True -> "TRUE" -{- |-  Converts a 'BooleanExpr' to a 'ValueExpression' so that it can be used+{- | Converts a 'BooleanExpr' to a 'ValueExpression' so that it can be used   anywhere 'ValueExpression' is allowed.    @since 1.0.0.0@@ -111,8 +115,7 @@ booleanValueExpression (BooleanExpr rawSql) =   RawSql.unsafeFromRawSql rawSql -{- |-  The SQL @OR@ operator. The arguments will be surrounded with parentheses+{- | The SQL @OR@ operator. The arguments will be surrounded with parentheses   to ensure that the associativity of expression in the resulting SQL matches   the associativity implied by this Haskell function. @@ -125,8 +128,7 @@     (booleanValueExpression left)     (booleanValueExpression right) -{- |-  The SQL @OR@ operator (alias for 'orExpr').+{- | The SQL @OR@ operator (alias for 'orExpr').    @since 1.0.0.0 -}@@ -135,8 +137,7 @@  infixr 8 .|| -{- |-  The SQL @AND@ operator. The arguments will be surrounded with parentheses+{- | The SQL @AND@ operator. The arguments will be surrounded with parentheses   to ensure that the associativity of expression in the resulting SQL matches   the associativity implied by this Haskell function. @@ -149,8 +150,7 @@     (booleanValueExpression left)     (booleanValueExpression right) -{- |-  The SQL @AND@ operator (alias for 'andExpr').+{- | The SQL @AND@ operator (alias for 'andExpr').    @since 1.0.0.0 -}@@ -159,8 +159,19 @@  infixr 8 .&& -{- |-  The SQL @IN@ operator. The result will be @TRUE@ if the given value+{- | The SQL @NOT@ operator. The argument will be surrounded with parentheses+  to ensure that the associativity of expression in the resulting SQL matches+  the associativity implied by this Haskell function.++  @since 1.1.0.0+-}+notExpr :: BooleanExpr -> BooleanExpr+notExpr bool =+  BooleanExpr $+    RawSql.fromString "NOT "+      <> RawSql.parenthesized bool++{- | The SQL @IN@ operator. The result will be @TRUE@ if the given value   appears in the list of values given.    @since 1.0.0.0@@ -169,8 +180,7 @@ valueIn needle haystack =   inPredicate needle (inValueList haystack) -{- |-  The SQL @NOT IN@ operator. The result will be @TRUE@ if the given value+{- | The SQL @NOT IN@ operator. The result will be @TRUE@ if the given value   does not appear in the list of values given.    @since 1.0.0.0@@ -179,8 +189,7 @@ valueNotIn needle haystack =   notInPredicate needle (inValueList haystack) -{- |-  The SQL @IN@ operator, like 'valueIn', but for when you want to construct a+{- | The SQL @IN@ operator, like 'valueIn', but for when you want to construct a   tuple in SQL and check if it is in a list of tuples. It is up to the caller   to ensure that all the tuples given have the same arity. @@ -192,8 +201,7 @@     (rowValueConstructor needle)     (inValueList (fmap rowValueConstructor haystack)) -{- |-  The SQL @NOT IN@ operator, like 'valueNotIn', but for when you want to+{- | The SQL @NOT IN@ operator, like 'valueNotIn', but for when you want to   construct a tuple in SQL and check if it is not in a list of tuples. It is up   to the caller to ensure that all the tuples given have the same arity. @@ -205,8 +213,7 @@     (rowValueConstructor needle)     (inValueList (fmap rowValueConstructor haystack)) -{- |-  Lower-level access to the SQL @IN@ operator. This takes any 'ValueExpression'+{- | Lower-level access to the SQL @IN@ operator. This takes any 'ValueExpression'   and 'InValuePredicate'. It is up to the caller to ensure the expressions   given make sense together. @@ -219,8 +226,7 @@       <> RawSql.fromString " IN "       <> RawSql.toRawSql predicate -{- |-  Lower-level access to the SQL @NOT IN@ operator. This takes any+{- | Lower-level access to the SQL @NOT IN@ operator. This takes any   'ValueExpression' and 'InValuePredicate'. It is up to the caller to ensure   the expressions given make sense together. @@ -233,8 +239,7 @@       <> RawSql.fromString " NOT IN "       <> RawSql.toRawSql predicate -{- |-Type to represent the right hand side of an @IN@ or @NOT IN@ expression.+{- | Type to represent the right hand side of an @IN@ or @NOT IN@ expression. E.G.  > (10,12,13)@@ -247,10 +252,12 @@ -} newtype InValuePredicate   = InValuePredicate RawSql.RawSql-  deriving (RawSql.SqlExpression)+  deriving+    ( -- | @since 1.0.0.0+      RawSql.SqlExpression+    ) -{- |-  Constructs an 'InValuePredicate' from the given list of 'ValueExpression'.+{- | Constructs an 'InValuePredicate' from the given list of 'ValueExpression'.    @since 1.0.0.0 -}@@ -261,8 +268,7 @@       <> RawSql.intercalate RawSql.commaSpace values       <> RawSql.rightParen -{- |-  Surrounds the given 'BooleanExpr' with parentheses.+{- | Surrounds the given 'BooleanExpr' with parentheses.    @since 1.0.0.0 -}@@ -271,8 +277,7 @@   BooleanExpr $     RawSql.leftParen <> RawSql.toRawSql expr <> RawSql.rightParen -{- |-  The SQL @=@ operator.+{- | The SQL @=@ operator.    @since 1.0.0.0 -}@@ -280,8 +285,7 @@ equals =   binaryOpExpression equalsOp -{- |-  The SQL @<>@ operator.+{- | The SQL @<>@ operator.    @since 1.0.0.0 -}@@ -289,17 +293,31 @@ notEquals =   binaryOpExpression notEqualsOp -{- |-  The SQL @>@ operator.+{- | The SQL @IS DISTINCT FROM@ binary comparison. +  @since 1.1.0.0+-}+isDistinctFrom :: ValueExpression -> ValueExpression -> BooleanExpr+isDistinctFrom =+  binaryOpExpression isDistinctFromOp++{- | The SQL @IS NOT DISTINCT FROM@ binary comparison.++  @since 1.1.0.0+-}+isNotDistinctFrom :: ValueExpression -> ValueExpression -> BooleanExpr+isNotDistinctFrom =+  binaryOpExpression isNotDistinctFromOp++{- | The SQL @>@ operator.+   @since 1.0.0.0 -} greaterThan :: ValueExpression -> ValueExpression -> BooleanExpr greaterThan =   binaryOpExpression greaterThanOp -{- |-  The SQL @<@ operator.+{- | The SQL @<@ operator.    @since 1.0.0.0 -}@@ -307,8 +325,7 @@ lessThan =   binaryOpExpression lessThanOp -{- |-  The SQL @>=@ operator.+{- | The SQL @>=@ operator.    @since 1.0.0.0 -}@@ -316,8 +333,7 @@ greaterThanOrEqualTo =   binaryOpExpression greaterThanOrEqualsOp -{- |-  The SQL @<=@ operator.+{- | The SQL @<=@ operator.    @since 1.0.0.0 -}@@ -325,8 +341,7 @@ lessThanOrEqualTo =   binaryOpExpression lessThanOrEqualsOp -{- |-  The SQL @LIKE@ operator.+{- | The SQL @LIKE@ operator.    @since 1.0.0.0 -}@@ -334,8 +349,7 @@ like =   binaryOpExpression likeOp -{- |-  The SQL @ILIKE@ operator.+{- | The SQL @ILIKE@ operator.    @since 1.0.0.0 -}@@ -343,8 +357,7 @@ likeInsensitive =   binaryOpExpression iLikeOp -{- |-  The SQL @IS NULL@ condition.+{- | The SQL @IS NULL@ condition.    @since 1.0.0.0 -}@@ -355,8 +368,7 @@       <> RawSql.space       <> RawSql.fromString "IS NULL" -{- |-  The SQL @IS NOT NULL@ condition.+{- | The SQL @IS NOT NULL@ condition.    @since 1.0.0.0 -}
+ src/Orville/PostgreSQL/Expr/Window.hs view
@@ -0,0 +1,17 @@+{-# OPTIONS_GHC -Wno-missing-import-lists #-}++{- |+Copyright : Flipstone Technology Partners 2024+License   : MIT+Stability : Stable++@since 1.1.0.0+-}+module Orville.PostgreSQL.Expr.Window+  ( module Export+  )+where++import Orville.PostgreSQL.Expr.Window.WindowClause as Export+import Orville.PostgreSQL.Expr.Window.WindowDefinitionExpr as Export+import Orville.PostgreSQL.Expr.Window.WindowFunction as Export
+ src/Orville/PostgreSQL/Expr/Window/WindowClause.hs view
@@ -0,0 +1,89 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++{- |+Copyright : Flipstone Technology Partners 2024+License   : MIT+Stability : Stable++@since 1.1.0.0+-}+module Orville.PostgreSQL.Expr.Window.WindowClause+  ( WindowClause+  , windowClause+  , NamedWindowDefinitionExpr+  , namedWindowDefinition+  , appendNamedWindowDefinitionExpr+  )+where++import Orville.PostgreSQL.Expr.Name (WindowName)+import Orville.PostgreSQL.Expr.Window.WindowDefinitionExpr (WindowDefinitionExpr)+import qualified Orville.PostgreSQL.Raw.RawSql as RawSql++{- | Type to represent a named SQL window definition. This should only be used in a @WINDOW@ clause.++'NamedWindowDefinitionExpr' provides a 'RawSql.SqlExpression' instance.+See 'RawSql.unsafeSqlExpression' for how to construct a value with your own custom SQL.++@since 1.1.0.0+-}+newtype NamedWindowDefinitionExpr = NamedWindowDefinitionExpr RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++{- |+@since 1.1.0.0+-}+instance Semigroup NamedWindowDefinitionExpr where+  (<>) = appendNamedWindowDefinitionExpr++{- | Combines two 'NamedWindowDefinitionExpr's with a comma between them.++@since 1.1.0.0+-}+appendNamedWindowDefinitionExpr :: NamedWindowDefinitionExpr -> NamedWindowDefinitionExpr -> NamedWindowDefinitionExpr+appendNamedWindowDefinitionExpr (NamedWindowDefinitionExpr a) (NamedWindowDefinitionExpr b) =+  NamedWindowDefinitionExpr (a <> RawSql.commaSpace <> b)++{- | Builds a 'NamedWindowDefinitionExpr' with the given name and 'WindowDefinitionExpr'.+1+@since 1.1.0.0+-}+namedWindowDefinition :: WindowName -> WindowDefinitionExpr -> NamedWindowDefinitionExpr+namedWindowDefinition windowName expr =+  NamedWindowDefinitionExpr $+    RawSql.toRawSql windowName+      <> RawSql.space+      <> RawSql.fromString "AS"+      <> RawSql.space+      <> RawSql.parenthesized (RawSql.toRawSql expr)++{- | Type to represent a SQL window clause. E.G.++> WINDOW foo , bar++'WindowClause' provides a 'RawSql.SqlExpression' instance. See+'RawSql.unsafeSqlExpression' for how to construct a value with your own custom+SQL.++@since 1.1.0.0+-}+newtype WindowClause+  = WindowClause RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++{- | Builds a full 'WindowClause' with the given windowing described in the 'NamedWindowDefinitionExpr'+1+@since 1.1.0.0+-}+windowClause :: NamedWindowDefinitionExpr -> WindowClause+windowClause namedWindowDefinitionExpr =+  WindowClause $+    RawSql.fromString "WINDOW"+      <> RawSql.space+      <> RawSql.toRawSql namedWindowDefinitionExpr
+ src/Orville/PostgreSQL/Expr/Window/WindowDefinitionExpr.hs view
@@ -0,0 +1,309 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++{- |+Copyright : Flipstone Technology Partners 2024+License   : MIT+Stability : Stable++@since 1.1.0.0+-}+module Orville.PostgreSQL.Expr.Window.WindowDefinitionExpr+  ( WindowDefinitionExpr+  , windowDefinition+  , PartitionByExpr+  , partitionBy+  , FrameClause+  , frameClause+  , FrameModeExpr+  , rangeFrameMode+  , rowsFrameMode+  , groupsFrameMode+  , FrameStartExpr+  , unboundedPrecedingFrameStart+  , offsetPrecedingFrameStart+  , currentRowFrameStart+  , offsetFollowingFrameStart+  , FrameEndExpr+  , offsetPrecedingFrameEnd+  , currentRowFrameEnd+  , offsetFollowingFrameEnd+  , unboundedFollowingFrameEnd+  , FrameExclusionExpr+  , currentRowFrameExclusion+  , groupFrameExclusion+  , tiesFrameExclusion+  , noOthersFrameExclusion+  )+where++import qualified Data.List.NonEmpty as NEL+import Data.Maybe (catMaybes)++import Orville.PostgreSQL.Expr.Name (WindowName)+import qualified Orville.PostgreSQL.Expr.OrderBy as OrderBy+import qualified Orville.PostgreSQL.Expr.ValueExpression as ValueExpression+import qualified Orville.PostgreSQL.Raw.RawSql as RawSql++{- | Type to represent a SQL window definition. This can be used in either a @WINDOW@ clause or in the+portion of a window function call after the @OVER@.++'WindowDefinitionExpr' provides a 'RawSql.SqlExpression' instance.+See 'RawSql.unsafeSqlExpression' for how to construct a value with your own custom SQL.++@since 1.1.0.0+-}+newtype WindowDefinitionExpr = WindowDefinitionExpr RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++{- | Builds a 'WindowDefinitionExpr'. Note that it is up to the caller to ensure the options make+   sense togther, including particular rules below.++N.B. If the 'WindowName' is used (so we are "copying" a previous definition), then a few rules apply:+  * The 'ParititionByExpr' and 'OrderByClause' will be copied if any+  * If either 'ParitionByExpr' and 'OrderByClause' exist then a 'PartitionByExpr' may not be specified.+  * The 'OrderByClause' can _only_ be specified if the window copied does not have one.+  * The copied window must not specify a 'FrameClause'.++@since 1.1.0.0+-}+windowDefinition ::+  -- | An, optional, already existing named window that must be named in a prior @WINDOW@ entry.+  Maybe WindowName ->+  Maybe PartitionByExpr ->+  Maybe OrderBy.OrderByClause ->+  Maybe FrameClause ->+  WindowDefinitionExpr+windowDefinition mbWindowName mbPartitionBy mbOrderBy mbFrame =+  WindowDefinitionExpr+    . RawSql.intercalate RawSql.space+    $ catMaybes+      [ fmap RawSql.toRawSql mbWindowName+      , fmap RawSql.toRawSql mbPartitionBy+      , fmap RawSql.toRawSql mbOrderBy+      , fmap RawSql.toRawSql mbFrame+      ]++{- | Type to represent the @PARTITION BY expression [, ...]@ portion of a SQL window definition.++'PartitionByExpr' provides a 'RawSql.SqlExpression' instance.+See 'RawSql.unsafeSqlExpression' for how to construct a value with your own custom SQL.++@since 1.1.0.0+-}+newtype PartitionByExpr = PartitionByExpr RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++{- | Builds a 'PartitionByExpr'.++@since 1.1.0.0+-}+partitionBy :: NEL.NonEmpty ValueExpression.ValueExpression -> PartitionByExpr+partitionBy exprs =+  PartitionByExpr $+    RawSql.fromString "PARTITION BY "+      <> RawSql.intercalate RawSql.commaSpace exprs++{- | Type to represent the framing clause of a window definition.++'FrameClause' provides a 'RawSql.SqlExpression' instance.+See 'RawSql.unsafeSqlExpression' for how to construct a value with your own custom SQL.++@since 1.1.0.0+-}+newtype FrameClause = FrameClause RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++{- | Builds a 'FrameClause' from the given pieces.++@since 1.1.0.0+-}+frameClause :: Maybe FrameModeExpr -> FrameStartExpr -> Maybe FrameEndExpr -> Maybe FrameExclusionExpr -> FrameClause+frameClause mbFrameMode frameStart mbFrameEnd mbExclusion =+  let+    mode =+      maybe mempty RawSql.toRawSql mbFrameMode+    exclusion =+      maybe mempty RawSql.toRawSql mbExclusion+  in+    FrameClause $+      mode+        <> RawSql.space+        <> case mbFrameEnd of+          Nothing -> RawSql.toRawSql frameStart+          Just frameEnd ->+            RawSql.fromString "BETWEEN "+              <> RawSql.toRawSql frameStart+              <> RawSql.fromString " AND "+              <> RawSql.toRawSql frameEnd+        <> RawSql.space+        <> exclusion++{- | Type to represent the mode of the framing in a window definition.++'FrameModeExpr' provides a 'RawSql.SqlExpression' instance.+See 'RawSql.unsafeSqlExpression' for how to construct a value with your own custom SQL.++@since 1.1.0.0+-}+newtype FrameModeExpr = FrameModeExpr RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++{- | Value for the frame mode of @RANGE@++@since 1.1.0.0+-}+rangeFrameMode :: FrameModeExpr+rangeFrameMode = FrameModeExpr $ RawSql.fromString "RANGE"++{- | Value for the frame mode of @ROWS@++@since 1.1.0.0+-}+rowsFrameMode :: FrameModeExpr+rowsFrameMode = FrameModeExpr $ RawSql.fromString "ROWS"++{- | Value for the frame mode of @GROUPS@++@since 1.1.0.0+-}+groupsFrameMode :: FrameModeExpr+groupsFrameMode = FrameModeExpr $ RawSql.fromString "GROUPS"++{- | Type to represent the beginning of the framing in a window definition.++'FrameStartExpr' provides a 'RawSql.SqlExpression' instance.+See 'RawSql.unsafeSqlExpression' for how to construct a value with your own custom SQL.++@since 1.1.0.0+-}+newtype FrameStartExpr = FrameStartExpr RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++{- | Value for a frame start of @UNBOUNDED PRECEDING@++@since 1.1.0.0+-}+unboundedPrecedingFrameStart :: FrameStartExpr+unboundedPrecedingFrameStart = FrameStartExpr $ RawSql.fromString "UNBOUNDED PRECEDING"++{- | Value for a frame start of @expression PRECEDING@. Note that it is up to the caller to ensure+   that the 'ValueExpression' is valid in this context.++@since 1.1.0.0+-}+offsetPrecedingFrameStart :: ValueExpression.ValueExpression -> FrameStartExpr+offsetPrecedingFrameStart val = FrameStartExpr $ RawSql.toRawSql val <> RawSql.fromString " PRECEDING"++{- | Value for a frame start of @CURRENT ROW@++@since 1.1.0.0+-}+currentRowFrameStart :: FrameStartExpr+currentRowFrameStart = FrameStartExpr $ RawSql.fromString "CURRENT ROW"++{- | Value for a frame start of @expression FOLLOWING@. Note that it is up to the caller to ensure+   that the 'ValueExpression' is valid in this context.++@since 1.1.0.0+-}+offsetFollowingFrameStart :: ValueExpression.ValueExpression -> FrameStartExpr+offsetFollowingFrameStart val = FrameStartExpr $ RawSql.toRawSql val <> RawSql.fromString " FOLLOWING"++{- | Type to represent the ending of the framing in a window definition.++'FrameEndExpr' provides a 'RawSql.SqlExpression' instance.+See 'RawSql.unsafeSqlExpression' for how to construct a value with your own custom SQL.++@since 1.1.0.0+-}+newtype FrameEndExpr = FrameEndExpr RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++{- | Value for a frame end of @expression PRECEDING@. Note that it is up to the caller to ensure+   that the 'ValueExpression' is valid in this context.++@since 1.1.0.0+-}+offsetPrecedingFrameEnd :: ValueExpression.ValueExpression -> FrameEndExpr+offsetPrecedingFrameEnd val = FrameEndExpr $ RawSql.toRawSql val <> RawSql.fromString " PRECEDING"++{- | Value for a frame end of @CURRENT ROW@++@since 1.1.0.0+-}+currentRowFrameEnd :: FrameEndExpr+currentRowFrameEnd = FrameEndExpr $ RawSql.fromString "CURRENT ROW"++{- | Value for a frame end of @expression FOLLOWING@. Note that it is up to the caller to ensure+   that the 'ValueExpression' is valid in this context.++@since 1.1.0.0+-}+offsetFollowingFrameEnd :: ValueExpression.ValueExpression -> FrameEndExpr+offsetFollowingFrameEnd val = FrameEndExpr $ RawSql.toRawSql val <> RawSql.fromString " FOLLOWING"++{- | Value for a frame end of @UNBOUNDED FOLLOWING@++@since 1.1.0.0+-}+unboundedFollowingFrameEnd :: FrameEndExpr+unboundedFollowingFrameEnd = FrameEndExpr $ RawSql.fromString "UNBOUNDED FOLLOWING"++{- | Type to represent the exclusion of results in the framing in a window definition.++'FrameExclusionExpr' provides a 'RawSql.SqlExpression' instance.+See 'RawSql.unsafeSqlExpression' for how to construct a value with your own custom SQL.++@since 1.1.0.0+-}+newtype FrameExclusionExpr = FrameExclusionExpr RawSql.RawSql+  deriving+    ( -- | @since 1.1.0.0+      RawSql.SqlExpression+    )++{- | Value for a frame exclusion of @EXCLUDE CURRENT ROW@++@since 1.1.0.0+-}+currentRowFrameExclusion :: FrameExclusionExpr+currentRowFrameExclusion = FrameExclusionExpr $ RawSql.fromString "EXCLUDE CURRENT ROW"++{- | Value for a frame exclusion of @EXCLUDE GROUP@++@since 1.1.0.0+-}+groupFrameExclusion :: FrameExclusionExpr+groupFrameExclusion = FrameExclusionExpr $ RawSql.fromString "EXCLUDE GROUP"++{- | Value for a frame exclusion of @EXCLUDE TIES@++@since 1.1.0.0+-}+tiesFrameExclusion :: FrameExclusionExpr+tiesFrameExclusion = FrameExclusionExpr $ RawSql.fromString "EXCLUDE TIES"++{- | Value for a frame exclusion of @EXCLUDE NO OTHERS@++@since 1.1.0.0+-}+noOthersFrameExclusion :: FrameExclusionExpr+noOthersFrameExclusion = FrameExclusionExpr $ RawSql.fromString "EXCLUDE NO OTHERS"
+ src/Orville/PostgreSQL/Expr/Window/WindowFunction.hs view
@@ -0,0 +1,306 @@+{- |+Copyright : Flipstone Technology Partners 2024+License   : MIT+Stability : Stable++@since 1.1.0.0+-}+module Orville.PostgreSQL.Expr.Window.WindowFunction+  ( windowFunction+  , rowNumberWindowFunction+  , rankWindowFunction+  , denseRankWindowFunction+  , percentRankWindowFunction+  , arrayAggWindowFunction+  , avgWindowFunction+  , bitAndWindowFunction+  , bitOrWindowFunction+  , bitXorWindowFunction+  , boolAndWindowFunction+  , boolOrWindowFunction+  , countWindowFunction+  , everyWindowFunction+  , jsonAggWindowFunction+  , jsonObjectAggWindowFunction+  , jsonbAggWindowFunction+  , jsonbObjectAggWindowFunction+  , maxWindowFunction+  , minWindowFunction+  , stringAggWindowFunction+  , sumWindowFunction+  , corWindowFunction+  , covarPopWindowFunction+  , covarSampWindowFunction+  , stddevPopWindowFunction+  , stddevSampWindowFunction+  , varPopWindowFunction+  , varSampWindowFunction+  )+where++import qualified Orville.PostgreSQL.Expr.Filter as Filter+import Orville.PostgreSQL.Expr.Name (FunctionName, functionName)+import qualified Orville.PostgreSQL.Expr.Name as Name+import qualified Orville.PostgreSQL.Expr.ValueExpression as ValueExpression+import Orville.PostgreSQL.Expr.Window.WindowDefinitionExpr (WindowDefinitionExpr)+import qualified Orville.PostgreSQL.Raw.RawSql as RawSql++{- | The @row_number@ window function++@since 1.1.0.0+-}+rowNumberWindowFunction :: Maybe Filter.FilterExpr -> WindowDefinitionExpr -> ValueExpression.ValueExpression+rowNumberWindowFunction = windowFunction (functionName "row_number") mempty++{- | The @rank@ window function++@since 1.1.0.0+-}+rankWindowFunction :: Maybe Filter.FilterExpr -> WindowDefinitionExpr -> ValueExpression.ValueExpression+rankWindowFunction = windowFunction (functionName "rank") mempty++{- | The @dense_rank@ window function++@since 1.1.0.0+-}+denseRankWindowFunction :: Maybe Filter.FilterExpr -> WindowDefinitionExpr -> ValueExpression.ValueExpression+denseRankWindowFunction = windowFunction (functionName "dense_rank") mempty++{- | The @percent_rank@ window function++@since 1.1.0.0+-}+percentRankWindowFunction :: Maybe Filter.FilterExpr -> WindowDefinitionExpr -> ValueExpression.ValueExpression+percentRankWindowFunction = windowFunction (functionName "percent_rank") mempty++{- | Build a 'ValueExpression' that represents a window function call. It is up to the caller to+ensure this makes sense as windowing functions are not allowed in all places.++@since 1.1.0.0+-}+windowFunction ::+  FunctionName ->+  [ValueExpression.ValueExpression] ->+  Maybe Filter.FilterExpr ->+  WindowDefinitionExpr ->+  ValueExpression.ValueExpression+windowFunction function parameters mbFilter windowDef =+  let+    filtering =+      case mbFilter of+        Nothing -> mempty+        Just filterExpr -> RawSql.space <> RawSql.toRawSql filterExpr+  in+    RawSql.unsafeFromRawSql $+      RawSql.toRawSql (ValueExpression.functionCall function parameters)+        <> filtering+        <> RawSql.space+        <> RawSql.fromString "OVER"+        <> RawSql.parenthesized (RawSql.toRawSql windowDef)++{- | The SQL @array_agg@ window function.++@since 1.1.0.0+-}+arrayAggWindowFunction ::+  ValueExpression.ValueExpression -> Maybe Filter.FilterExpr -> WindowDefinitionExpr -> ValueExpression.ValueExpression+arrayAggWindowFunction = singleParameterWindowFunction Name.arrayAggFunctionName++{- | The SQL @avg@ window function.++@since 1.1.0.0+-}+avgWindowFunction ::+  ValueExpression.ValueExpression -> Maybe Filter.FilterExpr -> WindowDefinitionExpr -> ValueExpression.ValueExpression+avgWindowFunction = singleParameterWindowFunction Name.avgFunctionName++{- | The SQL @bit_and@ window function.++@since 1.1.0.0+-}+bitAndWindowFunction ::+  ValueExpression.ValueExpression -> Maybe Filter.FilterExpr -> WindowDefinitionExpr -> ValueExpression.ValueExpression+bitAndWindowFunction = singleParameterWindowFunction Name.bitAndFunctionName++{- | The SQL @bit_or@ window function.++@since 1.1.0.0+-}+bitOrWindowFunction ::+  ValueExpression.ValueExpression -> Maybe Filter.FilterExpr -> WindowDefinitionExpr -> ValueExpression.ValueExpression+bitOrWindowFunction = singleParameterWindowFunction Name.bitOrFunctionName++{- | The SQL @bit_xor@ window function.++@since 1.1.0.0+-}+bitXorWindowFunction ::+  ValueExpression.ValueExpression -> Maybe Filter.FilterExpr -> WindowDefinitionExpr -> ValueExpression.ValueExpression+bitXorWindowFunction = singleParameterWindowFunction Name.bitXorFunctionName++{- | The SQL @bool_and@ window function.++@since 1.1.0.0+-}+boolAndWindowFunction ::+  ValueExpression.ValueExpression -> Maybe Filter.FilterExpr -> WindowDefinitionExpr -> ValueExpression.ValueExpression+boolAndWindowFunction = singleParameterWindowFunction Name.boolAndFunctionName++{- | The SQL @bool_or@ window function.++@since 1.1.0.0+-}+boolOrWindowFunction ::+  ValueExpression.ValueExpression -> Maybe Filter.FilterExpr -> WindowDefinitionExpr -> ValueExpression.ValueExpression+boolOrWindowFunction = singleParameterWindowFunction Name.boolOrFunctionName++{- | The SQL @count@ window function.++@since 1.1.0.0+-}+countWindowFunction ::+  ValueExpression.ValueExpression -> Maybe Filter.FilterExpr -> WindowDefinitionExpr -> ValueExpression.ValueExpression+countWindowFunction = singleParameterWindowFunction Name.countFunctionName++{- | The SQL @every@ window function.++@since 1.1.0.0+-}+everyWindowFunction ::+  ValueExpression.ValueExpression -> Maybe Filter.FilterExpr -> WindowDefinitionExpr -> ValueExpression.ValueExpression+everyWindowFunction = singleParameterWindowFunction Name.everyFunctionName++{- | The SQL @json_agg@ window function.++@since 1.1.0.0+-}+jsonAggWindowFunction ::+  ValueExpression.ValueExpression -> Maybe Filter.FilterExpr -> WindowDefinitionExpr -> ValueExpression.ValueExpression+jsonAggWindowFunction = singleParameterWindowFunction Name.jsonAggFunctionName++{- | The SQL @json_object_agg@ window function.++@since 1.1.0.0+-}+jsonObjectAggWindowFunction ::+  ValueExpression.ValueExpression -> ValueExpression.ValueExpression -> Maybe Filter.FilterExpr -> WindowDefinitionExpr -> ValueExpression.ValueExpression+jsonObjectAggWindowFunction param1 param2 =+  windowFunction Name.jsonObjectAggFunctionName [param1, param2]++{- | The SQL @jsonb_agg@ window function.++@since 1.1.0.0+-}+jsonbAggWindowFunction ::+  ValueExpression.ValueExpression -> Maybe Filter.FilterExpr -> WindowDefinitionExpr -> ValueExpression.ValueExpression+jsonbAggWindowFunction = singleParameterWindowFunction Name.jsonbAggFunctionName++{- | The SQL @jsonb_object_agg@ window function.++@since 1.1.0.0+-}+jsonbObjectAggWindowFunction ::+  ValueExpression.ValueExpression -> ValueExpression.ValueExpression -> Maybe Filter.FilterExpr -> WindowDefinitionExpr -> ValueExpression.ValueExpression+jsonbObjectAggWindowFunction param1 param2 =+  windowFunction Name.jsonbObjectAggFunctionName [param1, param2]++{- | The SQL @max@ window function.++@since 1.1.0.0+-}+maxWindowFunction ::+  ValueExpression.ValueExpression -> Maybe Filter.FilterExpr -> WindowDefinitionExpr -> ValueExpression.ValueExpression+maxWindowFunction = singleParameterWindowFunction Name.maxFunctionName++{- | The SQL @min@ window function.++@since 1.1.0.0+-}+minWindowFunction ::+  ValueExpression.ValueExpression -> Maybe Filter.FilterExpr -> WindowDefinitionExpr -> ValueExpression.ValueExpression+minWindowFunction = singleParameterWindowFunction Name.minFunctionName++{- | The SQL @string_agg@ window function.++@since 1.1.0.0+-}+stringAggWindowFunction ::+  ValueExpression.ValueExpression -> ValueExpression.ValueExpression -> Maybe Filter.FilterExpr -> WindowDefinitionExpr -> ValueExpression.ValueExpression+stringAggWindowFunction param1 param2 =+  windowFunction Name.stringAggFunctionName [param1, param2]++{- | The SQL @sum@ window function.++@since 1.1.0.0+-}+sumWindowFunction ::+  ValueExpression.ValueExpression -> Maybe Filter.FilterExpr -> WindowDefinitionExpr -> ValueExpression.ValueExpression+sumWindowFunction = singleParameterWindowFunction Name.sumFunctionName++{- | The SQL @cor@ window function.++@since 1.1.0.0+-}+corWindowFunction ::+  ValueExpression.ValueExpression -> ValueExpression.ValueExpression -> Maybe Filter.FilterExpr -> WindowDefinitionExpr -> ValueExpression.ValueExpression+corWindowFunction param1 param2 =+  windowFunction Name.corFunctionName [param1, param2]++{- | The SQL @covar_pop@ window function.++@since 1.1.0.0+-}+covarPopWindowFunction ::+  ValueExpression.ValueExpression -> ValueExpression.ValueExpression -> Maybe Filter.FilterExpr -> WindowDefinitionExpr -> ValueExpression.ValueExpression+covarPopWindowFunction param1 param2 =+  windowFunction Name.covarPopFunctionName [param1, param2]++{- | The SQL @covar_samp@ window function.++@since 1.1.0.0+-}+covarSampWindowFunction ::+  ValueExpression.ValueExpression -> ValueExpression.ValueExpression -> Maybe Filter.FilterExpr -> WindowDefinitionExpr -> ValueExpression.ValueExpression+covarSampWindowFunction param1 param2 =+  windowFunction Name.covarSampFunctionName [param1, param2]++{- | The SQL @stddev_pop@ window function.++@since 1.1.0.0+-}+stddevPopWindowFunction ::+  ValueExpression.ValueExpression -> ValueExpression.ValueExpression -> Maybe Filter.FilterExpr -> WindowDefinitionExpr -> ValueExpression.ValueExpression+stddevPopWindowFunction param1 param2 =+  windowFunction Name.stddevPopFunctionName [param1, param2]++{- | The SQL @stddev_samp@ window function.++@since 1.1.0.0+-}+stddevSampWindowFunction ::+  ValueExpression.ValueExpression -> ValueExpression.ValueExpression -> Maybe Filter.FilterExpr -> WindowDefinitionExpr -> ValueExpression.ValueExpression+stddevSampWindowFunction param1 param2 =+  windowFunction Name.stddevSampFunctionName [param1, param2]++{- | The SQL @var_pop@ window function.++@since 1.1.0.0+-}+varPopWindowFunction ::+  ValueExpression.ValueExpression -> ValueExpression.ValueExpression -> Maybe Filter.FilterExpr -> WindowDefinitionExpr -> ValueExpression.ValueExpression+varPopWindowFunction param1 param2 =+  windowFunction Name.varPopFunctionName [param1, param2]++{- | The SQL @var_samp@ window function.++@since 1.1.0.0+-}+varSampWindowFunction ::+  ValueExpression.ValueExpression -> ValueExpression.ValueExpression -> Maybe Filter.FilterExpr -> WindowDefinitionExpr -> ValueExpression.ValueExpression+varSampWindowFunction param1 param2 =+  windowFunction Name.varSampFunctionName [param1, param2]++singleParameterWindowFunction ::+  FunctionName -> ValueExpression.ValueExpression -> Maybe Filter.FilterExpr -> WindowDefinitionExpr -> ValueExpression.ValueExpression+singleParameterWindowFunction function parameter =+  windowFunction function (pure parameter)
+ src/Orville/PostgreSQL/Extension/PgTrgm.hs view
@@ -0,0 +1,182 @@+{- |+Copyright : Flipstone Technology Partners 2024+License   : MIT+Stability : Stable++Functionality for loading and using the+[pg_trgm](https://www.postgresql.org/docs/current/pgtrgm.html) extension. While this extension is+supplied by default with PostgreSQL, it is entirely up to users to ensure access to the extension.++@since 1.1.0.0+-}+module Orville.PostgreSQL.Extension.PgTrgm+  ( trigramSimilaritySyntheticField+  , trigramWordSimilaritySyntheticField+  , trigramStrictWordSimilaritySyntheticField+  , mkNamedTrigramGinIndexDefinition+  , mkNamedTrigramGistIndexDefinition+  , trigramSimilarity+  , trigramWordSimilarity+  , trigramStrictWordSimilarity+  ) where++import qualified Data.List.NonEmpty as NEL++import qualified Orville.PostgreSQL.Expr as Expr+import qualified Orville.PostgreSQL.Internal.IndexDefinition as IndexDefinition+import qualified Orville.PostgreSQL.Marshall as Marshall+import qualified Orville.PostgreSQL.Raw.RawSql as RawSql++{- | Create a named GIN index over the given fields for fast text searching.++@since 1.1.0.0+-}+mkNamedTrigramGinIndexDefinition ::+  -- | The name of the index to be created.+  String ->+  -- | Field names to create the index over.+  NEL.NonEmpty Marshall.FieldName ->+  IndexDefinition.IndexDefinition+mkNamedTrigramGinIndexDefinition name =+  IndexDefinition.mkNamedIndexDefinition Expr.NonUniqueIndex name . RawSql.unsafeFromRawSql . trigramGinIndexFieldsExpr++trigramGinIndexFieldsExpr :: NEL.NonEmpty Marshall.FieldName -> RawSql.RawSql+trigramGinIndexFieldsExpr fields =+  let+    fieldExpr :: Marshall.FieldName -> RawSql.RawSql+    fieldExpr name =+      RawSql.toRawSql (Marshall.fieldNameToColumnName name)+        <> RawSql.space+        <> RawSql.fromString "gin_trgm_ops"+  in+    RawSql.fromString "USING GIN "+      <> RawSql.parenthesized (RawSql.intercalate RawSql.commaSpace (fmap fieldExpr fields))++{- | Create a named GIST index for fast text searching.  The index is created, over the fields each of+  with an optional override of the index parameter "siglen". See [pg_trgm index+  docs](https://www.postgresql.org/docs/current/pgtrgm.html#PGTRGM-INDEX) for more information.++@since 1.1.0.0+-}+mkNamedTrigramGistIndexDefinition ::+  -- | The name of the index to be created.+  String ->+  -- | Pairs of field name and optionally a value of the siglen parameter, to create the index over.+  NEL.NonEmpty (Marshall.FieldName, Maybe Int) ->+  IndexDefinition.IndexDefinition+mkNamedTrigramGistIndexDefinition name =+  IndexDefinition.mkNamedIndexDefinition Expr.NonUniqueIndex name . RawSql.unsafeFromRawSql . trigramGistIndexFieldsExpr++trigramGistIndexFieldsExpr ::+  NEL.NonEmpty (Marshall.FieldName, Maybe Int) ->+  RawSql.RawSql+trigramGistIndexFieldsExpr fields =+  let+    fieldExpr :: (Marshall.FieldName, Maybe Int) -> RawSql.RawSql+    fieldExpr (name, mbSiglen) =+      RawSql.toRawSql (Marshall.fieldNameToColumnName name)+        <> RawSql.space+        <> RawSql.fromString "gist_trgm_ops"+        <> maybe+          mempty+          (\len -> RawSql.parenthesized (RawSql.fromString "siglen=" <> RawSql.intDecLiteral len))+          mbSiglen+  in+    RawSql.fromString "USING GIST "+      <> RawSql.parenthesized (RawSql.intercalate RawSql.commaSpace (fmap fieldExpr fields))++{- | Create a synthetic field using the similarity function provided by pg_trgm.++@since 1.1.0.0+-}+trigramSimilaritySyntheticField ::+  -- | The column to be used in the similarity comparison+  Expr.ColumnName ->+  -- | The value to be compared against.+  Expr.ValueExpression ->+  -- | The alias to be used to name the similarity result.+  String ->+  -- | A field with the resulting similarity score between the column value and the comparison.+  Marshall.SyntheticField Double+trigramSimilaritySyntheticField colname compareVal fieldAlias =+  Marshall.syntheticField+    (trigramSimilarity (Expr.columnReference $ Expr.unqualified colname) compareVal)+    fieldAlias+    Marshall.double++{- | Create a synthetic field using the word_similarity function provided by pg_trgm.++@since 1.1.0.0+-}+trigramWordSimilaritySyntheticField ::+  -- | The column to be used in the word_similarity comparison+  Expr.ColumnName ->+  -- | The value to be compared against.+  Expr.ValueExpression ->+  -- | The alias to be used to name the word_similarity result.+  String ->+  -- | A field with the resulting word_similarity score between the column value and the comparison.+  Marshall.SyntheticField Double+trigramWordSimilaritySyntheticField colname compareVal fieldAlias =+  Marshall.syntheticField+    (trigramWordSimilarity (Expr.columnReference $ Expr.unqualified colname) compareVal)+    fieldAlias+    Marshall.double++{- | Create a synthetic field using the strict_word_similarity function provided by pg_trgm.++@since 1.1.0.0+-}+trigramStrictWordSimilaritySyntheticField ::+  -- | The column to be used in the strict_word_similarity comparison+  Expr.ColumnName ->+  -- | The value to be compared against.+  Expr.ValueExpression ->+  -- | The alias to be used to name the strict_word_similarity result.+  String ->+  -- | A field with the resulting strict_word_similarity score between the column value and the comparison.+  Marshall.SyntheticField Double+trigramStrictWordSimilaritySyntheticField colname compareVal fieldAlias =+  Marshall.syntheticField+    (trigramStrictWordSimilarity (Expr.columnReference $ Expr.unqualified colname) compareVal)+    fieldAlias+    Marshall.double++{- | Call the similarity function provided by pg_trgm, comparing the pair of values.++@since 1.1.0.0+-}+trigramSimilarity ::+  Expr.ValueExpression ->+  Expr.ValueExpression ->+  Expr.ValueExpression+trigramSimilarity firstVal secondVal =+  Expr.functionCall+    (Expr.functionName "similarity")+    [firstVal, secondVal]++{- | Call the word_similarity function provided by pg_trgm, comparing the pair of values.++@since 1.1.0.0+-}+trigramWordSimilarity ::+  Expr.ValueExpression ->+  Expr.ValueExpression ->+  Expr.ValueExpression+trigramWordSimilarity firstVal secondVal =+  Expr.functionCall+    (Expr.functionName "word_similarity")+    [firstVal, secondVal]++{- | Call the strict_word_similarity function provided by pg_trgm, comparing the pair of values.++@since 1.1.0.0+-}+trigramStrictWordSimilarity ::+  Expr.ValueExpression ->+  Expr.ValueExpression ->+  Expr.ValueExpression+trigramStrictWordSimilarity firstVal secondVal =+  Expr.functionCall+    (Expr.functionName "strict_word_similarity")+    [firstVal, secondVal]
src/Orville/PostgreSQL/Internal/Bracket.hs view
@@ -1,5 +1,5 @@ {- |-Copyright : Flipstone Technology Partners 2023+Copyright : Flipstone Technology Partners 2023-2025 License   : MIT Stability : Stable @@ -11,16 +11,20 @@   ) where  import Control.Exception (SomeException, catch, mask, throwIO)+import Control.Monad (void) import Control.Monad.IO.Class (MonadIO (liftIO))  import Orville.PostgreSQL.Monad.MonadOrville (MonadOrvilleControl (liftCatch, liftMask)) -data BracketResult-  = BracketSuccess+{- | Track the result of a bracket++@since 1.0.0.0+-}+data BracketResult a+  = BracketSuccess a   | BracketError -{- |-  INTERNAL: A version of 'Control.Exception.bracket' that allows us to distinguish between+{- | INTERNAL: A version of 'Control.Exception.bracket' that allows us to distinguish between   exception and non-exception release cases. This is available in certain   packages as a typeclass function under the name "generalBracket", but is   implemented here directly in terms of IO's 'mask' and 'catch' to guarantee@@ -32,35 +36,33 @@ bracketWithResult ::   (MonadIO m, MonadOrvilleControl m) =>   m a ->-  (a -> BracketResult -> m c) ->+  (a -> BracketResult b -> m c) ->   (a -> m b) ->   m b bracketWithResult acquire release action = do   liftMask mask $ \restore -> do     resource <- acquire-     result <-       liftCatch         catch         (restore (action resource))         (handleAndRethrow (release resource BracketError)) -    _ <- release resource BracketSuccess+    _ <- release resource (BracketSuccess result)      pure result -{- |-  INTERNAL: Catch any exception, run the given handler, and rethrow the+{- | INTERNAL: Catch any exception, run the given handler, and rethrow the   exception. This is mostly useful to force the exception being caught to be of   the type 'SomeException'.  @since 1.0.0.0 -} handleAndRethrow ::-  MonadIO m =>+  (MonadIO m, MonadOrvilleControl m) =>   m a ->   SomeException ->   m b handleAndRethrow handle ex = do-  _ <- handle+  liftCatch catch (void handle) (\e -> case e :: SomeException of _ -> pure ())   liftIO . throwIO $ ex
src/Orville/PostgreSQL/Internal/Extra/NonEmpty.hs view
@@ -7,12 +7,25 @@ -} module Orville.PostgreSQL.Internal.Extra.NonEmpty   ( foldl1'+  , foldMap1'   ) where  import qualified Data.Foldable as Fold import Data.List.NonEmpty (NonEmpty ((:|))) +{- | A variant of foldl' that safely does not need a base case.++@since 1.0.0.0+-} foldl1' :: (a -> a -> a) -> NonEmpty a -> a foldl1' f (first :| rest) =   Fold.foldl' f first rest++{- | A variant of foldMap' that safely does not need a base case.++@since 1.0.0.0+-}+foldMap1' :: Semigroup m => (a -> m) -> NonEmpty a -> m+foldMap1' f (first :| rest) =+  Fold.foldl' (\m a -> m <> f a) (f first) rest
src/Orville/PostgreSQL/Internal/FieldName.hs view
@@ -18,27 +18,31 @@  import qualified Orville.PostgreSQL.Expr as Expr -{- |-  A simple type to represent the name of a field.+{- | A simple type to represent the name of a field.  @since 1.0.0.0 -} newtype FieldName   = FieldName B8.ByteString-  deriving (Eq, Ord, Show)+  deriving+    ( -- | @since 1.0.0.0+      Eq+    , -- | @since 1.0.0.0+      Ord+    , -- | @since 1.0.0.0+      Show+    ) -{- |-  Convert a field name to a 'Expr.ColumnName' for usage in SQL expressions.+{- | Convert a field name to a 'Expr.ColumnName' for usage in SQL expressions.   The field name will be properly quoted and escaped.  @since 1.0.0.0 -} fieldNameToColumnName :: FieldName -> Expr.ColumnName-fieldNameToColumnName (FieldName name) =-  Expr.fromIdentifier (Expr.identifierFromBytes name)+fieldNameToColumnName =+  Expr.fromIdentifier . Expr.identifierFromBytes . fieldNameToByteString -{- |-  Constructs a 'FieldName' from a 'String'.+{- | Constructs a 'FieldName' from a 'String'.  @since 1.0.0.0 -}@@ -46,8 +50,7 @@ stringToFieldName =   FieldName . B8.pack -{- |-  Converts a 'FieldName' back to a 'String'.+{- | Converts a 'FieldName' back to a 'String'.  @since 1.0.0.0 -}@@ -55,8 +58,7 @@ fieldNameToString =   B8.unpack . fieldNameToByteString -{- |-  Converts a 'FieldName' back to a 'B8.ByteString'.+{- | Converts a 'FieldName' back to a 'B8.ByteString'.  @since 1.0.0.0 -}@@ -64,8 +66,7 @@ fieldNameToByteString (FieldName name) =   name -{- |-  Constructs a 'FieldName' from a 'B8.ByteString'.+{- | Constructs a 'FieldName' from a 'B8.ByteString'.  @since 1.0.0.0 -}
src/Orville/PostgreSQL/Internal/IndexDefinition.hs view
@@ -30,8 +30,7 @@ import qualified Orville.PostgreSQL.Expr as Expr import qualified Orville.PostgreSQL.Marshall.FieldDefinition as FieldDefinition -{- |-  Defines an index that can be added to a 'Orville.PostgreSQL.TableDefinition'.+{- | Defines an index that can be added to a 'Orville.PostgreSQL.TableDefinition'.   Use one of the constructor functions below (such as 'uniqueIndex') to   construct the index definition you wish to have and then use   'Orville.PostgreSQL.addTableIndexes' to add them to your table definition.@@ -42,14 +41,13 @@ data IndexDefinition = IndexDefinition   { i_indexCreateExpr ::       IndexCreationStrategy ->-      Expr.Qualified Expr.TableName ->+      Expr.QualifiedOrUnqualified Expr.TableName ->       Expr.CreateIndexExpr   , i_indexMigrationKey :: IndexMigrationKey   , i_indexCreationStrategy :: IndexCreationStrategy   } -{- |-  Sets the 'IndexCreationStrategy' to be used when creating the index described+{- | Sets the 'IndexCreationStrategy' to be used when creating the index described   by the 'IndexDefinition'. By default, all indexes are created using the   'Transactional' strategy, but some tables are too large for this to be   feasible. See the 'Concurrent' creation strategy for how to work around this.@@ -65,8 +63,7 @@     { i_indexCreationStrategy = strategy     } -{- |-  Gets the 'IndexCreationStrategy' to be used when creating the index described+{- | Gets the 'IndexCreationStrategy' to be used when creating the index described   by the 'IndexDefinition'. By default, all indexes are created using the   'Transactional' strategy. @@ -78,22 +75,19 @@ indexCreationStrategy =   i_indexCreationStrategy -{- |-  Defines how an 'IndexDefinition' will be executed to add an index to a table.+{- | Defines how an 'IndexDefinition' will be executed to add an index to a table.   By default, all indexes are created using the 'Transactional' strategy.  @since 1.0.0.0 -} data IndexCreationStrategy-  = -- |-    --       The default strategy. The index will be added as part of a+  = -- | The default strategy. The index will be added as part of a     --       database transaction along with all the other DDL being executed     --       to migrate the database schema. If any migration should fail, the     --       index creation will be rolled back as part of the transaction.     --       This is how schema migrations work in general in Orville.     Transactional-  | -- |-    --       Creates the index using the @CONCURRENTLY@ keyword in PostgreSQL.+  | -- | Creates the index using the @CONCURRENTLY@ keyword in PostgreSQL.     --       Index creation will not lock the table during creation, allowing     --       the application to access the table normally while the index is     --       created. Concurrent index creation cannot be done in a@@ -112,10 +106,14 @@     --       indexes concurrently before you use this strategy. See     --       https://www.postgresql.org/docs/current/sql-createindex.html#SQL-CREATEINDEX-CONCURRENTLY.     Concurrent-  deriving (Eq, Show)+  deriving+    ( -- | @since 1.0.0.0+      Eq+    , -- | @since 1.0.0.0+      Show+    ) -{- |-  Orville uses 'IndexMigrationKey' values while performing auto migrations to+{- | Orville uses 'IndexMigrationKey' values while performing auto migrations to   determine whether an index needs to be added or dropped. For most use cases   the constructor functions that build an 'IndexDefinition' will create this   automatically for you.@@ -125,10 +123,14 @@ data IndexMigrationKey   = AttributeBasedIndexKey AttributeBasedIndexMigrationKey   | NamedIndexKey NamedIndexMigrationKey-  deriving (Eq, Ord)+  deriving+    ( -- | @since 1.0.0.0+      Eq+    , -- | @since 1.0.0.0+      Ord+    ) -{- |-  An 'IndexMigrationKey' using 'AttributeBasedIndexMigrationKey' will cause+{- | An 'IndexMigrationKey' using 'AttributeBasedIndexMigrationKey' will cause   Orville to compare the structure of the indexes found in the database to the   index structure it wants to create. If no matching index is found it will   create a new index.@@ -139,10 +141,16 @@   { indexKeyUniqueness :: Expr.IndexUniqueness   , indexKeyColumns :: [FieldDefinition.FieldName]   }-  deriving (Eq, Ord, Show)+  deriving+    ( -- | @since 1.0.0.0+      Eq+    , -- | @since 1.0.0.0+      Ord+    , -- | @since 1.0.0.0+      Show+    ) -{- |-  An 'IndexMigrationKey' using 'NamedIndexMigrationKey' will cause Orville to+{- | An 'IndexMigrationKey' using 'NamedIndexMigrationKey' will cause Orville to   compare the only the names of indexes found in the database when determine   whether to create the index. If an index with a matching name is found no   index will be created. If no matching index name is found a new index will be@@ -154,28 +162,25 @@ -} type NamedIndexMigrationKey = String -{- |-  Gets the 'IndexMigrationKey' for the 'IndexDefinition'+{- | Gets the 'IndexMigrationKey' for the 'IndexDefinition'  @since 1.0.0.0 -} indexMigrationKey :: IndexDefinition -> IndexMigrationKey indexMigrationKey = i_indexMigrationKey -{- |-  Gets the SQL expression that will be used to add the index to the specified+{- | Gets the SQL expression that will be used to add the index to the specified   table.  @since 1.0.0.0 -}-indexCreateExpr :: IndexDefinition -> Expr.Qualified Expr.TableName -> Expr.CreateIndexExpr+indexCreateExpr :: IndexDefinition -> Expr.QualifiedOrUnqualified Expr.TableName -> Expr.CreateIndexExpr indexCreateExpr indexDef =   i_indexCreateExpr     indexDef     (i_indexCreationStrategy indexDef) -{- |-  Constructs an 'IndexDefinition' for a non-unique index on the given columns.+{- | Constructs an 'IndexDefinition' for a non-unique index on the given columns.  @since 1.0.0.0 -}@@ -183,8 +188,7 @@ nonUniqueIndex =   mkIndexDefinition Expr.NonUniqueIndex -{- |-  Constructs an 'IndexDefinition' for a non-unique index with given SQL and+{- | Constructs an 'IndexDefinition' for a non-unique index with given SQL and   index name.  @since 1.0.0.0@@ -193,8 +197,7 @@ nonUniqueNamedIndex =   mkNamedIndexDefinition Expr.NonUniqueIndex -{- |-  Constructs an 'IndexDefinition' for a @UNIQUE@ index on the given columns.+{- | Constructs an 'IndexDefinition' for a @UNIQUE@ index on the given columns.  @since 1.0.0.0 -}@@ -202,8 +205,7 @@ uniqueIndex =   mkIndexDefinition Expr.UniqueIndex -{- |-  Constructs an 'IndexDefinition' for a @UNIQUE@ index with given SQL and index+{- | Constructs an 'IndexDefinition' for a @UNIQUE@ index with given SQL and index   name.  @since 1.0.0.0@@ -212,8 +214,7 @@ uniqueNamedIndex =   mkNamedIndexDefinition Expr.UniqueIndex -{- |-  Constructs an 'IndexDefinition' for an index on the given columns with the+{- | Constructs an 'IndexDefinition' for an index on the given columns with the   given uniqueness.  @since 1.0.0.0@@ -243,8 +244,7 @@       , i_indexCreationStrategy = Transactional       } -{- |-  Constructs an 'IndexDefinition' for an index with the given uniqueness, given+{- | Constructs an 'IndexDefinition' for an index with the given uniqueness, given   name, and given SQL.  @since 1.0.0.0@@ -270,8 +270,7 @@       , i_indexCreationStrategy = Transactional       } -{- |-  Internal helper to determine whether @CONCURRENTLY@ should be included in+{- | Internal helper to determine whether @CONCURRENTLY@ should be included in   the SQL to create the index.  @since 1.0.0.0
src/Orville/PostgreSQL/Internal/MigrationLock.hs view
@@ -8,7 +8,9 @@ @since 1.0.0.0 -} module Orville.PostgreSQL.Internal.MigrationLock-  ( MigrationLockId+  ( MigrationLockOptions (migrationLockId, maxLockAttempts, delayBetweenLockAttemptsMicros, lockDelayVariationMicros)+  , defaultLockOptions+  , MigrationLockId   , defaultLockId   , nextLockId   , withMigrationLock@@ -21,6 +23,7 @@ import qualified Control.Monad as Monad import qualified Control.Monad.IO.Class as MIO import Data.Int (Int32)+import qualified System.Random as Rand  import qualified Orville.PostgreSQL.Execution as Exec import qualified Orville.PostgreSQL.Internal.Bracket as Bracket@@ -29,8 +32,58 @@ import qualified Orville.PostgreSQL.Raw.RawSql as RawSql import qualified Orville.PostgreSQL.Raw.SqlValue as SqlValue -{- |-Identifies a PostgreSQL advisory lock to to be aquired by the application. Use+{- | 'MigrationLockOptions' controls how Orville acquires its PostgreSQL advisory lock+  to ensure that only one process is attempting to make schema changes at a time.+  See the descriptions of each of the options for more detail:++  * 'migrationLockId'+  * 'maxLockAttempts'+  * 'delayBetweenLockAttemptsMicros'+  * 'lockDelayVariationMicros'++@since 1.1.0.0+-}+data MigrationLockOptions = MigrationLockOptions+  { migrationLockId :: MigrationLockId+  -- ^ Specifies the 'MigrationLockId' for the lock that will be acquired to ensure+  -- only one process is attempting to migrate the schema at a time.+  --+  -- @since 1.1.0.0+  , maxLockAttempts :: Int+  -- ^ The maximum number of times Orville will attempt to acquire the migration+  -- lock. If the lock has not bene acquired after this many attempts, a+  -- 'MigrationLockError' will be thrown as an exception.+  --+  -- @since 1.1.0.0+  , delayBetweenLockAttemptsMicros :: Int+  -- ^ The minimum number of microseconds Orville will wait after a failed attempt to+  -- acquire the lock before it tries again. A random variation will be added to this+  -- to determine the delay after each failed attempt. See 'lockDelayVariationMicros'+  --+  -- @since 1.1.0.0+  , lockDelayVariationMicros :: Int+  -- ^ The maximum variation to add to the delay before attempting to acquire the lock+  -- again. The actual variation at each delay will be a pseudo-random number between+  -- @0@ and 'lockDelayVariationMicros'.+  --+  -- @since 1.1.0.0+  }++{- | The default lock options use the 'defaultLockId', 25 attempts and randomized delay+between 100 and 125 milliseconds.++@since 1.1.0.0+-}+defaultLockOptions :: MigrationLockOptions+defaultLockOptions =+  MigrationLockOptions+    { migrationLockId = defaultLockId+    , maxLockAttempts = 25+    , delayBetweenLockAttemptsMicros = 100000+    , lockDelayVariationMicros = 25000+    }++{- | Identifies a PostgreSQL advisory lock to to be aquired by the application. Use 'defaultLockId' to obtain the default value and 'nextLockId' to create custom values if you need them. @@ -41,8 +94,7 @@   , i_lockKey2 :: Int32   } -{- |-The lock id that Orville uses by default to ensure that just one copy of the+{- | The lock id that Orville uses by default to ensure that just one copy of the application is attempting to run migrations at a time.  @since 1.0.0.0@@ -54,8 +106,7 @@     , i_lockKey2 = 7995632     } -{- |-Increments the id of the given 'MigrationLockId', creating a new distinct lock+{- | Increments the id of the given 'MigrationLockId', creating a new distinct lock id. You can use this to create your own custom 'MigrationLockId' values as necessary if you need to control migration runs in a custom manner. @@ -70,8 +121,7 @@ orvilleLockScope :: Int32 orvilleLockScope = 17772 -{- |-  Executes an Orville action with a PostgreSQL advisory lock held that+{- | Executes an Orville action with a PostgreSQL advisory lock held that   indicates to other Orville processes that a database migration is being done   and no others should be performed concurrently. @@ -79,23 +129,35 @@ -} withMigrationLock ::   Monad.MonadOrville m =>-  MigrationLockId ->+  MigrationLockOptions ->   m a ->   m a-withMigrationLock lockId action =+withMigrationLock options action =   Monad.withConnection_ $     Bracket.bracketWithResult-      (accquireTransactionLock lockId)-      (\() _bracketResult -> releaseTransactionLock lockId)+      (accquireTransactionLock options)+      (\() _bracketResult -> releaseTransactionLock options)       (\() -> action)  accquireTransactionLock ::   forall m.   Monad.MonadOrville m =>-  MigrationLockId ->+  MigrationLockOptions ->   m ()-accquireTransactionLock lockId =+accquireTransactionLock options =   let+    lockId =+      migrationLockId options++    maxAttempts =+      maxLockAttempts options++    delayMicros =+      delayBetweenLockAttemptsMicros options++    delayVariationMicros =+      lockDelayVariationMicros options+     go :: Int -> m ()     go attempts = do       locked <- attemptLockAcquisition@@ -103,14 +165,17 @@         then pure ()         else do           MIO.liftIO $ do-            Monad.when (attempts >= 25) $ do+            Monad.when (attempts >= maxAttempts) $ do               throwIO $                 MigrationLockError-                  "Giving up after 25 attempts to aquire the migration lock."-            threadDelay 10000+                  ("Giving up after " <> show maxAttempts <> " attempts to aquire the migration lock.") +            variation <- Rand.randomRIO (0, delayVariationMicros)+            threadDelay (delayMicros + variation)+           go $ attempts + 1 +    attemptLockAcquisition :: m Bool     attemptLockAcquisition = do       tryLockResults <-         Exec.executeAndDecode Exec.OtherQuery (tryLockExpr lockId) lockedMarshaller@@ -124,9 +189,9 @@   in     go 0 -releaseTransactionLock :: Monad.MonadOrville m => MigrationLockId -> m ()+releaseTransactionLock :: Monad.MonadOrville m => MigrationLockOptions -> m () releaseTransactionLock =-  Exec.executeVoid Exec.OtherQuery . releaseLockExpr+  Exec.executeVoid Exec.OtherQuery . releaseLockExpr . migrationLockId  lockedMarshaller :: Marshall.AnnotatedSqlMarshaller Bool Bool lockedMarshaller =@@ -152,14 +217,17 @@     <> RawSql.parameter (SqlValue.fromInt32 (i_lockKey2 lockId))     <> RawSql.rightParen -{- |-  Raised if 'withMigrationLock' cannot acquire the migration lock in a+{- | Raised if 'withMigrationLock' cannot acquire the migration lock in a   timely manner.  @since 1.0.0.0 -} newtype MigrationLockError   = MigrationLockError String-  deriving (Show)+  deriving+    ( -- | @since 1.0.0.0+      Show+    ) +-- | @since 1.0.0.0 instance Exception MigrationLockError
src/Orville/PostgreSQL/Internal/MonadOrville.hs view
@@ -32,8 +32,7 @@ import Orville.PostgreSQL.Monad.HasOrvilleState (HasOrvilleState (askOrvilleState, localOrvilleState)) import Orville.PostgreSQL.Raw.Connection (Connection, withPoolConnection) -{- |-  'MonadOrville' is the typeclass that most Orville operations require to+{- | 'MonadOrville' is the typeclass that most Orville operations require to   do anything that connects to the database. 'MonadOrville' itself is empty,   but it lists all the required typeclasses as superclass constraints so that   it can be used instead of listing all the constraints on every function.@@ -57,8 +56,7 @@   ) =>   MonadOrville m -{- |-  'MonadOrvilleControl' presents the interface that Orville will use to lift+{- | 'MonadOrvilleControl' presents the interface that Orville will use to lift   low-level IO operations that cannot be lifted via   'Control.Monad.IO.Class.liftIO' (i.e. those where the IO parameter is   contravariant rather than covariant).@@ -75,20 +73,31 @@   implementation of the methods below for monads that implement   'Control.Monad.IO.Unlift.MonadUnliftIO'. +  Note: Orville assumes that execution of any monadic operations will continue+  normally other than when an exception is thrown in the IO monad. The monad+  implementing 'MonadOrvilleControl' allows for the monad execution to+  terminate in other ways (such as a 'Nothing' in the case of 'MaybeT' or+  'Left' in the case of 'ExceptT'), it will likely interfere with Orville's+  'withTransaction' function. In particular, if the action passed to+  'withTransaction' terminates early in any way other than an exception it will+  prevent the transaction from being resolved before the connection is returned+  to the connection pool. When that connection is taken from the pool in the+  future, it will already have the open and unfinished transaction on it.+  Another call to 'withTransaction' using that connection will raise an+  exception when it detects that there is already an open transaction.+ @since 1.0.0.0 -} class MonadOrvilleControl m where-  -- |-  --     Orville will use this function to lift the acquisition of connections-  --     from the resource pool into the application monad.+  -- | Orville will use this function to lift the acquisition of connections+  -- from the resource pool into the application monad.   --   -- @since 1.0.0.0   liftWithConnection ::     (forall a. (Connection -> IO a) -> IO a) -> (Connection -> m b) -> m b -  -- |-  --     Orville will use this function to lift exception catches into the-  --     application monad.+  -- | Orville will use this function to lift exception catches into the+  -- application monad.   --   -- @since 1.0.0.0   liftCatch ::@@ -98,10 +107,9 @@     (e -> m b) ->     m b -  -- |-  --     Orville will use this function to lift 'Control.Exception.mask' calls-  --     into the application monad to guarantee resource cleanup is executed-  --     even when asynchronous exceptions are thrown.+  -- | Orville will use this function to lift 'Control.Exception.mask' calls+  -- into the application monad to guarantee resource cleanup is executed+  -- even when asynchronous exceptions are thrown.   --   -- @since 1.0.0.0   liftMask ::@@ -109,6 +117,7 @@     ((forall a. m a -> m a) -> m c) ->     m c +-- | @since 1.0.0.0 instance MonadOrvilleControl IO where   liftWithConnection ioWithConn =     ioWithConn@@ -119,6 +128,7 @@   liftMask ioMask =     ioMask +-- | @since 1.0.0.0 instance MonadOrvilleControl m => MonadOrvilleControl (ReaderT state m) where   liftWithConnection ioWithConn action = do     ReaderT $ \env ->@@ -136,10 +146,10 @@       liftMask ioMask $ \restore ->         runReaderT (action (mapReaderT restore)) env +-- | @since 1.0.0.0 instance (MonadOrvilleControl m, MonadIO m) => MonadOrville (ReaderT OrvilleState m) -{- |-  'withConnection' should be used to receive a 'Connection' handle for+{- | 'withConnection' should be used to receive a 'Connection' handle for   executing queries against the database from within an application monad using   Orville.  For the "outermost" call of 'withConnection', a connection will be   acquired from the resource pool. Additional calls to 'withConnection' that@@ -155,8 +165,7 @@ withConnection connectedAction = do   withConnectedState (connectedAction . connectedConnection) -{- |-  'withConnection_' is a convenience version of 'withConnection' for those that+{- | 'withConnection_' is a convenience version of 'withConnection' for those that   don't need the actual connection handle. You might want to use this function   even without using the handle because it ensures that all the Orville   operations performed by the action passed to it occur on the same connection.@@ -170,8 +179,7 @@ withConnection_ =   withConnection . const -{- |-  INTERNAL: This in an internal version of 'withConnection' that gives access to+{- | INTERNAL: This in an internal version of 'withConnection' that gives access to   the entire 'ConnectedState' value to allow for transaction management.  @since 1.0.0.0
src/Orville/PostgreSQL/Internal/OrvilleState.hs view
@@ -48,8 +48,7 @@ import qualified Orville.PostgreSQL.Raw.RawSql as RawSql import qualified Orville.PostgreSQL.Raw.SqlCommenter as SqlCommenter -{- |-  'OrvilleState' is used to manage opening connections to the database,+{- | 'OrvilleState' is used to manage opening connections to the database,   transactions, etc. 'newOrvilleState' should be used to create an appopriate   initial state for your monad's context. @@ -65,8 +64,7 @@   , _orvilleSqlCommenterAttributes :: Maybe SqlCommenter.SqlCommenterAttributes   } -{- |-  Get the connection pool being used for the 'OrvilleState'.+{- | Get the connection pool being used for the 'OrvilleState'.  @since 1.0.0.0 -}@@ -74,8 +72,7 @@ orvilleConnectionPool =   _orvilleConnectionPool -{- |-  INTERNAL: The 'ConnectionState' indicates whether Orville currently has a+{- | INTERNAL: The 'ConnectionState' indicates whether Orville currently has a   connection open, and contains the connection if it does.  @since 1.0.0.0@@ -84,8 +81,7 @@ orvilleConnectionState =   _orvilleConnectionState -{- |-  The 'ErrorDetailLevel' controls how much information Orville includes in+{- | The 'ErrorDetailLevel' controls how much information Orville includes in   error messages it generates when data cannot be decoded from rows in the   database. @@ -95,8 +91,7 @@ orvilleErrorDetailLevel =   _orvilleErrorDetailLevel -{- |-  Orville will call the transaction callback any time a transaction event+{- | Orville will call the transaction callback any time a transaction event   occurs. You can register a callback with 'addTransactionCallback'.  @since 1.0.0.0@@ -105,8 +100,7 @@ orvilleTransactionCallback =   _orvilleTransactionCallback -{- |-  The SQL expression that Orville will use to begin a transaction. You can set+{- | The SQL expression that Orville will use to begin a transaction. You can set   this via 'setBeginTransactionExpr' to have fine-grained control over the   transaction parameters, such as isolation level. @@ -116,8 +110,7 @@ orvilleBeginTransactionExpr =   _orvilleBeginTransactionExpr -{- |-  The SqlCommenter attributes that Orville will include with queries. These can+{- | The SqlCommenter attributes that Orville will include with queries. These can   be modified with 'addSqlCommenterAttributes'. See   https://google.github.io/sqlcommenter/. @@ -127,8 +120,7 @@ orvilleSqlCommenterAttributes =   _orvilleSqlCommenterAttributes -{- |-  Registers a callback to be invoked during transactions.+{- | Registers a callback to be invoked during transactions.    The callback given will be called after the SQL statement corresponding   to the given event has finished executing. Callbacks will be called@@ -160,8 +152,7 @@   in     state {_orvilleTransactionCallback = wrappedCallback} -{- |-  Creates an appropriate initial 'OrvilleState' that will use the connection+{- | Creates an appropriate initial 'OrvilleState' that will use the connection   pool given to initiate connections to the database.  @since 1.0.0.0@@ -178,8 +169,7 @@     , _orvilleSqlCommenterAttributes = Nothing     } -{- |-  Creates a new initial 'OrvilleState' using the connection pool from the+{- | Creates a new initial 'OrvilleState' using the connection pool from the   provided state. You might need to use this if you are spawning one Orville   monad from another and they should not share the same connection and   transaction state.@@ -192,8 +182,7 @@     <$> _orvilleErrorDetailLevel     <*> _orvilleConnectionPool -{- |-  INTERNAL: Transitions the 'OrvilleState' into "connected" status, storing the+{- | INTERNAL: Transitions the 'OrvilleState' into "connected" status, storing the   given 'Connection' as the database connection to be used to execute all   queries. This is used by 'Orville.PostgreSQL.Monad.withConnection' to track   the connection it retrieves from the pool.@@ -206,8 +195,7 @@     { _orvilleConnectionState = Connected connectedState     } -{- |-  INTERNAL: This type is used to signal whether a database connection has+{- | INTERNAL: This type is used to signal whether a database connection has   been retrieved from the pool for the current operation or not. The   value is tracked in the 'OrvilleState' for the host monad, and is checked   by 'Orville.PostgreSQL.Monad.withConnection' to avoid checking out two@@ -220,8 +208,7 @@   = NotConnected   | Connected ConnectedState -{- |-  INTERNAL: This type is used hold the connection while it is open and+{- | INTERNAL: This type is used hold the connection while it is open and   track the state of open transactions and savepoints on the connection.  @since 1.0.0.0@@ -231,8 +218,7 @@   , connectedTransaction :: Maybe TransactionState   } -{- |-  INTERNAL: This type is use to track the state of open transactions and+{- | INTERNAL: This type is use to track the state of open transactions and   savepoints on an open connection.  @since 1.0.0.0@@ -241,8 +227,7 @@   = OutermostTransaction   | SavepointTransaction Savepoint -{- |-  INTERNAL: Constructs a new 'TransactionState' to represent beginning a+{- | INTERNAL: Constructs a new 'TransactionState' to represent beginning a   new transaction in SQL.  @since 1.0.0.0@@ -257,17 +242,20 @@     Just (SavepointTransaction savepoint) ->       SavepointTransaction (nextSavepoint savepoint) -{- |-  An internal Orville identifier for a savepoint in a PostgreSQL transaction.+{- | An internal Orville identifier for a savepoint in a PostgreSQL transaction.  @since 1.0.0.0 -} newtype Savepoint   = Savepoint Int-  deriving (Eq, Show)+  deriving+    ( -- | @since 1.0.0.0+      Eq+    , -- | @since 1.0.0.0+      Show+    ) -{- |-  The initial identifier Orville uses to track the first savepoint within+{- | The initial identifier Orville uses to track the first savepoint within   a transaction.  @since 1.0.0.0@@ -276,8 +264,7 @@ initialSavepoint =   Savepoint 1 -{- |-  Determines the identifier for the next savepoint in a transaction after the+{- | Determines the identifier for the next savepoint in a transaction after the   given savepoint.  @since 1.0.0.0@@ -286,8 +273,7 @@ nextSavepoint (Savepoint n) =   Savepoint (n + 1) -{- |-  Indicates how many levels of nested savepoints the given 'Savepoint'+{- | Indicates how many levels of nested savepoints the given 'Savepoint'   identifier represents.  @since 1.0.0.0@@ -295,8 +281,7 @@ savepointNestingLevel :: Savepoint -> Int savepointNestingLevel (Savepoint n) = n -{- |-  Describes an event in the lifecycle of a database transaction. You can use+{- | Describes an event in the lifecycle of a database transaction. You can use   'addTransactionCallback' to register a callback to respond to these events.   The callback will be called after the event in question has been successfully   executed.@@ -324,18 +309,21 @@     CommitTransaction   | -- | Indicates that the transaction has been rolled back.     RollbackTransaction-  deriving (Eq, Show)+  deriving+    ( -- | @since 1.0.0.0+      Eq+    , -- | @since 1.0.0.0+      Show+    ) -{- |-  The default transaction callback is simply a no-op.+{- | The default transaction callback is simply a no-op.  @since 1.0.0.0 -} defaultTransactionCallback :: TransactionEvent -> IO () defaultTransactionCallback = const (pure ()) -{- |-  Constructs the appropriate 'TransactionEvent' for opening a new transaction+{- | Constructs the appropriate 'TransactionEvent' for opening a new transaction   based on the current 'TransactionState'.  @since 1.0.0.0@@ -346,8 +334,7 @@     OutermostTransaction -> BeginTransaction     SavepointTransaction savepoint -> NewSavepoint savepoint -{- |-  Constructs the appropriate 'TransactionEvent' for rolling back the innermost+{- | Constructs the appropriate 'TransactionEvent' for rolling back the innermost   transaction based on the current 'TransactionState'.  @since 1.0.0.0@@ -358,8 +345,7 @@     OutermostTransaction -> RollbackTransaction     SavepointTransaction savepoint -> RollbackToSavepoint savepoint -{- |-  Constructs the appropriate 'TransactionEvent' to represent a transaction+{- | Constructs the appropriate 'TransactionEvent' to represent a transaction   completely successfully based on the current 'TransactionState'.  @since 1.0.0.0@@ -370,8 +356,7 @@     OutermostTransaction -> CommitTransaction     SavepointTransaction savepoint -> ReleaseSavepoint savepoint -{- |-  The callback Orville will call whenever it wants to run SQL. You can+{- | The callback Orville will call whenever it wants to run SQL. You can   register a callback using 'addSqlExecutionCallback'.  @since 1.0.0.0@@ -386,8 +371,7 @@ orvilleSqlExecutionCallback =   _orvilleSqlExecutionCallback -{- |-  The default SQL execption callback simply runs the IO action given without+{- | The default SQL execption callback simply runs the IO action given without   doing anything else.  @since 1.0.0.0@@ -395,8 +379,7 @@ defaultSqlExectionCallback :: QueryType -> RawSql.RawSql -> IO a -> IO a defaultSqlExectionCallback _ _ io = io -{- |-  Adds a callback to be called when an Orville operation executes a SQL+{- | Adds a callback to be called when an Orville operation executes a SQL   statement. The callback is given the IO action that will perform the   query execution and must call that action for the query to be run.   In particular, you can use this to time queries and log any that are slow.@@ -424,8 +407,7 @@   in     state {_orvilleSqlExecutionCallback = layeredCallback} -{- |-  The default begin transaction expression is simply @BEGIN TRANSACTION@+{- | The default begin transaction expression is simply @BEGIN TRANSACTION@   with no options specified.  @since 1.0.0.0@@ -434,8 +416,7 @@ defaultBeginTransactionExpr =   Expr.beginTransaction Nothing -{- |-  Sets the SQL expression that Orville will use to begin transactions. You can+{- | Sets the SQL expression that Orville will use to begin transactions. You can   control the transaction isolation level by building your own   'Expr.BeginTransactionExpr' with the desired isolation level. @@ -450,8 +431,7 @@     { _orvilleBeginTransactionExpr = expr     } -{- |-  Sets the SqlCommenterAttributes that Orville will then add to any following+{- | Sets the SqlCommenterAttributes that Orville will then add to any following   statement executions.  @since 1.0.0.0@@ -465,8 +445,7 @@     { _orvilleSqlCommenterAttributes = Just comments     } -{- |-  Adds the SqlCommenterAttributes to the already existing attributes that+{- | Adds the SqlCommenterAttributes to the already existing attributes that   Orville will then add to any following statement executions.  @since 1.0.0.0
src/Orville/PostgreSQL/Internal/RowCountExpectation.hs view
@@ -14,8 +14,7 @@ import Control.Exception (Exception, throwIO) import Control.Monad.IO.Class (MonadIO (liftIO)) -{- |-  INTERNAL: This should really never get thrown in the real world. It would be+{- | INTERNAL: This should really never get thrown in the real world. It would be   thrown if the returning clause from an insert statement for a single record   returned 0 records or more than 1 record. @@ -23,10 +22,18 @@ -} newtype RowCountExpectationError   = RowCountExpectationError String-  deriving (Show)+  deriving+    ( -- | @since 1.0.0.0+      Show+    ) +-- | @since 1.0.0.0 instance Exception RowCountExpectationError +{- | Throw if input was not exactly size 1.++@since 1.0.0.0+-} expectExactlyOneRow :: MonadIO m => String -> [a] -> m a expectExactlyOneRow caller rows =   case rows of@@ -38,6 +45,10 @@           <> ": Expected exactly one row to be returned, but got "           <> show (length rows) +{- | Throw if input size was greater than 1.++@since 1.0.0.0+-} expectAtMostOneRow :: MonadIO m => String -> [a] -> m (Maybe a) expectAtMostOneRow caller rows =   case rows of
src/Orville/PostgreSQL/Marshall.hs view
@@ -1,7 +1,7 @@ {-# OPTIONS_GHC -Wno-missing-import-lists #-}  {- |-Copyright : Flipstone Technology Partners 2023+Copyright : Flipstone Technology Partners 2023-2024 License   : MIT Stability : Stable @@ -20,15 +20,19 @@   , module Orville.PostgreSQL.Marshall.SyntheticField   , module Orville.PostgreSQL.Marshall.MarshallError   , module Orville.PostgreSQL.Marshall.SqlType+  , module Orville.PostgreSQL.Marshall.AliasName+  , module Orville.PostgreSQL.Marshall.SqlComparable   ) where  -- Note: we list the re-exports explicity above to control the order that they -- appear in the generated haddock documentation. +import Orville.PostgreSQL.Marshall.AliasName import Orville.PostgreSQL.Marshall.DefaultValue import Orville.PostgreSQL.Marshall.FieldDefinition import Orville.PostgreSQL.Marshall.MarshallError+import Orville.PostgreSQL.Marshall.SqlComparable import Orville.PostgreSQL.Marshall.SqlMarshaller import Orville.PostgreSQL.Marshall.SqlType import Orville.PostgreSQL.Marshall.SyntheticField
+ src/Orville/PostgreSQL/Marshall/AliasName.hs view
@@ -0,0 +1,86 @@+{- |+Copyright : Flipstone Technology Partners 2024-2025+License   : MIT+Stability : Stable++@since 1.1.0.0+-}+module Orville.PostgreSQL.Marshall.AliasName+  ( AliasName+  , stringToAliasName+  , aliasNameToString+  , aliasNameToAliasExpr+  , aliasNameAndFieldNameToColumnName+  , aliasNameToByteString+  , byteStringToAliasName+  ) where++import qualified Data.ByteString.Char8 as B8++import qualified Orville.PostgreSQL.Expr as Expr+import Orville.PostgreSQL.Internal.FieldName (FieldName, fieldNameToColumnName)++{- | A simple type to represent the name of a field.++@since 1.1.0.0+-}+newtype AliasName+  = AliasName B8.ByteString+  deriving+    ( -- | @since 1.1.0.0+      Eq+    , -- | @since 1.1.0.0+      Ord+    , -- | @since 1.1.0.0+      Show+    )++{- | Convert an 'AliasName' and a 'FieldName' to a 'Expr.ColumnName' for usage in SQL expressions.+  The result will be properly quoted and escaped.++@since 1.1.0.0+-}+aliasNameAndFieldNameToColumnName :: AliasName -> FieldName -> Expr.Qualified Expr.ColumnName+aliasNameAndFieldNameToColumnName aliasName =+  Expr.aliasQualifyColumn+    (aliasNameToAliasExpr aliasName)+    . fieldNameToColumnName++{- | Constructs a 'AliasName' from a 'String'.++@since 1.1.0.0+-}+stringToAliasName :: String -> AliasName+stringToAliasName =+  AliasName . B8.pack++{- | Converts a 'AliasName' to an 'AliasExpr'.++@since 1.1.0.0+-}+aliasNameToAliasExpr :: AliasName -> Expr.AliasExpr+aliasNameToAliasExpr =+  Expr.fromIdentifier . Expr.identifierFromBytes . aliasNameToByteString++{- | Converts a 'AliasName' back to a 'String'.++@since 1.1.0.0+-}+aliasNameToString :: AliasName -> String+aliasNameToString =+  B8.unpack . aliasNameToByteString++{- | Converts a 'AliasName' back to a 'B8.ByteString'.++@since 1.1.0.0+-}+aliasNameToByteString :: AliasName -> B8.ByteString+aliasNameToByteString (AliasName name) =+  name++{- | Constructs a 'AliasName' from a 'B8.ByteString'.++@since 1.1.0.0+-}+byteStringToAliasName :: B8.ByteString -> AliasName+byteStringToAliasName = AliasName
src/Orville/PostgreSQL/Marshall/DefaultValue.hs view
@@ -1,5 +1,3 @@-{-# LANGUAGE GeneralizedNewtypeDeriving #-}- {- | Copyright : Flipstone Technology Partners 2023 License   : MIT@@ -34,14 +32,13 @@ import qualified Data.Text as T import qualified Data.Text.Encoding as TextEnc import qualified Data.Time as Time-import qualified Numeric as Numeric+import qualified Numeric  import qualified Orville.PostgreSQL.Expr as Expr import qualified Orville.PostgreSQL.Raw.PgTime as PgTime import qualified Orville.PostgreSQL.Raw.RawSql as RawSql -{- |-  A 'DefaultValue' is a SQL expression that can be attached to a+{- | A 'DefaultValue' is a SQL expression that can be attached to a   field definition to give a default value for a column at the database level.   The default value will be used if an insert is done and the column is not   provided.@@ -58,8 +55,7 @@ newtype DefaultValue a   = DefaultValue Expr.ValueExpression -{- |-  Builds a default value for any 'Integral' type @n@ by converting it to an+{- | Builds a default value for any 'Integral' type @n@ by converting it to an   'Integer'.  @since 1.0.0.0@@ -81,8 +77,7 @@             <> RawSql.fromString "::integer"       else DefaultValue . RawSql.unsafeFromRawSql . RawSql.fromBytes $ decimalBytes -{- |-  Builds a default value from an 'Int16' for use with small integer fields.+{- | Builds a default value from an 'Int16' for use with small integer fields.    This is a specialization of 'integerDefault'. @@ -91,8 +86,7 @@ smallIntegerDefault :: Int16 -> DefaultValue Int16 smallIntegerDefault = integralDefault -{- |-  Builds a default value from an 'Int32' for use with integer fields.+{- | Builds a default value from an 'Int32' for use with integer fields.    This is a specialization of 'integerDefault'. @@ -101,8 +95,7 @@ integerDefault :: Int32 -> DefaultValue Int32 integerDefault = integralDefault -{- |-  Builds a default value from an 'Int16' for use with big integer fields.+{- | Builds a default value from an 'Int16' for use with big integer fields.    This is a specialization of 'integerDefault'. @@ -111,8 +104,7 @@ bigIntegerDefault :: Int64 -> DefaultValue Int64 bigIntegerDefault = integralDefault -{- |-  Builds a default value from a 'Double' field for use with double fields.+{- | Builds a default value from a 'Double' field for use with double fields.  @since 1.0.0.0 -}@@ -132,8 +124,7 @@             <> RawSql.fromString "::numeric"       else DefaultValue . RawSql.unsafeFromRawSql . RawSql.fromBytes $ decimalBytes -{- |-  Builds a default value from a 'Bool', for use with boolean fields.+{- | Builds a default value from a 'Bool', for use with boolean fields.  @since 1.0.0.0 -}@@ -147,8 +138,7 @@   in     DefaultValue $ RawSql.unsafeSqlExpression pgString -{- |-  Builds a default value from a 'T.Text', for use with unbounded, bounded+{- | Builds a default value from a 'T.Text', for use with unbounded, bounded   and fixed-length text fields.  @since 1.0.0.0@@ -159,8 +149,7 @@     RawSql.stringLiteral (TextEnc.encodeUtf8 text)       <> RawSql.fromString "::text" -{- |-  Builds a default value from a 'Time.Day' for use with date fields.+{- | Builds a default value from a 'Time.Day' for use with date fields.  @since 1.0.0.0 -}@@ -174,8 +163,7 @@       RawSql.stringLiteral pgText         <> RawSql.fromString "::date" -{- |-  Builds a default value that will default to the current date (i.e. the+{- | Builds a default value that will default to the current date (i.e. the   date at which the database populates the default value on a given row).    For use with date fields.@@ -189,8 +177,7 @@     . RawSql.fromString     $ "('now'::text)::date" -{- |-  Builds a default value from a 'Time.UTCTime' for use with UTC timestamp fields.+{- | Builds a default value from a 'Time.UTCTime' for use with UTC timestamp fields.  @since 1.0.0.0 -}@@ -204,8 +191,7 @@       RawSql.stringLiteral pgText         <> RawSql.fromString "::timestamp with time zone" -{- |-  Builds a default value that will default to the current UTC time (i.e. the+{- | Builds a default value that will default to the current UTC time (i.e. the   time at which the database populates the default value on a given row).    For use with UTC timestamp fields.@@ -216,8 +202,7 @@ currentUTCTimestampDefault =   DefaultValue $ RawSql.unsafeSqlExpression "now()" -{- |-  Builds a default value from a 'Time.LocalTime' for use with local timestamp fields.+{- | Builds a default value from a 'Time.LocalTime' for use with local timestamp fields.  @since 1.0.0.0 -}@@ -232,8 +217,7 @@       $ RawSql.stringLiteral pgText         <> RawSql.fromString "::timestamp without time zone" -{- |-  Builds a default value that will default to the current local time (i.e. the+{- | Builds a default value that will default to the current local time (i.e. the   time at which the database populates the default value on a given row).    Note: "local" time here will be determined by the database itself, subject to@@ -247,8 +231,7 @@ currentLocalTimestampDefault =   DefaultValue $ RawSql.unsafeSqlExpression "('now'::text)::timestamp without time zone" -{- |-  Coerces a 'DefaultValue' so that it can be used with field definitions of+{- | Coerces a 'DefaultValue' so that it can be used with field definitions of   a different Haskell type. The coercion will always succeed, and is safe as   far as Haskell itself is concerned. As long as the 'DefaultValue' is used   with a column whose database type is the same as the one the 'DefaultValue'@@ -260,8 +243,7 @@ coerceDefaultValue (DefaultValue expression) =   DefaultValue expression -{- |-  Returns a database value expression for the default value.+{- | Returns a database value expression for the default value.  @since 1.0.0.0 -}@@ -269,8 +251,7 @@ defaultValueExpression (DefaultValue expression) =   expression -{- |-  Constructs a default value from a 'Expr.ValueExpression'. You can use this to+{- | Constructs a default value from a 'Expr.ValueExpression'. You can use this to   construct default values for any SQL expression that Orville does not support   directly. 
src/Orville/PostgreSQL/Marshall/FieldDefinition.hs view
@@ -1,8 +1,10 @@+{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-}+{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-}  {- |-Copyright : Flipstone Technology Partners 2023+Copyright : Flipstone Technology Partners 2023-2025 License   : MIT Stability : Stable @@ -33,6 +35,8 @@   , (.==)   , fieldNotEquals   , (./=)+  , fieldIsDistinctFrom+  , fieldIsNotDistinctFrom   , fieldGreaterThan   , (.>)   , fieldLessThan@@ -94,6 +98,14 @@   , jsonbField   , fieldOfType   , whereColumnComparison+  , QualifiedFieldDefinition+  , qualifyField+  , qualifiedFieldColumnName+  , FieldQualifier (qualifyColumnName)+  , FieldIdentityGeneration (GeneratedAlways, GeneratedByDefault)+  , markAsIdentity+  , unmarkIdentity+  , fieldIdentityGeneration   ) where @@ -107,14 +119,16 @@  import qualified Orville.PostgreSQL.Expr as Expr import Orville.PostgreSQL.Internal.FieldName (FieldName, byteStringToFieldName, fieldNameToByteString, fieldNameToColumnName, fieldNameToString, stringToFieldName)+import qualified Orville.PostgreSQL.Marshall.AliasName as AliasName import qualified Orville.PostgreSQL.Marshall.DefaultValue as DefaultValue+import Orville.PostgreSQL.Marshall.SqlComparable (SqlComparable (referenceValueExpression, toComparableSqlValue))+import qualified Orville.PostgreSQL.Marshall.SqlComparable as SqlComparable import qualified Orville.PostgreSQL.Marshall.SqlType as SqlType import qualified Orville.PostgreSQL.Raw.SqlValue as SqlValue import qualified Orville.PostgreSQL.Schema.ConstraintDefinition as ConstraintDefinition import qualified Orville.PostgreSQL.Schema.TableIdentifier as TableIdentifier -{- |-  'FieldDefinition' determines the SQL construction of a column in the+{- | 'FieldDefinition' determines the SQL construction of a column in the   database, comprising the name, SQL type and whether the field is nullable.   A 'FieldDefinition' is matched to a particular Haskell type, which it knows   how to marshall to and from the database representation of SQL type for@@ -129,18 +143,26 @@   , i_fieldDefaultValue :: Maybe (DefaultValue.DefaultValue a)   , i_fieldDescription :: Maybe String   , i_fieldTableConstraints :: [FieldName -> ConstraintDefinition.ConstraintDefinition]+  , i_fieldIdentity :: IdentityGADT nullability   } -{- |-  The name used in database queries to reference the field.+{- | Constructs the 'Expr.ValueExpression' for a field for use in SQL expressions+  from the "Orville.PostgreSQL.Expr" module. +@since 1.1.0.0+-}+instance SqlComparable.SqlComparable (FieldDefinition nullability a) a where+  toComparableSqlValue = fieldValueToSqlValue+  referenceValueExpression = fieldColumnReference++{- | The name used in database queries to reference the field.+ @since 1.0.0.0 -} fieldName :: FieldDefinition nullability a -> FieldName fieldName = i_fieldName -{- |-  Sets the name used in database queries to reference the field.+{- | Sets the name used in database queries to reference the field.  @since 1.0.0.0 -}@@ -150,17 +172,15 @@     { i_fieldName = newName     } -{- |-  Returns the description that was passed to 'setFieldDescription', if any.+{- | Returns the description that was passed to 'setFieldDescription', if any.  @since 1.0.0.0 -} fieldDescription :: FieldDefinition nullability a -> Maybe String fieldDescription = i_fieldDescription -{- |-  Sets the description for the field. This description is not currently used-  anywhere by Orville itself, but users can retrieve the description via+{- | Sets the description for the field. This description will be used to add+  a comment to the column, and users can retrieve the description via   'fieldDescription' for their own purposes (e.g. generating documentation).  @since 1.0.0.0@@ -171,8 +191,7 @@     { i_fieldDescription = Just description     } -{- |-  The 'SqlType.SqlType' for the 'FieldDefinition' determines the PostgreSQL+{- | The 'SqlType.SqlType' for the 'FieldDefinition' determines the PostgreSQL   data type used to define the field as well as how to marshall Haskell values   to and from the database. @@ -181,16 +200,14 @@ fieldType :: FieldDefinition nullability a -> SqlType.SqlType a fieldType = i_fieldType -{- |-  Returns the default value definition for the field, if any has been set.+{- | Returns the default value definition for the field, if any has been set.  @since 1.0.0.0 -} fieldDefaultValue :: FieldDefinition nullability a -> Maybe (DefaultValue.DefaultValue a) fieldDefaultValue = i_fieldDefaultValue -{- |- A 'FieldNullability' is returned by the 'fieldNullability' function, which+{- | A 'FieldNullability' is returned by the 'fieldNullability' function, which  can be used when a function works on both 'Nullable' and 'NotNull' functions  but needs to deal with each type of field separately. It adds wrapper  constructors around the 'FieldDefinition' that you can pattern match on to@@ -202,8 +219,7 @@   = NullableField (FieldDefinition Nullable a)   | NotNullField (FieldDefinition NotNull a) -{- |- Resolves the @nullability@ of a field to a concrete type, which is returned+{- | Resolves the @nullability@ of a field to a concrete type, which is returned  via the 'FieldNullability' type. You can pattern match on this type to then  extract the either 'Nullable' or 'NotNull' field for cases where you may  require different logic based on the nullability of a field.@@ -216,8 +232,7 @@     NullableGADT -> NullableField field     NotNullGADT -> NotNullField field -{- |-  Indicates whether a field is not nullable.+{- | Indicates whether a field is not nullable.  @since 1.0.0.0 -}@@ -227,8 +242,7 @@     NullableGADT -> False     NotNullGADT -> True -{- |-  A list of table constraints that will be included on any table that uses this+{- | A list of table constraints that will be included on any table that uses this   field definition.  @since 1.0.0.0@@ -249,8 +263,7 @@       ConstraintDefinition.emptyTableConstraints       constructedConstraints -{- |-  Adds the given table constraints to the field definition. These constraints+{- | Adds the given table constraints to the field definition. These constraints   will then be included on any table where the field is used. The constraints   are passed a function that will take the name of the field definition and   construct the constraints. This allows the@@ -274,8 +287,7 @@         constraintDefs <> i_fieldTableConstraints fieldDef     } -{- |-  Adds a @FOREIGN KEY@ constraint to the 'FieldDefinition' (using+{- | Adds a @FOREIGN KEY@ constraint to the 'FieldDefinition' (using   'addFieldTableConstraints'). This constraint will be included on any table   that uses the field definition. @@ -294,8 +306,7 @@     foreignFieldName     ConstraintDefinition.defaultForeignKeyOptions -{- |-  Adds a @FOREIGN KEY@ constraint to the 'FieldDefinition'. This constraint+{- | Adds a @FOREIGN KEY@ constraint to the 'FieldDefinition'. This constraint   will be included on any table that uses the field definition.  @since 1.0.0.0@@ -324,8 +335,7 @@   in     addFieldTableConstraints [constraintToAdd] fieldDef -{- |-  Adds a @UNIQUE@ constraint to the 'FieldDefinition'. This constraint+{- | Adds a @UNIQUE@ constraint to the 'FieldDefinition'. This constraint   will be included on any table that uses the field definition.  @since 1.0.0.0@@ -340,8 +350,7 @@   in     addFieldTableConstraints [constraintToAdd] fieldDef -{- |-  Marshalls a Haskell value to be stored in the field to its 'SqlValue.SqlValue'+{- | Marshalls a Haskell value to be stored in the field to its 'SqlValue.SqlValue'   representation and packages the result as a 'Expr.ValueExpression' so that   it can be easily used with other @Expr@ functions. @@ -351,18 +360,15 @@ fieldValueToExpression field =   Expr.valueExpression . fieldValueToSqlValue field -{- |-  Marshalls a Haskell value to be stored in the field to its 'SqlValue.SqlValue'+{- | Marshalls a Haskell value to be stored in the field to its 'SqlValue.SqlValue'   representation.  @since 1.0.0.0 -} fieldValueToSqlValue :: FieldDefinition nullability a -> a -> SqlValue.SqlValue-fieldValueToSqlValue =-  SqlType.sqlTypeToSql . fieldType+fieldValueToSqlValue = SqlType.sqlTypeToSql . fieldType -{- |-  Marshalls a 'SqlValue.SqlValue' from the database into the Haskell value that represents it.+{- | Marshalls a 'SqlValue.SqlValue' from the database into the Haskell value that represents it.   This may fail, in which case a 'Left' is returned with an error message.  @since 1.0.0.0@@ -371,8 +377,7 @@ fieldValueFromSqlValue =   SqlType.sqlTypeFromSql . fieldType -{- |-  Constructs the 'Expr.ColumnName' for a field for use in SQL expressions+{- | Constructs the 'Expr.ColumnName' for a field for use in SQL expressions   from the "Orville.PostgreSQL.Expr" module.  @since 1.0.0.0@@ -381,18 +386,19 @@ fieldColumnName =   fieldNameToColumnName . fieldName -{- |-  Constructs the 'Expr.ValueExpression' for a field for use in SQL expressions-  from the "Orville.PostgreSQL.Expr" module.+{- | Constructs the 'Expr.ValueExpression' for use in SQL expressions from the+  "Orville.PostgreSQL.Expr" module.  @since 1.0.0.0 -} fieldColumnReference :: FieldDefinition nullability a -> Expr.ValueExpression fieldColumnReference =-  Expr.columnReference . fieldColumnName+  Expr.columnReference+    . Expr.unqualified+    . fieldNameToColumnName+    . fieldName -{- |-  Constructs the equivalent 'Expr.FieldDefinition' as a SQL expression,+{- | Constructs the equivalent 'Expr.FieldDefinition' as a SQL expression,   generally for use in DDL for creating columns in a table.  @since 1.0.0.0@@ -400,29 +406,30 @@ fieldColumnDefinition :: FieldDefinition nullability a -> Expr.ColumnDefinition fieldColumnDefinition fieldDef =   Expr.columnDefinition-    (fieldColumnName fieldDef)+    (fieldNameToColumnName $ fieldName fieldDef)     (SqlType.sqlTypeExpr $ fieldType fieldDef)-    (Just $ fieldColumnConstraint fieldDef)+    (fieldColumnConstraints fieldDef)     (fmap (Expr.columnDefault . DefaultValue.defaultValueExpression) $ i_fieldDefaultValue fieldDef) -{- |-  INTERNAL - Builds the appropriate ColumnConstraint for a field. Currently-  this only handles nullability, but if we add support for more constraints-  directly on columns it may end up handling those as well.+{- | INTERNAL - Builds the appropriate [ColumnConstraint] for a field.  @since 1.0.0.0 -}-fieldColumnConstraint :: FieldDefinition nullabily a -> Expr.ColumnConstraint-fieldColumnConstraint fieldDef =+fieldColumnConstraints :: FieldDefinition nullabily a -> [Expr.ColumnConstraint]+fieldColumnConstraints fieldDef =   case fieldNullability fieldDef of-    NotNullField _ ->-      Expr.notNullConstraint+    NotNullField nnf ->+      case i_fieldIdentity nnf of+        IsIdentityGADT GeneratedAlways ->+          [Expr.notNullConstraint, Expr.identityColumnConstraint Expr.alwaysColumnIdentityGeneration]+        IsIdentityGADT GeneratedByDefault ->+          [Expr.notNullConstraint, Expr.identityColumnConstraint Expr.byDefaultColumnIdentityGeneration]+        AllowedIdentityButNotSetGADT ->+          pure Expr.notNullConstraint     NullableField _ ->-      Expr.nullConstraint--{- |+      pure Expr.nullConstraint -  The type in considered internal because it requires GADTs to make use of+{- | The type in considered internal because it requires GADTs to make use of   it meaningfully. The 'FieldNullability' type is used as the public interface   to surface this information to users outside the module. @@ -438,9 +445,7 @@   NullableGADT :: NullabilityGADT Nullable   NotNullGADT :: NullabilityGADT NotNull -{- |--  'NotNull' is a valueless type used to track that a 'FieldDefinition'+{- | 'NotNull' is a valueless type used to track that a 'FieldDefinition'   represents a field that is marked not-null in the database schema. See the   'FieldNullability' type for the value-level representation of field nullability. @@ -448,8 +453,7 @@ -} data NotNull -{- |-  'Nullable' is a valueless type used to track that a 'FieldDefinition'+{- | 'Nullable' is a valueless type used to track that a 'FieldDefinition'   represents a field that is marked nullable in the database schema. See the   'FieldNullability' type for the value-level representation of field nullability. @@ -457,8 +461,7 @@ -} data Nullable -{- |-  Builds a 'FieldDefinition' that stores Haskell 'Int32' values as the+{- | Builds a 'FieldDefinition' that stores Haskell 'Int32' values as the   PostgreSQL "INT" type.  @since 1.0.0.0@@ -469,8 +472,7 @@   FieldDefinition NotNull Int32 integerField = fieldOfType SqlType.integer -{- |-  Builds a 'FieldDefinition' that stores Haskell 'Int16' values as the+{- | Builds a 'FieldDefinition' that stores Haskell 'Int16' values as the   PostgreSQL "SMALLINT" type.  @since 1.0.0.0@@ -481,8 +483,7 @@   FieldDefinition NotNull Int16 smallIntegerField = fieldOfType SqlType.smallInteger -{- |-  Builds a 'FieldDefinition' that stores an 'Int32' value as the "SERIAL"+{- | Builds a 'FieldDefinition' that stores an 'Int32' value as the "SERIAL"   type. This can be used to create auto-incrementing columns.  @since 1.0.0.0@@ -493,8 +494,7 @@   FieldDefinition NotNull Int32 serialField = fieldOfType SqlType.serial -{- |-  Builds a 'FieldDefinition' that stores Haskell 'Int64' values as the+{- | Builds a 'FieldDefinition' that stores Haskell 'Int64' values as the   PostgreSQL "BIGINT" type.  @since 1.0.0.0@@ -505,8 +505,7 @@   FieldDefinition NotNull Int64 bigIntegerField = fieldOfType SqlType.bigInteger -{- |-  Builds a 'FieldDefinition' that stores an 'Int64' value as the "BIGSERIAL"+{- | Builds a 'FieldDefinition' that stores an 'Int64' value as the "BIGSERIAL"   type. This can be used to create auto-incrementing columns.  @since 1.0.0.0@@ -517,8 +516,7 @@   FieldDefinition NotNull Int64 bigSerialField = fieldOfType SqlType.bigSerial -{- |-  Builds a 'FieldDefinition' that stores a 'Double' value as the "DOUBLE+{- | Builds a 'FieldDefinition' that stores a 'Double' value as the "DOUBLE   PRECISION" type. Note: PostgreSQL's "DOUBLE PRECISION" type only allows for   up to 15 digits of precision, so some rounding may occur when values are   stored in the database.@@ -531,8 +529,7 @@   FieldDefinition NotNull Double doubleField = fieldOfType SqlType.double -{- |-  Builds a 'FieldDefinition' that stores Haskell 'Bool' values as the+{- | Builds a 'FieldDefinition' that stores Haskell 'Bool' values as the   PostgreSQL "BOOLEAN" type.  @since 1.0.0.0@@ -543,8 +540,7 @@   FieldDefinition NotNull Bool booleanField = fieldOfType SqlType.boolean -{- |-  Builds a 'FieldDefinition' that stores Haskell 'T.Text' values as the+{- | Builds a 'FieldDefinition' that stores Haskell 'T.Text' values as the   PostgreSQL "TEXT" type. Note that this PostgreSQL has no particular   limit on the length of text stored. @@ -556,8 +552,7 @@   FieldDefinition NotNull T.Text unboundedTextField = fieldOfType SqlType.unboundedText -{- |-  Builds a 'FieldDefinition' that stores Haskell 'T.Text' values as the+{- | Builds a 'FieldDefinition' that stores Haskell 'T.Text' values as the   PostgreSQL "VARCHAR" type. Attempting to store a value beyond the length   specified will cause an error. @@ -571,8 +566,7 @@   FieldDefinition NotNull T.Text boundedTextField name len = fieldOfType (SqlType.boundedText len) name -{- |-  Builds a 'FieldDefinition' that stores Haskell 'T.Text' values as the+{- | Builds a 'FieldDefinition' that stores Haskell 'T.Text' values as the   PostgreSQL "CHAR" type. Attempting to store a value beyond the length   specified will cause an error. Storing a value that is not the full   length of the field will result in padding by the database.@@ -587,8 +581,7 @@   FieldDefinition NotNull T.Text fixedTextField name len = fieldOfType (SqlType.fixedText len) name -{- |-  Builds a @FieldDefinition@ that stores PostgreSQL text search vector values.+{- | Builds a @FieldDefinition@ that stores PostgreSQL text search vector values.   The values are represented as Haskell 'T.Text' values, but are interpreted as   text search vector values by PostgreSQL when passed to it. @@ -600,8 +593,7 @@ textSearchVectorField :: String -> FieldDefinition NotNull T.Text textSearchVectorField = fieldOfType SqlType.textSearchVector -{- |-  Builds a 'FieldDefinition' that stores Haskell 'T.Text' values as the+{- | Builds a 'FieldDefinition' that stores Haskell 'T.Text' values as the   PostgreSQL "JSONB" type.  @since 1.0.0.0@@ -611,10 +603,13 @@   FieldDefinition NotNull T.Text jsonbField = fieldOfType SqlType.jsonb -{- |-  Builds a 'FieldDefinition' that stores Haskell 'Time.Day' values as the+{- | Builds a 'FieldDefinition' that stores Haskell 'Time.Day' values as the   PostgreSQL "DATE" type. +  This field cannot represent the full range of 'Time.Day' values. PostgreSQL supports years+  from -4731 to 5874897 inclusive for this field, and sending a 'Time.Day' with a year outside+  of this range to the database will result in a PostgreSQL exception.+ @since 1.0.0.0 -} dateField ::@@ -623,10 +618,13 @@   FieldDefinition NotNull Time.Day dateField = fieldOfType SqlType.date -{- |-  Builds a 'FieldDefinition' that stores Haskell 'Time.UTCTime' values as the+{- | Builds a 'FieldDefinition' that stores Haskell 'Time.UTCTime' values as the   PostgreSQL "TIMESTAMP with time zone" type. +  This field cannot represent the full range of 'Time.UTCTime' values. PostgreSQL supports years+  from -4731 to 294276 inclusive for this field, and sending a 'Time.UTCTime' with a year outside+  of this range to the database will result in a PostgreSQL exception.+ @since 1.0.0.0 -} utcTimestampField ::@@ -635,10 +633,13 @@   FieldDefinition NotNull Time.UTCTime utcTimestampField = fieldOfType SqlType.timestamp -{- |-  Builds a 'FieldDefinition' that stores Haskell 'Time.UTCTime' values as the+{- | Builds a 'FieldDefinition' that stores Haskell 'Time.UTCTime' values as the   PostgreSQL "TIMESTAMP without time zone" type. +  This field cannot represent the full range of 'Time.LocalTime' values. PostgreSQL supports years+  from -4731 to 294276 inclusive for this field, and sending a 'Time.LocalTime' with a year outside+  of this range to the database will result in a PostgreSQL exception.+ @since 1.0.0.0 -} localTimestampField ::@@ -647,8 +648,7 @@   FieldDefinition NotNull Time.LocalTime localTimestampField = fieldOfType SqlType.timestampWithoutZone -{- |-  Builds a 'FieldDefinition' that stores Haskell 'UUID.UUID' values as the+{- | Builds a 'FieldDefinition' that stores Haskell 'UUID.UUID' values as the   PostgreSQL "UUID" type.  @since 1.0.0.0@@ -659,8 +659,7 @@   FieldDefinition NotNull UUID.UUID uuidField = fieldOfType SqlType.uuid -{- |-  Builds a 'FieldDefinition' that will use the given 'SqlType.SqlType' to+{- | Builds a 'FieldDefinition' that will use the given 'SqlType.SqlType' to   determine the database representation of the field. If you have created a   custom 'SqlType.SqlType', you can use this function to construct a helper   like the other functions in this module for creating 'FieldDefinition's for@@ -682,13 +681,13 @@     , i_fieldDefaultValue = Nothing     , i_fieldDescription = Nothing     , i_fieldTableConstraints = mempty+    , i_fieldIdentity = AllowedIdentityButNotSetGADT     } -{- |-  Makes a 'NotNull' field 'Nullable' by wrapping the Haskell type of the field-  in 'Maybe'. The field will be marked as @NULL@ in the database schema and-  the value 'Nothing' will be used to represent @NULL@ values when converting-  to and from SQL.+{- | Makes a 'NotNull' field 'Nullable' by wrapping the Haskell type of the field in 'Maybe'. The+  field will be marked as @NULL@ in the database schema and the value 'Nothing' will be used to+  represent @NULL@ values when converting to and from SQL. If the field was previously an indentity+  column, that will be removed.  @since 1.0.0.0 -}@@ -713,10 +712,10 @@       , i_fieldDefaultValue = fmap DefaultValue.coerceDefaultValue (i_fieldDefaultValue field)       , i_fieldDescription = fieldDescription field       , i_fieldTableConstraints = i_fieldTableConstraints field+      , i_fieldIdentity = NotIdentityGADT       } -{- |-  Adds a 'Maybe' wrapper to a field that is already nullable. (If your field is+{- | Adds a 'Maybe' wrapper to a field that is already nullable. (If your field is   'NotNull', you wanted 'nullableField' instead of this function). Note that   fields created using this function have asymmetric encoding and decoding of   @NULL@ values. Because the provided field is 'Nullable', @NULL@ values decoded@@ -737,7 +736,7 @@     nullableType sqlType =       sqlType         { SqlType.sqlTypeToSql = maybe SqlValue.sqlNull (SqlType.sqlTypeToSql sqlType)-        , SqlType.sqlTypeFromSql = \sqlValue -> Just <$> SqlType.sqlTypeFromSql sqlType sqlValue+        , SqlType.sqlTypeFromSql = fmap Just . SqlType.sqlTypeFromSql sqlType         }   in     FieldDefinition@@ -747,10 +746,10 @@       , i_fieldDefaultValue = fmap DefaultValue.coerceDefaultValue (i_fieldDefaultValue field)       , i_fieldDescription = fieldDescription field       , i_fieldTableConstraints = i_fieldTableConstraints field+      , i_fieldIdentity = i_fieldIdentity field       } -{- |-  Applies a 'SqlType.SqlType' conversion to a 'FieldDefinition'. You can+{- | Applies a 'SqlType.SqlType' conversion to a 'FieldDefinition'. You can   use this function to create 'FieldDefinition's based on the primitive ones   provided, but with more specific Haskell types. @@ -769,23 +768,21 @@     , i_fieldDefaultValue = fmap DefaultValue.coerceDefaultValue (i_fieldDefaultValue fieldDef)     } -{- |-  A specialization of 'convertField' that can be used with types that implement+{- | A specialization of 'convertField' that can be used with types that implement   'Coerce.Coercible'. This is particularly useful for newtype wrappers around   primitive types.  @since 1.0.0.0 -} coerceField ::-  (Coerce.Coercible a b, Coerce.Coercible b a) =>+  Coerce.Coercible a b =>   FieldDefinition nullability a ->   FieldDefinition nullability b coerceField =   convertField     (SqlType.convertSqlType Coerce.coerce Coerce.coerce) -{- |-  Sets a default value for the field. The default value will be added as part+{- | Sets a default value for the field. The default value will be added as part   of the column definition in the database. Because the default value is   ultimately provided by the database, this can be used to add a not-null column   safely to an existing table as long as a reasonable default value is@@ -802,8 +799,7 @@     { i_fieldDefaultValue = Just defaultValue     } -{- |-  Removes any default value that may have been set on a field via+{- | Removes any default value that may have been set on a field via   @setDefaultValue@.  @since 1.0.0.0@@ -816,9 +812,28 @@     { i_fieldDefaultValue = Nothing     } -{- |-  Adds a prefix, followed by an underscore, to a field's name.+{- | Use the supplied options to mark a column as an identity column. +@since 1.1.0.0+-}+markAsIdentity :: FieldIdentityGeneration -> FieldDefinition NotNull a -> FieldDefinition NotNull a+markAsIdentity identityGen fieldDef =+  fieldDef+    { i_fieldIdentity = IsIdentityGADT identityGen+    }++{- | Remove the identity portion of a field. Note that if a field++@since 1.1.0.0+-}+unmarkIdentity :: FieldDefinition NotNull a -> FieldDefinition NotNull a+unmarkIdentity fieldDef =+  fieldDef+    { i_fieldIdentity = AllowedIdentityButNotSetGADT+    }++{- | Adds a prefix, followed by an underscore, to a field's name.+ @since 1.0.0.0 -} prefixField ::@@ -830,38 +845,33 @@     { i_fieldName = byteStringToFieldName (B8.pack prefix <> "_" <> fieldNameToByteString (fieldName fieldDef))     } -{- |-  Constructs a 'Expr.SetClause' that will set the column named in the+{- | Constructs a 'Expr.SetClause' that will set the column named in the   field definition to the given value. The value is converted to a SQL   value using 'fieldValueToSqlValue'.  @since 1.0.0.0 -} setField :: FieldDefinition nullability a -> a -> Expr.SetClause-setField fieldDef value =+setField fieldDef =   Expr.setColumn-    (fieldColumnName fieldDef)-    (fieldValueToSqlValue fieldDef value)+    (Expr.unqualified (fieldColumnName fieldDef))+    . fieldValueToSqlValue fieldDef -{- |-  Operator alias for 'setField'.+{- | Operator alias for 'setField'.  @since 1.0.0.0 -} (.:=) :: FieldDefinition nullability a -> a -> Expr.SetClause (.:=) = setField -{- |-  Checks that the value in a field equals a particular value.+{- | Checks that the value in a field equals a particular value.  @since 1.0.0.0 -} fieldEquals :: FieldDefinition nullability a -> a -> Expr.BooleanExpr-fieldEquals =-  whereColumnComparison Expr.equals+fieldEquals = SqlComparable.equals -{- |-  Operator alias for 'fieldEquals'.+{- | Operator alias for 'fieldEquals'.  @since 1.0.0.0 -}@@ -870,17 +880,14 @@  infixl 9 .== -{- |-  Checks that the value in a field does not equal a particular value.+{- | Checks that the value in a field does not equal a particular value.  @since 1.0.0.0 -} fieldNotEquals :: FieldDefinition nullability a -> a -> Expr.BooleanExpr-fieldNotEquals =-  whereColumnComparison Expr.notEquals+fieldNotEquals = SqlComparable.notEquals -{- |-  Operator alias for 'fieldNotEquals'.+{- | Operator alias for 'fieldNotEquals'.  @since 1.0.0.0 -}@@ -889,17 +896,28 @@  infixl 9 ./= -{- |-  Checks that the value in a field is greater than a particular value.+{- | Checks that the value in a field is distinct from a particular value. +@since 1.1.0.0+-}+fieldIsDistinctFrom :: FieldDefinition nullability a -> a -> Expr.BooleanExpr+fieldIsDistinctFrom = SqlComparable.isDistinctFrom++{- | Checks that the value in a field is not distinct from a particular value.++@since 1.1.0.0+-}+fieldIsNotDistinctFrom :: FieldDefinition nullability a -> a -> Expr.BooleanExpr+fieldIsNotDistinctFrom = SqlComparable.isNotDistinctFrom++{- | Checks that the value in a field is greater than a particular value.+ @since 1.0.0.0 -} fieldGreaterThan :: FieldDefinition nullability a -> a -> Expr.BooleanExpr-fieldGreaterThan =-  whereColumnComparison Expr.greaterThan+fieldGreaterThan = SqlComparable.greaterThan -{- |-  Operator alias for 'fieldGreaterThan'.+{- | Operator alias for 'fieldGreaterThan'.  @since 1.0.0.0 -}@@ -908,17 +926,14 @@  infixl 9 .> -{- |-  Checks that the value in a field is less than a particular value.+{- | Checks that the value in a field is less than a particular value.  @since 1.0.0.0 -} fieldLessThan :: FieldDefinition nullability a -> a -> Expr.BooleanExpr-fieldLessThan =-  whereColumnComparison Expr.lessThan+fieldLessThan = SqlComparable.lessThan -{- |-  Operator alias for 'fieldLessThan'.+{- | Operator alias for 'fieldLessThan'.  @since 1.0.0.0 -}@@ -927,17 +942,14 @@  infixl 9 .< -{- |-  Checks that the value in a field is greater than or equal to a particular value.+{- | Checks that the value in a field is greater than or equal to a particular value.  @since 1.0.0.0 -} fieldGreaterThanOrEqualTo :: FieldDefinition nullability a -> a -> Expr.BooleanExpr-fieldGreaterThanOrEqualTo =-  whereColumnComparison Expr.greaterThanOrEqualTo+fieldGreaterThanOrEqualTo = SqlComparable.greaterThanOrEqualTo -{- |-  Operator alias for 'fieldGreaterThanOrEqualTo'.+{- | Operator alias for 'fieldGreaterThanOrEqualTo'.  @since 1.0.0.0 -}@@ -946,17 +958,14 @@  infixl 9 .>= -{- |-  Checks that the value in a field is less than or equal to a particular value.+{- | Checks that the value in a field is less than or equal to a particular value.  @since 1.0.0.0 -} fieldLessThanOrEqualTo :: FieldDefinition nullability a -> a -> Expr.BooleanExpr-fieldLessThanOrEqualTo =-  whereColumnComparison Expr.lessThanOrEqualTo+fieldLessThanOrEqualTo = SqlComparable.lessThanOrEqualTo -{- |-  Operator alias for 'fieldLessThanOrEqualTo'.+{- | Operator alias for 'fieldLessThanOrEqualTo'.  @since 1.0.0.0 -}@@ -965,59 +974,42 @@  infixl 9 .<= -{- |-  Checks that the value in a field matches a like pattern.+{- | Checks that the value in a field matches a like pattern.  @since 1.0.0.0 -} fieldLike :: FieldDefinition nullability a -> T.Text -> Expr.BooleanExpr-fieldLike fieldDef likePattern =-  Expr.like-    (fieldColumnReference fieldDef)-    (Expr.valueExpression (SqlValue.fromText likePattern))+fieldLike = SqlComparable.like -{- |-  Checks that the value in a field matches a like pattern case insensitively.+{- | Checks that the value in a field matches a like pattern case insensitively.  @since 1.0.0.0 -} fieldLikeInsensitive :: FieldDefinition nullability a -> T.Text -> Expr.BooleanExpr-fieldLikeInsensitive fieldDef likePattern =-  Expr.likeInsensitive-    (fieldColumnReference fieldDef)-    (Expr.valueExpression (SqlValue.fromText likePattern))+fieldLikeInsensitive = SqlComparable.likeInsensitive -{- |-  Checks that the value in a field is null.+{- | Checks that the value in a field is null.  @since 1.0.0.0 -} fieldIsNull :: FieldDefinition Nullable a -> Expr.BooleanExpr-fieldIsNull =-  Expr.isNull . fieldColumnReference+fieldIsNull = SqlComparable.isNull -{- |-  Checks that the value in a field is not null.+{- | Checks that the value in a field is not null.  @since 1.0.0.0 -} fieldIsNotNull :: FieldDefinition Nullable a -> Expr.BooleanExpr-fieldIsNotNull =-  Expr.isNotNull . fieldColumnReference+fieldIsNotNull = SqlComparable.isNotNull -{- |-  Checks that a field matches a list of values.+{- | Checks that a field matches a list of values.  @since 1.0.0.0 -} fieldIn :: FieldDefinition nullability a -> NonEmpty a -> Expr.BooleanExpr-fieldIn fieldDef values =-  Expr.valueIn-    (fieldColumnReference fieldDef)-    (fmap (fieldValueToExpression fieldDef) values)+fieldIn = SqlComparable.isIn -{- |-  Operator alias for 'fieldIn'.+{- | Operator alias for 'fieldIn'.  @since 1.0.0.0 -}@@ -1026,19 +1018,14 @@  infixl 9 .<- -{- |-  Checks that a field does not match a list of values.+{- | Checks that a field does not match a list of values.  @since 1.0.0.0 -} fieldNotIn :: FieldDefinition nullability a -> NonEmpty a -> Expr.BooleanExpr-fieldNotIn fieldDef values =-  Expr.valueNotIn-    (fieldColumnReference fieldDef)-    (fmap (fieldValueToExpression fieldDef) values)+fieldNotIn = SqlComparable.isNotIn -{- |-  Operator alias for 'fieldNotIn'.+{- | Operator alias for 'fieldNotIn'.  @since 1.0.0.0 -}@@ -1047,8 +1034,7 @@  infixl 9 .</- -{- |-  Checks that a tuple of two fields is in the list of specified tuples.+{- | Checks that a tuple of two fields is in the list of specified tuples.  @since 1.0.0.0 -}@@ -1057,13 +1043,10 @@   FieldDefinition nullabilityB b ->   NonEmpty (a, b) ->   Expr.BooleanExpr-fieldTupleIn fieldDefA fieldDefB values =-  Expr.tupleIn-    (fieldColumnReference fieldDefA :| [fieldColumnReference fieldDefB])-    (fmap (toSqlValueTuple fieldDefA fieldDefB) values)+fieldTupleIn =+  SqlComparable.tupleIn -{- |-  Checks that a tuple of two fields is not in the list of specified tuples.+{- | Checks that a tuple of two fields is not in the list of specified tuples.  @since 1.0.0.0 -}@@ -1072,27 +1055,9 @@   FieldDefinition nullabilityB b ->   NonEmpty (a, b) ->   Expr.BooleanExpr-fieldTupleNotIn fieldDefA fieldDefB values =-  Expr.tupleNotIn-    (fieldColumnReference fieldDefA :| [fieldColumnReference fieldDefB])-    (fmap (toSqlValueTuple fieldDefA fieldDefB) values)--{- |-  Constructs a SqlValue "tuple" (i.e. NonEmpty list) for two fields.--@since 1.0.0.0--}-toSqlValueTuple ::-  FieldDefinition nullabilityA a ->-  FieldDefinition nullabilityB b ->-  (a, b) ->-  NonEmpty Expr.ValueExpression-toSqlValueTuple fieldDefA fieldDefB (a, b) =-  fieldValueToExpression fieldDefA a-    :| [fieldValueToExpression fieldDefB b]+fieldTupleNotIn = SqlComparable.tupleNotIn -{- |-  Constructs a field-based 'Expr.BooleanExpr' using a function that+{- | Constructs a field-based 'Expr.BooleanExpr' using a function that   builds a 'Expr.BooleanExpr'.  @since 1.0.0.0@@ -1100,17 +1065,143 @@ whereColumnComparison ::   (Expr.ValueExpression -> Expr.ValueExpression -> Expr.BooleanExpr) ->   (FieldDefinition nullability a -> a -> Expr.BooleanExpr)-whereColumnComparison columnComparison fieldDef a =+whereColumnComparison columnComparison fieldDef =   columnComparison-    (fieldColumnReference fieldDef)-    (fieldValueToExpression fieldDef a)+    (SqlComparable.referenceValueExpression fieldDef)+    . fieldValueToExpression fieldDef -{-- |-  Orders a query by the column name for the given field.---}+{- | Orders a query by the column name for the given field. This is a type-specific+alias for 'SqlComparable.orderBySqlComparable'++@since 1.0.0.0+-} orderByField ::   FieldDefinition nullability value ->   Expr.OrderByDirection ->   Expr.OrderByExpr orderByField =-  Expr.orderByColumnName . fieldColumnName+  SqlComparable.orderBySqlComparable++{- | A type class that allows different types to be used as qualifiers for column names.+This is often used in conjuction with 'QualifiedFieldDefinition' to represent a+'FieldDefinition' whose reference needs to be qualified in a SQL query involving+multiple tables.++Instances are provided for table 'TableIdentifier.TableIdentifier' and+'AliasName.AliasName' so that either can be used a qualifiers for fields.++@since 1.1.0.0+-}+class FieldQualifier qualifier where+  qualifyColumnName :: qualifier -> Expr.ColumnName -> Expr.Qualified Expr.ColumnName++{- |++@since 1.1.0.0+-}+instance FieldQualifier TableIdentifier.TableIdentifier where+  qualifyColumnName tableId =+    Expr.qualifyColumn+      (TableIdentifier.tableIdSchemaName tableId)+      (TableIdentifier.tableIdUnqualifiedName tableId)++{- |++@since 1.1.0.0+-}+instance FieldQualifier AliasName.AliasName where+  qualifyColumnName =+    Expr.aliasQualifyColumn . AliasName.aliasNameToAliasExpr++{- | A 'FieldDefinition' paired with a 'qualifier' so SQL references to the field+will be qualified. A 'SqlComparable.SqlComparable' instance is provided so that+'QualifiedFieldDefinition' can be used with the SQL comparison and ordering+functions offered by 'Sql.Comparable.SqlComparable'.++See Also: 'qualifyField', 'FieldQualifier'++@since 1.1.0.0+-}+data QualifiedFieldDefinition qualifier nullability a = QualifiedFieldDefinition+  { i_fieldQualifier :: qualifier+  , i_unqualifiedDefinition :: FieldDefinition nullability a+  }++{- | Constructs a 'QualifiedFieldDefinition' that will qualify the field with+the specified qualifier. Generally speaking, the 'qualifier' will need to+provide an instance of 'FieldQualifier' for the constructed 'QualifiedFieldDefinition'+to be of use.++@since 1.1.0.0+-}+qualifyField ::+  qualifier ->+  FieldDefinition nullability a ->+  QualifiedFieldDefinition qualifier nullability a+qualifyField =+  QualifiedFieldDefinition++{- | Generates the SQL expression to refer to the column represented by a+'QualifiedFieldDefinition'. The column will be qualified using the 'qualifier'+that was passed to 'qualifyField'.++@since 1.1.0.0+-}+qualifiedFieldColumnName ::+  FieldQualifier qualifier =>+  QualifiedFieldDefinition qualifier nullability a ->+  Expr.Qualified Expr.ColumnName+qualifiedFieldColumnName qualifiedFieldDef =+  qualifyColumnName+    (i_fieldQualifier qualifiedFieldDef)+    (fieldColumnName (i_unqualifiedDefinition qualifiedFieldDef))++{- |++@since 1.1.0.0+-}+instance FieldQualifier qualifier => SqlComparable.SqlComparable (QualifiedFieldDefinition qualifier nullability a) a where+  toComparableSqlValue =+    toComparableSqlValue . i_unqualifiedDefinition++  referenceValueExpression =+    Expr.columnReference . Expr.untrackQualified . qualifiedFieldColumnName++{- | INTERNAL: This type is an internal tracking of if a column is an identity column. We tie this to+   the nullability because a nullable column is not allowed to be an identity column.++@since 1.1.0.0+-}+data IdentityGADT nullability where+  IsIdentityGADT :: FieldIdentityGeneration -> IdentityGADT NotNull+  AllowedIdentityButNotSetGADT :: IdentityGADT NotNull+  NotIdentityGADT :: IdentityGADT Nullable++{- | Get the 'FieldIdentityGeneration', if there is one, of a 'FieldDefinition'.++@since 1.1.0.0+-}+fieldIdentityGeneration :: FieldDefinition nullability a -> Maybe FieldIdentityGeneration+fieldIdentityGeneration fieldDef =+  case i_fieldIdentity fieldDef of+    AllowedIdentityButNotSetGADT -> Nothing+    NotIdentityGADT -> Nothing+    IsIdentityGADT colId -> Just colId++{- | Represents how the identity field be will generated.++@since 1.1.0.0+-}+data FieldIdentityGeneration+  = -- | The field will always be generated, and user supplied values during write for it is expressly not allowed.+    --+    -- @since 1.1.0.0+    GeneratedAlways+  | -- | The field will be generated by default, allowing for user defined values for writes will be allowed.+    --+    -- @since 1.1.0.0+    GeneratedByDefault+  deriving+    ( -- | @since 1.1.0.0+      Eq+    )
src/Orville/PostgreSQL/Marshall/MarshallError.hs view
@@ -1,5 +1,5 @@ {- |-Copyright : Flipstone Technology Partners 2023+Copyright : Flipstone Technology Partners 2023-2025 License   : MIT Stability : Stable @@ -20,14 +20,14 @@ import Control.Exception (Exception) import qualified Data.ByteString.Char8 as B8 import qualified Data.List as List+import qualified Data.List.NonEmpty as NE import qualified Data.Set as Set  import Orville.PostgreSQL.ErrorDetailLevel (ErrorDetailLevel, redactErrorMessage, redactIdentifierValue, redactNonIdentifierValue, redactSchemaName) import qualified Orville.PostgreSQL.Raw.PgTextFormatValue as PgTextFormatValue import qualified Orville.PostgreSQL.Raw.SqlValue as SqlValue -{- |-  A 'MarshallError' may be returned from+{- | A 'MarshallError' may be returned from   'Orville.PostgreSQL.Marshall.marshallResultFromSql' when a row being decoded   from the database doesn't meet the expectations of the   'Orville.PostgreSQL.Marshall.SqlMarshaller' that is decoding it.@@ -55,8 +55,7 @@  instance Exception MarshallError -{- |-  Renders a 'MarshallError' to a string using the specified 'ErrorDetailLevel'.+{- | Renders a 'MarshallError' to a string using the specified 'ErrorDetailLevel'.    This ignores any 'ErrorDetailLevel' that was captured by default from   the Orville context and uses the specified level of detail instead.@@ -74,7 +73,7 @@ renderMarshallError detailLevel marshallError =   let     presentableRowId =-      map+      fmap         (presentSqlColumnValue detailLevel redactIdentifierValue)         (marshallErrorRowIdentifier marshallError)   in@@ -85,8 +84,7 @@       , renderMarshallErrorDetails detailLevel (marshallErrorDetails marshallError)       ] -{- |-  A internal helper to present a redacted column name and sql value in an error+{- | A internal helper to present a redacted column name and sql value in an error   message. The redacter function is passed as an argument here so that this   function can be used to present either ID values or general values as   required by the context of the caller.@@ -102,18 +100,17 @@   let     sqlValueString =       redacter detailLevel $-        case SqlValue.toPgValue sqlValue of-          Nothing ->-            "NULL"-          Just pgValue ->-            B8.unpack . PgTextFormatValue.toByteString $ pgValue+        SqlValue.foldSqlValue+          (B8.unpack . PgTextFormatValue.toByteString)+          (\vals -> "(" <> List.intercalate ", " (NE.toList vals) <> ")")+          "NULL"+          sqlValue   in     redactSchemaName detailLevel (B8.unpack columnName)       <> " = "       <> sqlValueString -{- |-  A 'MarshallErrorDetails' may be returned from+{- | A 'MarshallErrorDetails' may be returned from   'Orville.PostgreSQL.Marshall.marshallResultFromSql' if the result set being   decoded from the database doesn't meet the expectations of the   'Orville.PostgreSQL.Marshall.SqlMarshaller' that is decoding it.@@ -127,8 +124,7 @@   | -- | Indicates that an expected column was not found in the result set.     MissingColumnError MissingColumnErrorDetails -{- |-  Renders a 'MarshallErrorDetails' to a 'String' with a specified+{- | Renders a 'MarshallErrorDetails' to a 'String' with a specified   'ErrorDetailLevel'.  @since 1.0.0.0@@ -139,8 +135,7 @@     DecodingError details -> renderDecodingErrorDetails detailLevel details     MissingColumnError details -> renderMissingColumnErrorDetails detailLevel details -{- |-  Details about an error that occurred while decoding values found in a SQL+{- | Details about an error that occurred while decoding values found in a SQL   result set.  @since 1.0.0.0@@ -150,8 +145,7 @@   , decodingErrorMessage :: String   } -{- |-  Renders a 'DecodingErrorDetails' to a 'String' with a specified+{- | Renders a 'DecodingErrorDetails' to a 'String' with a specified   'ErrorDetailLevel'.  @since 1.0.0.0@@ -160,7 +154,7 @@ renderDecodingErrorDetails detailLevel details =   let     presentableErrorValues =-      map+      fmap         (presentSqlColumnValue detailLevel redactNonIdentifierValue)         (decodingErrorValues details)   in@@ -172,19 +166,17 @@       , "]"       ] -{- |-  Details about a column that was found to be missing in a SQL result set+{- | Details about a column that was found to be missing in a SQL result set   during decoding.  @since 1.0.0.0 -} data MissingColumnErrorDetails = MissingColumnErrorDetails   { missingColumnName :: B8.ByteString-  , actualColumnNames :: (Set.Set B8.ByteString)+  , actualColumnNames :: Set.Set B8.ByteString   } -{- |-  Renders a 'MissingColumnErrorDetails' to a 'String' with a specified+{- | Renders a 'MissingColumnErrorDetails' to a 'String' with a specified   'ErrorDetailLevel'.  @since 1.0.0.0@@ -193,7 +185,7 @@ renderMissingColumnErrorDetails detailLevel details =   let     presentableActualNames =-      map+      fmap         (redactSchemaName detailLevel . B8.unpack)         (Set.toList $ actualColumnNames details)   in
+ src/Orville/PostgreSQL/Marshall/SqlComparable.hs view
@@ -0,0 +1,305 @@+{-# LANGUAGE FunctionalDependencies #-}++{- |+Copyright : Flipstone Technology Partners 2024-2025+License   : MIT+Stability : Stable++@since 1.1.0.0+-}+module Orville.PostgreSQL.Marshall.SqlComparable+  ( SqlComparable (toComparableSqlValue, referenceValueExpression)+  , toSqlValueTuple+  , equals+  , notEquals+  , isDistinctFrom+  , isNotDistinctFrom+  , lessThan+  , lessThanOrEqualTo+  , greaterThan+  , greaterThanOrEqualTo+  , isIn+  , isNotIn+  , isNull+  , isNotNull+  , like+  , likeInsensitive+  , tupleIn+  , tupleNotIn+  , orderBySqlComparable+  ) where++import Data.List.NonEmpty (NonEmpty ((:|)))+import qualified Data.Text as T++import qualified Orville.PostgreSQL.Expr as Expr+import qualified Orville.PostgreSQL.Raw.SqlValue as SqlValue++{- | This provides a common interface to being able to produce a 'SqlValue.SqlValue' as well as having+a way to reference the item in SQL. This can be something like a field of a table, a field that+should be referenced via an alias, or a subquery.++Here the first argument to the class should be some abstraction over some part of SQL that produces+a Haskell value. With the type of the Haskell value as the second argument.++@since 1.1.0.0+-}+class SqlComparable sqlAbstraction haskellValue | sqlAbstraction -> haskellValue where+  -- | Uses the first argument to marshall a Haskell value its 'SqlValue.SqlValue' representation.+  --+  -- @since 1.1.0.0+  toComparableSqlValue :: sqlAbstraction -> haskellValue -> SqlValue.SqlValue++  -- | Constructs the 'Expr.ValueExpression' for use in SQL expressions from the+  -- "Orville.PostgreSQL.Expr" module.+  --+  -- @since 1.1.0.0+  referenceValueExpression :: sqlAbstraction -> Expr.ValueExpression++{- | Constructs a SqlValue "tuple" (i.e. NonEmpty list) for two items that can themselves be made to 'SqlValue.SqlValue'.++@since 1.1.0.0+-}+toSqlValueTuple ::+  (SqlComparable a c, SqlComparable b d) =>+  a ->+  b ->+  (c, d) ->+  NonEmpty Expr.ValueExpression+toSqlValueTuple a b (c, d) =+  Expr.valueExpression (toComparableSqlValue a c)+    :| [Expr.valueExpression $ toComparableSqlValue b d]++{- | Checks that the referenced item from the first argument does not equal the SQL value of the+  second argument. This is commonly used in a @WHERE@ clause such as @WHERE foo = 1@. Frequently you+  would not want to use this directly, but instead use+  'Orville.PostgreSQL.Marshall.FieldDefinition.fieldEquals'.++@since 1.1.0.0+-}+equals :: SqlComparable a b => a -> b -> Expr.BooleanExpr+equals a =+  Expr.equals+    (referenceValueExpression a)+    . Expr.valueExpression+    . toComparableSqlValue a++{- | Checks that the referenced item from the first argument does not equal the SQL value of the+  second argument. This is commonly used with columns as you might in a @WHERE@ clause such as+  @WHERE foo != 1@. Frequently you would not want to use this directly, but instead use+  'Orville.PostgreSQL.Marshall.FieldDefinition.fieldNotEquals'.++@since 1.1.0.0+-}+notEquals :: SqlComparable a b => a -> b -> Expr.BooleanExpr+notEquals a =+  Expr.notEquals+    (referenceValueExpression a)+    . Expr.valueExpression+    . toComparableSqlValue a++{- | Checks that the referenced item from the first argument is distinct from the SQL value of the+  second argument. This is commonly used with columns as you might in a @WHERE@ clause such as+  @WHERE foo IS DISTINCT FROM 1@. Frequently you would not want to use this directly, but instead use+  'Orville.PostgreSQL.Marshall.FieldDefinition.fieldIsDistinctFrom'.++@since 1.1.0.0+-}+isDistinctFrom :: SqlComparable a b => a -> b -> Expr.BooleanExpr+isDistinctFrom a =+  Expr.isDistinctFrom+    (referenceValueExpression a)+    . Expr.valueExpression+    . toComparableSqlValue a++{- | Checks that the referenced item from the first argument is not distinct from the SQL value of the+  second argument. This is commonly used with columns as you might in a @WHERE@ clause such as+  @WHERE foo IS NOT DISTINCT FROM 1@. Frequently you would not want to use this directly, but instead+  use 'Orville.PostgreSQL.Marshall.FieldDefinition.fieldIsNotDistinctFrom'.++@since 1.1.0.0+-}+isNotDistinctFrom :: SqlComparable a b => a -> b -> Expr.BooleanExpr+isNotDistinctFrom a =+  Expr.isNotDistinctFrom+    (referenceValueExpression a)+    . Expr.valueExpression+    . toComparableSqlValue a++{- | Checks that the referenced item from the first argument is less than the SQL value of the+  second argument. This is commonly used with columns as you might in a @WHERE@ clause such as+  @WHERE foo < 1@. Frequently you would not want to use this directly, but instead+  use 'Orville.PostgreSQL.Marshall.FieldDefinition.fieldLessThan'.++@since 1.1.0.0+-}+lessThan :: SqlComparable a b => a -> b -> Expr.BooleanExpr+lessThan a =+  Expr.lessThan+    (referenceValueExpression a)+    . Expr.valueExpression+    . toComparableSqlValue a++{- | Checks that the referenced item from the first argument is less than or equal to the SQL value of+  the second argument. This is commonly used with columns as you might in a @WHERE@ clause such as+  @WHERE foo <= 1@. Frequently you would not want to use this directly, but instead use+  'Orville.PostgreSQL.Marshall.FieldDefinition.fieldLessThanOrEqualTo'.++@since 1.1.0.0+-}+lessThanOrEqualTo :: SqlComparable a b => a -> b -> Expr.BooleanExpr+lessThanOrEqualTo a =+  Expr.lessThanOrEqualTo+    (referenceValueExpression a)+    . Expr.valueExpression+    . toComparableSqlValue a++{- | Checks that the referenced item from the first argument is greater than the SQL value of the+  second argument. This is commonly used with columns as you might in a @WHERE@ clause such as+  @WHERE foo > 1@. Frequently you would not want to use this directly, but instead use+  'Orville.PostgreSQL.Marshall.FieldDefinition.fieldGreaterThan'.++@since 1.1.0.0+-}+greaterThan :: SqlComparable a b => a -> b -> Expr.BooleanExpr+greaterThan a =+  Expr.greaterThan+    (referenceValueExpression a)+    . Expr.valueExpression+    . toComparableSqlValue a++{- | Checks that the referenced item from the first argument is greater than or equal to the SQL value+  of the second argument. This is commonly used with columns as you might in a @WHERE@ clause such+  as @WHERE foo >= 1@. Frequently you would not want to use this directly, but instead use+  'Orville.PostgreSQL.Marshall.FieldDefinition.fieldGreaterThanOrEqualTo'.++@since 1.1.0.0+-}+greaterThanOrEqualTo :: SqlComparable a b => a -> b -> Expr.BooleanExpr+greaterThanOrEqualTo a =+  Expr.greaterThanOrEqualTo+    (referenceValueExpression a)+    . Expr.valueExpression+    . toComparableSqlValue a++{- | Checks that the referenced item from the first argument is in the list of the SQL values+  of the second argument. This is commonly used with columns as you might in a @WHERE@ clause such+  as @WHERE foo IN (1,2)@. Frequently you would not want to use this directly, but instead use+  'Orville.PostgreSQL.Marshall.FieldDefinition.fieldIn'.++@since 1.1.0.0+-}+isIn :: SqlComparable a b => a -> NonEmpty b -> Expr.BooleanExpr+isIn a =+  Expr.valueIn+    (referenceValueExpression a)+    . fmap (Expr.valueExpression . toComparableSqlValue a)++{- | Checks that the referenced item from the first argument is not in the list of the SQL values+  of the second argument. This is commonly used with columns as you might in a @WHERE@ clause such+  as @WHERE foo NOT IN (1,2)@. Frequently you would not want to use this directly, but instead use+  'Orville.PostgreSQL.Marshall.FieldDefinition.fieldNotIn'.++@since 1.1.0.0+-}+isNotIn :: SqlComparable a b => a -> NonEmpty b -> Expr.BooleanExpr+isNotIn a =+  Expr.valueNotIn+    (referenceValueExpression a)+    . fmap (Expr.valueExpression . toComparableSqlValue a)++{- | Checks that the referenced item is null. This is commonly used with columns as you might in a+  @WHERE@ clause such as @WHERE foo IS NULL@. Frequently you would not want to use this directly,+  but instead use 'Orville.PostgreSQL.Marshall.FieldDefinition.fieldIsNull'.++@since 1.1.0.0+-}+isNull :: SqlComparable a b => a -> Expr.BooleanExpr+isNull =+  Expr.isNull . referenceValueExpression++{- | Checks that the referenced item is not null. This is commonly used with columns as you might in a+  @WHERE@ clause such as @WHERE foo IS NOT NULL@. Frequently you would not want to use this+  directly, but instead use 'Orville.PostgreSQL.Marshall.FieldDefinition.fieldIsNotNull'.++@since 1.1.0.0+-}+isNotNull :: SqlComparable a b => a -> Expr.BooleanExpr+isNotNull =+  Expr.isNotNull . referenceValueExpression++{- | Checks that the referenced item matches the given like pattern case sensitively. This is+  commonly used with columns as you might in a @WHERE@ clause such as @WHERE foo LIKE+  'bar'@. Frequently you would not want to use this directly, but instead use+  'Orville.PostgreSQL.Marshall.FieldDefinition.fieldLike'.++@since 1.1.0.0+-}+like :: SqlComparable a b => a -> T.Text -> Expr.BooleanExpr+like a =+  Expr.like+    (referenceValueExpression a)+    . Expr.valueExpression+    . SqlValue.fromText++{- | Checks that the referenced item matches the given like pattern case insensitively. This is+  commonly used with columns as you might in a @WHERE@ clause such as @WHERE foo ILIKE+  'bar'@. Frequently you would not want to use this directly, but instead use+  'Orville.PostgreSQL.Marshall.FieldDefinition.fieldLikeInsensitive'.++@since 1.1.0.0+-}+likeInsensitive :: SqlComparable a b => a -> T.Text -> Expr.BooleanExpr+likeInsensitive a =+  Expr.likeInsensitive+    (referenceValueExpression a)+    . Expr.valueExpression+    . SqlValue.fromText++{- | Checks that the referenced items from the first two arguments are in the SQL values of the+  given list of tuples. This is commonly used with columns as you might in a @WHERE@ clause such as+  @WHERE (foo, bar) IN ((1,2), (3,4))@. Frequently you would not want to use this directly, but+  instead use 'Orville.PostgreSQL.Marshall.FieldDefinition.fieldTupleIn'.++@since 1.1.0.0+-}+tupleIn ::+  (SqlComparable a c, SqlComparable b d) =>+  a ->+  b ->+  NonEmpty (c, d) ->+  Expr.BooleanExpr+tupleIn a b =+  Expr.tupleIn+    (referenceValueExpression a :| [referenceValueExpression b])+    . fmap (toSqlValueTuple a b)++{- | Checks that the referenced items from the first two arguments are not in the SQL values of the+  given list of tuples. This is commonly used with columns as you might in a @WHERE@ clause such as+  @WHERE (foo, bar) IN ((1,2), (3,4))@. Frequently you would not want to use this directly, but+  instead use 'Orville.PostgreSQL.Marshall.FieldDefinition.fieldTupleNotIn'.++@since 1.1.0.0+-}+tupleNotIn ::+  (SqlComparable a c, SqlComparable b d) =>+  a ->+  b ->+  NonEmpty (c, d) ->+  Expr.BooleanExpr+tupleNotIn a b =+  Expr.tupleNotIn+    (referenceValueExpression a :| [referenceValueExpression b])+    . fmap (toSqlValueTuple a b)++{- | Orders a query by the 'referenceValueExpression' given by the 'comparable'.++@since 1.1.0.0+-}+orderBySqlComparable ::+  SqlComparable comparable value =>+  comparable ->+  Expr.OrderByDirection ->+  Expr.OrderByExpr+orderBySqlComparable =+  Expr.orderByValueExpression . referenceValueExpression
src/Orville/PostgreSQL/Marshall/SqlMarshaller.hs view
@@ -1,8 +1,10 @@+{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-}+{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE RankNTypes #-}  {- |-Copyright : Flipstone Technology Partners 2023+Copyright : Flipstone Technology Partners 2023-2025 License   : MIT Stability : Stable @@ -35,6 +37,7 @@   , marshallNested   , marshallMaybe   , marshallPartial+  , marshallQualifyFields   , prefixMarshaller   , ReadOnlyColumnOption (IncludeReadOnlyColumns, ExcludeReadOnlyColumns)   , collectFromField@@ -48,11 +51,22 @@   , applyRowSource   , constRowSource   , failRowSource+  , marshallerEquals+  , marshallerNotEquals+  , marshallerIsDistinctFrom+  , marshallerIsNotDistinctFrom+  , marshallerLessThan+  , marshallerLessThanOrEqualTo+  , marshallerGreaterThan+  , marshallerGreaterThanOrEqualTo+  , marshallerIn+  , marshallerNotIn   ) where  import Control.Monad (join) import qualified Data.ByteString.Char8 as B8+import qualified Data.List.NonEmpty as NE import qualified Data.Map.Strict as Map import Data.Maybe (catMaybes) import qualified Data.Set as Set@@ -61,14 +75,15 @@ import Orville.PostgreSQL.Execution.ExecutionResult (Column (Column), ExecutionResult, Row (Row)) import qualified Orville.PostgreSQL.Execution.ExecutionResult as Result import qualified Orville.PostgreSQL.Expr as Expr-import Orville.PostgreSQL.Marshall.FieldDefinition (FieldDefinition, FieldName, FieldNullability (NotNullField, NullableField), asymmetricNullableField, fieldColumnName, fieldName, fieldNameToByteString, fieldNameToColumnName, fieldNullability, fieldTableConstraints, fieldValueFromSqlValue, nullableField, prefixField, setField)+import Orville.PostgreSQL.Marshall.FieldDefinition (FieldDefinition, FieldName, FieldNullability (NotNullField, NullableField), FieldQualifier, asymmetricNullableField, fieldColumnName, fieldName, fieldNameToByteString, fieldNameToColumnName, fieldNullability, fieldTableConstraints, fieldValueFromSqlValue, nullableField, prefixField, qualifiedFieldColumnName, qualifyField, setField) import qualified Orville.PostgreSQL.Marshall.MarshallError as MarshallError-import Orville.PostgreSQL.Marshall.SyntheticField (SyntheticField, nullableSyntheticField, prefixSyntheticField, syntheticFieldAlias, syntheticFieldExpression, syntheticFieldValueFromSqlValue)+import qualified Orville.PostgreSQL.Marshall.SqlComparable as SqlComparable+import Orville.PostgreSQL.Marshall.SyntheticField (SyntheticField, nullableSyntheticField, prefixSyntheticField, syntheticFieldExpression, syntheticFieldName, syntheticFieldValueFromSqlValue) import qualified Orville.PostgreSQL.Raw.SqlValue as SqlValue import qualified Orville.PostgreSQL.Schema.ConstraintDefinition as ConstraintDefinition+import qualified Orville.PostgreSQL.Schema.TableIdentifier as TableIdentifier -{- |-  An 'AnnotatedSqlMarshaller' is a 'SqlMarshaller' that contains extra+{- | An 'AnnotatedSqlMarshaller' is a 'SqlMarshaller' that contains extra   annotations which cannot necessarily be determined from the data in the   marshaller itself. In particular, it includes the names of fields that can be   used to identify a row in the database when an error is encountered during@@ -88,8 +103,7 @@   , unannotatedSqlMarshaller :: SqlMarshaller writeEntity readEntity   } -{- |-  Creates an 'AnnotatedSqlMarshaller' that will use the given column names+{- | Creates an 'AnnotatedSqlMarshaller' that will use the given column names   to identify rows in error messages when decoding fails. Any column names   in the list that are not present in the result set will simply be omitted   from the error message.@@ -103,8 +117,7 @@ annotateSqlMarshaller =   AnnotatedSqlMarshaller -{- |-  Creates an 'AnnotatedSqlMarshaller' that will identify rows in decoding+{- | Creates an 'AnnotatedSqlMarshaller' that will identify rows in decoding   errors by any columns. This is the equivalent of @annotateSqlMarshaller []@.  @since 1.0.0.0@@ -115,8 +128,7 @@ annotateSqlMarshallerEmptyAnnotation =   annotateSqlMarshaller [] -{- |-  Applies the provided function to a 'SqlMarshaller' that has been annotated,+{- | Applies the provided function to a 'SqlMarshaller' that has been annotated,   preserving the annotations.  @since 1.0.0.0@@ -128,8 +140,7 @@ mapSqlMarshaller f (AnnotatedSqlMarshaller rowIdFields marshaller) =   AnnotatedSqlMarshaller rowIdFields (f marshaller) -{- |-  'SqlMarshaller' is how we group the lowest-level translation of single fields+{- | 'SqlMarshaller' is how we group the lowest-level translation of single fields   into a higher-level marshalling of full SQL records into Haskell records.   This is a flexible abstraction that allows us to ultimately model SQL tables   and work with them as potentially nested Haskell records. We can then@@ -156,16 +167,64 @@   MarshallPartial :: SqlMarshaller a (Either String b) -> SqlMarshaller a b   -- | Marshall a column that is read-only, like auto-incrementing ids.   MarshallReadOnly :: SqlMarshaller a b -> SqlMarshaller c b+  -- | Apply an qualifier to all the fields of a marshaller+  MarshallQualifyFields ::+    FieldQualifier qualifier =>+    qualifier ->+    SqlMarshaller a b ->+    SqlMarshaller a b +-- | @since 1.0.0.0 instance Functor (SqlMarshaller a) where-  fmap f marsh = MarshallApply (MarshallPure f) marsh+  fmap f = MarshallApply (MarshallPure f) +-- | @since 1.0.0.0 instance Applicative (SqlMarshaller a) where   pure = MarshallPure   (<*>) = MarshallApply -{- |-  Returns a list of 'Expr.DerivedColumn' expressions that can be used in a+{- | Marshallers with no natural columns will result in @null@ 'SqlValue.SqlValue' and+  'Expr.ValueExpression' as the outputs of 'SqlComparable.toComparableSqlValue' and+  'SqlComparable.referenceValueExpression', respectively.++  Both methods exclude columns marshalled with 'marshallReadOnly'.++@since 1.1.0.0+-}+instance SqlComparable.SqlComparable (SqlMarshaller writeEntity readEntity) writeEntity where+  toComparableSqlValue marshaller writeEntity =+    let+      collectSqlVals marshallerField vals =+        case marshallerField of+          Natural _ fieldDef (Just getField) ->+            SqlComparable.toComparableSqlValue fieldDef (getField writeEntity) : vals+          _ -> vals+    in+      maybe+        SqlValue.sqlNull+        SqlValue.fromRow+        (NE.nonEmpty $ foldMarshallerFields marshaller [] collectSqlVals)++  referenceValueExpression marshaller =+    let+      colValExprs =+        foldMarshallerFields marshaller [] $+          collectFromField+            ExcludeReadOnlyColumns+            ( \mbQualifier fieldDef ->+                case mbQualifier of+                  Nothing -> SqlComparable.referenceValueExpression fieldDef+                  Just qualifier ->+                    SqlComparable.referenceValueExpression $+                      qualifyField qualifier fieldDef+            )+    in+      maybe+        (Expr.valueExpression SqlValue.sqlNull)+        Expr.rowValueConstructor+        (NE.nonEmpty colValExprs)++{- | Returns a list of 'Expr.DerivedColumn' expressions that can be used in a   select statement to select values from the database for the 'SqlMarshaller'   decode. @@ -182,19 +241,27 @@       [Expr.DerivedColumn]     collectDerivedColumn entry columns =       case entry of-        Natural fieldDef _ ->-          (Expr.deriveColumn . Expr.columnReference . fieldColumnName $ fieldDef)-            : columns+        Natural mbQualifier fieldDef _ ->+          let+            colName =+              case mbQualifier of+                Nothing -> Expr.unqualified (fieldColumnName fieldDef)+                Just qualifier ->+                  Expr.untrackQualified+                    . qualifiedFieldColumnName+                    . qualifyField qualifier+                    $ fieldDef+          in+            Expr.deriveColumn (Expr.columnReference colName) : columns         Synthetic synthField ->           Expr.deriveColumnAs             (syntheticFieldExpression synthField)-            (fieldNameToColumnName $ syntheticFieldAlias synthField)+            (fieldNameToColumnName . syntheticFieldName $ synthField)             : columns   in     foldMarshallerFields marshaller [] collectDerivedColumn -{- |-  Returns the table constraints for all the 'FieldDefinition's used in the+{- | Returns the table constraints for all the 'FieldDefinition's used in the   'SqlMarshaller'.  @since 1.0.0.0@@ -210,7 +277,7 @@       ConstraintDefinition.TableConstraints     collectTableConstraints entry constraints =       case entry of-        Natural fieldDef _ -> constraints <> fieldTableConstraints fieldDef+        Natural _ fieldDef _ -> constraints <> fieldTableConstraints fieldDef         Synthetic _synthField -> constraints   in     foldMarshallerFields@@ -218,19 +285,17 @@       ConstraintDefinition.emptyTableConstraints       collectTableConstraints -{- |-  Represents a primitive entry in a 'SqlMarshaller'. This type is used with+{- | Represents a primitive entry in a 'SqlMarshaller'. This type is used with   'foldMarshallerFields' to provided the entry from the marshaller to the   folding function to be incorporated in the result of the fold.  @since 1.0.0.0 -} data MarshallerField writeEntity where-  Natural :: FieldDefinition nullability a -> Maybe (writeEntity -> a) -> MarshallerField writeEntity+  Natural :: FieldQualifier qualifier => Maybe qualifier -> FieldDefinition nullability a -> Maybe (writeEntity -> a) -> MarshallerField writeEntity   Synthetic :: SyntheticField a -> MarshallerField writeEntity -{- |-  A fold function that can be used with 'foldMarshallerFields' to collect+{- | A fold function that can be used with 'foldMarshallerFields' to collect   a value calculated from a 'FieldDefinition' via the given function. The calculated   value is added to the list of values being built. @@ -242,23 +307,22 @@ -} collectFromField ::   ReadOnlyColumnOption ->-  (forall nullability a. FieldDefinition nullability a -> result) ->+  (forall qualifier nullability a. FieldQualifier qualifier => Maybe qualifier -> FieldDefinition nullability a -> result) ->   MarshallerField entity ->   [result] ->   [result] collectFromField readOnlyColumnOption fromField entry results =   case entry of-    Natural fieldDef (Just _) ->-      fromField fieldDef : results-    Natural fieldDef Nothing ->+    Natural mbQualifier fieldDef (Just _) ->+      fromField mbQualifier fieldDef : results+    Natural mbQualifier fieldDef Nothing ->       case readOnlyColumnOption of-        IncludeReadOnlyColumns -> fromField fieldDef : results+        IncludeReadOnlyColumns -> fromField mbQualifier fieldDef : results         ExcludeReadOnlyColumns -> results     Synthetic _ ->       results -{- |-  Uses the field definitions in the marshaller to construct SQL expressions+{- | Uses the field definitions in the marshaller to construct SQL expressions   that will set columns of the field definitions to their corresponding values   found in the Haskell @writeEntity@ value. @@ -274,8 +338,7 @@     []     (collectSetClauses writeEntity) -{- |-  An internal helper function that collects the 'Expr.SetClause's to+{- | An internal helper function that collects the 'Expr.SetClause's to   update all the fields contained in a 'SqlMarshaller'  @since 1.0.0.0@@ -287,15 +350,14 @@   [Expr.SetClause] collectSetClauses entity entry clauses =   case entry of-    Natural fieldDef (Just accessor) ->+    Natural _ fieldDef (Just accessor) ->       setField fieldDef (accessor entity) : clauses-    Natural _ Nothing ->+    Natural _ _ Nothing ->       clauses     Synthetic _ ->       clauses -{- |-  Specifies whether read-only fields should be included when using functions+{- | Specifies whether read-only fields should be included when using functions   such as 'collectFromField'.  @since 1.0.0.0@@ -304,8 +366,7 @@   = IncludeReadOnlyColumns   | ExcludeReadOnlyColumns -{- |-  'foldMarshallerFields' allows you to consume the 'FieldDefinition's that+{- | 'foldMarshallerFields' allows you to consume the 'FieldDefinition's that   are contained within the 'SqlMarshaller' to process them however is   required. This can be used to collect the names of all the fields, encode   them to 'SqlValue.SqlValue', etc.@@ -318,10 +379,12 @@   (MarshallerField writeEntity -> result -> result) ->   result foldMarshallerFields marshaller =-  foldMarshallerFieldsPart marshaller (Just id)+  foldMarshallerFieldsPart+    (Nothing :: Maybe TableIdentifier.TableIdentifier)+    marshaller+    (Just id) -{- |-  The internal helper function that actually implements 'foldMarshallerFields'.+{- | The internal helper function that actually implements 'foldMarshallerFields'.   It takes with it a function that extracts the current nesting entity from   the overall @writeEntity@ that the 'SqlMarshaller' is build on. 'MarshallNest'   adds more nesting by composing its accessor with the one given here.@@ -329,36 +392,39 @@ @since 1.0.0.0 -} foldMarshallerFieldsPart ::+  FieldQualifier qualifier =>+  Maybe qualifier ->   SqlMarshaller entityPart readEntity ->   Maybe (writeEntity -> entityPart) ->   result ->   (MarshallerField writeEntity -> result -> result) ->   result-foldMarshallerFieldsPart marshaller getPart currentResult addToResult =+foldMarshallerFieldsPart mbQualifier marshaller getPart currentResult addToResult =   case marshaller of     MarshallPure _ ->       currentResult     MarshallApply submarshallerA submarshallerB ->       let         subresultB =-          foldMarshallerFieldsPart submarshallerB getPart currentResult addToResult+          foldMarshallerFieldsPart mbQualifier submarshallerB getPart currentResult addToResult       in-        foldMarshallerFieldsPart submarshallerA getPart subresultB addToResult+        foldMarshallerFieldsPart mbQualifier submarshallerA getPart subresultB addToResult     MarshallNest nestingFunction submarshaller ->-      foldMarshallerFieldsPart submarshaller (fmap (nestingFunction .) getPart) currentResult addToResult+      foldMarshallerFieldsPart mbQualifier submarshaller (fmap (nestingFunction .) getPart) currentResult addToResult     MarshallField fieldDefinition ->-      addToResult (Natural fieldDefinition getPart) currentResult+      addToResult (Natural mbQualifier fieldDefinition getPart) currentResult     MarshallSyntheticField syntheticField ->       addToResult (Synthetic syntheticField) currentResult     MarshallMaybeTag m ->-      foldMarshallerFieldsPart m getPart currentResult addToResult+      foldMarshallerFieldsPart mbQualifier m getPart currentResult addToResult     MarshallPartial m ->-      foldMarshallerFieldsPart m getPart currentResult addToResult+      foldMarshallerFieldsPart mbQualifier m getPart currentResult addToResult     MarshallReadOnly m ->-      foldMarshallerFieldsPart m Nothing currentResult addToResult+      foldMarshallerFieldsPart mbQualifier m Nothing currentResult addToResult+    MarshallQualifyFields q m ->+      foldMarshallerFieldsPart (Just q) m getPart currentResult addToResult -{- |-  Decodes all the rows found in an execution result at once. The first row that+{- | Decodes all the rows found in an execution result at once. The first row that   fails to decode will return the 'MarshallError.MarshallErrorDetails' that   results, otherwise all decoded rows will be returned. @@ -380,8 +446,7 @@     (unannotatedSqlMarshaller marshallerWithMeta)     result -{- |-  Decodes all the rows found in a execution result at once. The first row that+{- | Decodes all the rows found in a execution result at once. The first row that   fails to decode will return the 'MarshallError.MarshallErrorDetails' that   results, otherwise all decoded rows will be returned. If an error occurs   while decoding a row, the 'RowIdentityExtractor' will be used to extract@@ -430,8 +495,7 @@               Right bs ->                 pure (Right (b : bs)) -{- |-  Attempts to decode a result set row that has already been fetched from the+{- | Attempts to decode a result set row that has already been fetched from the   database server into a Haskell value. If the decoding fails, a   'MarshallError.MarshallError' will be returned. @@ -448,19 +512,18 @@   case result of     Left err -> do       rowId <- getRowId row-      pure $-        Left $-          MarshallError.MarshallError-            { MarshallError.marshallErrorDetailLevel = errorDetailLevel-            , MarshallError.marshallErrorRowIdentifier = rowId-            , MarshallError.marshallErrorDetails = err-            }+      pure+        . Left+        $ MarshallError.MarshallError+          { MarshallError.marshallErrorDetailLevel = errorDetailLevel+          , MarshallError.marshallErrorRowIdentifier = rowId+          , MarshallError.marshallErrorDetails = err+          }     Right entity ->       pure $         Right entity -{- |-  A 'RowSource' can fetch and decode rows from a database result set. Using+{- | A 'RowSource' can fetch and decode rows from a database result set. Using   a 'RowSource' gives random access to the rows in the result set, only   attempting to decode them when they are requested by the user via 'decodeRow'. @@ -475,15 +538,16 @@ newtype RowSource readEntity   = RowSource (Row -> IO (Either MarshallError.MarshallErrorDetails readEntity)) +-- | @since 1.0.0.0 instance Functor RowSource where   fmap = mapRowSource +-- | @since 1.0.0.0 instance Applicative RowSource where   pure = constRowSource   (<*>) = applyRowSource -{- |-  Adds a function to the decoding proocess to transform the value returned+{- | Adds a function to the decoding proocess to transform the value returned   by a 'RowSource'.  @since 1.0.0.0@@ -492,8 +556,7 @@ mapRowSource f (RowSource decodeA) =   RowSource $ \row -> fmap (fmap f) (decodeA row) -{- |-  Creates a 'RowSource' that always returns the value given, rather than+{- | Creates a 'RowSource' that always returns the value given, rather than   attempting to access the result set and decoding anything.  @since 1.0.0.0@@ -502,8 +565,7 @@ constRowSource =   RowSource . const . pure . Right -{- |-  Applies a function that will be decoded from the result set to another+{- | Applies a function that will be decoded from the result set to another   value decoded from the result set.  @since 1.0.0.0@@ -520,8 +582,7 @@         eitherA <- decodeA row         pure (fmap aToB eitherA) -{- |-  Creates a 'RowSource' that will always fail to decode by returning the+{- | Creates a 'RowSource' that will always fail to decode by returning the   provided error. This can be used in cases where a 'RowSource' must   be provided but it is already known at run time that decoding is impossible.   For instance, this is used internally when a 'FieldDefinition' references@@ -533,8 +594,7 @@ failRowSource =   RowSource . const . pure . Left -{- |-  Uses the 'SqlMarshaller' given to build a 'RowSource' that will decode+{- | Uses the 'SqlMarshaller' given to build a 'RowSource' that will decode   from the given result set. The returned 'RowSource' can then be used to   decode rows as desired by the user. Note that the entire result set is   held in memory for potential decoding until the 'RowSource' is garbage@@ -551,8 +611,8 @@   columnMap <- prepareColumnMap result    let-    mkSource :: SqlMarshaller a b -> RowSource b-    mkSource marshallerPart =+    mkSource :: Maybe qualifier -> SqlMarshaller a b -> RowSource b+    mkSource mbQualifier marshallerPart =       -- Note, this case statement is evaluated before the row argument is       -- ever passed to a 'RowSource' to ensure that a single 'RowSource'       -- operation is build and re-used when decoding many rows.@@ -560,9 +620,9 @@         MarshallPure readEntity ->           constRowSource readEntity         MarshallApply marshallAToB marshallA ->-          mkSource marshallAToB <*> mkSource marshallA+          mkSource mbQualifier marshallAToB <*> mkSource mbQualifier marshallA         MarshallNest _ someMarshaller ->-          mkSource someMarshaller+          mkSource mbQualifier someMarshaller         MarshallField fieldDef ->           mkFieldNameSource             (fieldName fieldDef)@@ -571,27 +631,29 @@             result         MarshallSyntheticField syntheticField ->           mkFieldNameSource-            (syntheticFieldAlias syntheticField)+            (syntheticFieldName syntheticField)             (syntheticFieldValueFromSqlValue syntheticField)             columnMap             result         MarshallMaybeTag m ->-          mkSource m+          mkSource mbQualifier m         MarshallPartial m ->           let             fieldNames =               foldMarshallerFields m [] $ \marshallerField names ->                 case marshallerField of-                  Natural field _ ->+                  Natural _ field _ ->                     fieldName field : names                   Synthetic field ->-                    syntheticFieldAlias field : names+                    syntheticFieldName field : names           in-            partialRowSource fieldNames columnMap result (mkSource m)+            partialRowSource fieldNames columnMap result (mkSource mbQualifier m)         MarshallReadOnly m ->-          mkSource m+          mkSource mbQualifier m+        MarshallQualifyFields q m ->+          mkSource (Just q) m -  pure . mkSource $ marshaller+  pure . mkSource Nothing $ marshaller  partialRowSource ::   ExecutionResult result =>@@ -629,8 +691,7 @@       Right (Right entity) ->         pure $ Right entity -{- |-  Builds a 'RowSource' that will retrieve and decode the name field from+{- | Builds a 'RowSource' that will retrieve and decode the name field from   the result.  @since 1.0.0.0@@ -653,8 +714,7 @@           , MarshallError.actualColumnNames = Map.keysSet columnMap           } -{- |-  An internal helper function that finds all the column names in a result set+{- | An internal helper function that finds all the column names in a result set   and associates them with the respective column numbers for easier lookup.  @since 1.0.0.0@@ -684,8 +744,7 @@       entries <- traverse mkNameEntry [Column 0 .. maxColumn]       pure $ Map.fromList (catMaybes entries) -{- |-  A internal helper function for to build a 'RowSource' that retrieves and+{- | A internal helper function for to build a 'RowSource' that retrieves and   decodes a single column value form the result set.  @since 1.0.0.0@@ -714,8 +773,7 @@         in           pure (Left $ MarshallError.DecodingError details) -{- |-  A 'RowIdentityExtractor' is used to retrieve identifying information for a+{- | A 'RowIdentityExtractor' is used to retrieve identifying information for a   row when a 'MarshallError.MarshallError' occurs reading it from the database.    You should only need to worry about this type if you're using@@ -729,8 +787,7 @@ newtype RowIdentityExtractor   = RowIdentityExtractor (Row -> IO [(B8.ByteString, SqlValue.SqlValue)]) -{- |-  Constructs a 'RowIdentityExtractor' that will extract values for the given+{- | Constructs a 'RowIdentityExtractor' that will extract values for the given   fields from the result set to identify rows in decoding errors. Any of the   named fields that are missing from the result set will not be included in the   extracted row identity.@@ -768,8 +825,7 @@       Just maxColumn ->         fmap catMaybes $ traverse getIdentityValue [Column 0 .. maxColumn] -{- |-  Builds a 'SqlMarshaller' that maps a single field of a Haskell entity to+{- | Builds a 'SqlMarshaller' that maps a single field of a Haskell entity to   a single column in the database. That value to store in the database will be   retrieved from the entity using a provided accessor function. This function   is intended to be used inside of a stanza of 'Applicative' syntax that will@@ -797,8 +853,7 @@ marshallField accessor fieldDef =   MarshallNest accessor (MarshallField fieldDef) -{- |-  Builds a 'SqlMarshaller' that will include a SQL expression in select+{- | Builds a 'SqlMarshaller' that will include a SQL expression in select   statements to calculate a value using the columns of the table being selected   from. The columns being used in the calculation do not themselves need   to be selected, though they must be present in the table so they can@@ -818,7 +873,7 @@   atLeast21Field =     SyntheticField       { syntheticFieldExpression = RawSql.unsafeSqlExpression "age >= 21"-      , syntheticFieldAlias = Orville.stringToFieldName "over21"+      , syntheticFieldName = Orville.stringToFieldName "over21"       , syntheticFieldValueFromSqlValue = SqlValue.toBool       }   @@@ -831,8 +886,7 @@ marshallSyntheticField =   MarshallSyntheticField -{- |-  Nests a 'SqlMarshaller' inside another, using the given accessor to retrieve+{- | Nests a 'SqlMarshaller' inside another, using the given accessor to retrieve   values to be marshalled. The resulting marshaller can then be used in the same   way as 'marshallField' within the applicative syntax of a larger marshaller. @@ -873,8 +927,7 @@ marshallNested =   MarshallNest -{- |-  Lifts a 'SqlMarshaller' to have both read/write entities be 'Maybe',+{- | Lifts a 'SqlMarshaller' to have both read/write entities be 'Maybe',   and applies a tag to avoid double mapping.  @since 1.0.0.0@@ -907,9 +960,10 @@         MarshallPartial (fmap sequence $ go m)       MarshallReadOnly m ->         MarshallReadOnly (go m)+      MarshallQualifyFields q m ->+        MarshallQualifyFields q (go m) -{- |-  Builds a 'SqlMarshaller' that will raise a decoding error when the value+{- | Builds a 'SqlMarshaller' that will raise a decoding error when the value   produced is a 'Left'.  @since 1.0.0.0@@ -917,8 +971,22 @@ marshallPartial :: SqlMarshaller a (Either String b) -> SqlMarshaller a b marshallPartial = MarshallPartial -{- |-  Adds a prefix, followed by an underscore, to the names of all of the fields+{- | Builds a 'SqlMarshaller' that will qualify references to the (non-synthetic)+ fields it contains with the given qualifier. Any fields that have already been+ qualified in the provided marshaller by a previous call to 'marshallQualifyFields'+ will retain the qualifier they were previously given.++@since 1.1.0.0+-}+marshallQualifyFields ::+  FieldQualifier qualifier =>+  qualifier ->+  SqlMarshaller a b ->+  SqlMarshaller a b+marshallQualifyFields =+  MarshallQualifyFields++{- | Adds a prefix, followed by an underscore, to the names of all of the fields   and synthetic fields in a 'SqlMarshaller'.  @since 1.0.0.0@@ -943,9 +1011,9 @@     MarshallMaybeTag m -> MarshallMaybeTag $ go m     MarshallPartial m -> MarshallPartial $ go m     MarshallReadOnly m -> MarshallReadOnly $ go m+    MarshallQualifyFields q m -> MarshallQualifyFields q $ go m -{- |-  Marks a 'SqlMarshaller' as read-only so that it will not attempt to+{- | Marks a 'SqlMarshaller' as read-only so that it will not attempt to   read any values from the @writeEntity@. You should use this if you have   a group of fields which are populated by database rather than the application. @@ -954,8 +1022,7 @@ marshallReadOnly :: SqlMarshaller a b -> SqlMarshaller c b marshallReadOnly = MarshallReadOnly -{- |-  A version of 'marshallField' that uses 'marshallReadOnly' to make a single+{- | A version of 'marshallField' that uses 'marshallReadOnly' to make a single   read-only field. You will usually use this in conjunction with a   'FieldDefinition' like @serialField@ where the value is populated by the   database.@@ -966,3 +1033,83 @@   FieldDefinition nullability fieldValue ->   SqlMarshaller writeEntity fieldValue marshallReadOnlyField = MarshallReadOnly . MarshallField++{- | Checks that the row value containing the write fields of the+  'SqlMarshaller' is equal to the given value encoded as a row.++@since 1.1.0.0+-}+marshallerEquals :: SqlMarshaller writeEntity x -> writeEntity -> Expr.BooleanExpr+marshallerEquals = SqlComparable.equals++{- | Checks that the row value containing the write fields of the+  'SqlMarshaller' is not equal to the given value encoded as a row.++@since 1.1.0.0+-}+marshallerNotEquals :: SqlMarshaller writeEntity x -> writeEntity -> Expr.BooleanExpr+marshallerNotEquals = SqlComparable.notEquals++{- | Checks that the row value containing the write fields of the+  'SqlMarshaller' is distinct from the given value encoded as a row.++@since 1.1.0.0+-}+marshallerIsDistinctFrom :: SqlMarshaller writeEntity x -> writeEntity -> Expr.BooleanExpr+marshallerIsDistinctFrom = SqlComparable.isDistinctFrom++{- | Checks that the row value containing the write fields of the+  'SqlMarshaller' is not distinct from the given value encoded as a row.++@since 1.1.0.0+-}+marshallerIsNotDistinctFrom :: SqlMarshaller writeEntity x -> writeEntity -> Expr.BooleanExpr+marshallerIsNotDistinctFrom = SqlComparable.isNotDistinctFrom++{- | Checks that the row value containing the write fields of the+  'SqlMarshaller' is less than the given value encoded as a row.++@since 1.1.0.0+-}+marshallerLessThan :: SqlMarshaller writeEntity x -> writeEntity -> Expr.BooleanExpr+marshallerLessThan = SqlComparable.lessThan++{- | Checks that the row value containing the write fields of the+  'SqlMarshaller' is less than or equal to the given value encoded as a row.++@since 1.1.0.0+-}+marshallerLessThanOrEqualTo :: SqlMarshaller writeEntity x -> writeEntity -> Expr.BooleanExpr+marshallerLessThanOrEqualTo = SqlComparable.lessThanOrEqualTo++{- | Checks that the row value containing the write fields of the+  'SqlMarshaller' is greater than the given value encoded as a row.++@since 1.1.0.0+-}+marshallerGreaterThan :: SqlMarshaller writeEntity x -> writeEntity -> Expr.BooleanExpr+marshallerGreaterThan = SqlComparable.greaterThan++{- | Checks that the row value containing the write fields of the+  'SqlMarshaller' is greater than or equal to the given value encoded as a row.++@since 1.1.0.0+-}+marshallerGreaterThanOrEqualTo :: SqlMarshaller writeEntity x -> writeEntity -> Expr.BooleanExpr+marshallerGreaterThanOrEqualTo = SqlComparable.greaterThanOrEqualTo++{- | Checks that the row value containing the write fields of the+  'SqlMarshaller' is in the given list of values encoded as a list of rows.++@since 1.1.0.0+-}+marshallerIn :: SqlMarshaller writeEntity x -> NE.NonEmpty writeEntity -> Expr.BooleanExpr+marshallerIn = SqlComparable.isIn++{- | Checks that the row value containing the write fields of the+  'SqlMarshaller' is not in the given list of values encoded as a list of rows.++@since 1.1.0.0+-}+marshallerNotIn :: SqlMarshaller writeEntity x -> NE.NonEmpty writeEntity -> Expr.BooleanExpr+marshallerNotIn = SqlComparable.isNotIn
src/Orville/PostgreSQL/Marshall/SqlType.hs view
@@ -1,5 +1,5 @@ {- |-Copyright : Flipstone Technology Partners 2023+Copyright : Flipstone Technology Partners 2023-2025 License   : MIT Stability : Stable @@ -63,8 +63,7 @@ import Orville.PostgreSQL.Raw.SqlValue (SqlValue) import qualified Orville.PostgreSQL.Raw.SqlValue as SqlValue -{- |-  SqlType defines the mapping of a Haskell type (@a@) to a SQL column type in the+{- | SqlType defines the mapping of a Haskell type (@a@) to a SQL column type in the   database. This includes both how to convert the type to and from the raw values   read from the database as well as the schema information required to create   and migrate columns using the type.@@ -100,8 +99,7 @@   -- logic to ignore the default value rather than drop it as it normally would.   } -{- |-  'integer' defines a 32-bit integer type. This corresponds to the "INTEGER" type in SQL.+{- | 'integer' defines a 32-bit integer type. This corresponds to the "INTEGER" type in SQL.  @since 1.0.0.0 -}@@ -117,8 +115,7 @@     , sqlTypeDontDropImplicitDefaultDuringMigrate = False     } -{- |-  'serial' defines a 32-bit auto-incrementing column type. This corresponds to+{- | 'serial' defines a 32-bit auto-incrementing column type. This corresponds to   the "SERIAL" type in PostgreSQL.  @since 1.0.0.0@@ -135,8 +132,7 @@     , sqlTypeDontDropImplicitDefaultDuringMigrate = True     } -{- |-  'bigInteger' defines a 64-bit integer type. This corresponds to the "BIGINT"+{- | 'bigInteger' defines a 64-bit integer type. This corresponds to the "BIGINT"   type in SQL.  @since 1.0.0.0@@ -153,8 +149,7 @@     , sqlTypeDontDropImplicitDefaultDuringMigrate = False     } -{- |-  'bigSerial' defines a 64-bit auto-incrementing column type. This corresponds to+{- | 'bigSerial' defines a 64-bit auto-incrementing column type. This corresponds to   the "BIGSERIAL" type in PostgresSQL.  @since 1.0.0.0@@ -171,8 +166,7 @@     , sqlTypeDontDropImplicitDefaultDuringMigrate = True     } -{- |-  'smallInteger' defines a 16-bit integer type. This corresponds to the "SMALLINT" type in SQL.+{- | 'smallInteger' defines a 16-bit integer type. This corresponds to the "SMALLINT" type in SQL.  @since 1.0.0.0 -}@@ -188,10 +182,11 @@     , sqlTypeDontDropImplicitDefaultDuringMigrate = False     } -{- |-  'double' defines a floating point numeric type. This corresponds to the+{- | 'double' defines a floating point numeric type. This corresponds to the   "DOUBLE PRECISION" type in SQL. +  Note that Infinity, -Infinity and NaN are not handled.+ @since 1.0.0.0 -} double :: SqlType Double@@ -206,8 +201,7 @@     , sqlTypeDontDropImplicitDefaultDuringMigrate = False     } -{- |-  'boolean' defines a True/False boolean type. This corresponds to the "BOOLEAN"+{- | 'boolean' defines a True/False boolean type. This corresponds to the "BOOLEAN"   type in SQL.  @since 1.0.0.0@@ -224,10 +218,12 @@     , sqlTypeDontDropImplicitDefaultDuringMigrate = False     } -{- |-  'unboundedText' defines an unbounded length text field type. This corresponds to a+{- | 'unboundedText' defines an unbounded length text field type. This corresponds to a   "TEXT" type in PostgreSQL. +  Note that values must not contain NULL codepoints, they are unsupported in+  PostgreSQL.+ @since 1.0.0.0 -} unboundedText :: SqlType Text@@ -242,10 +238,12 @@     , sqlTypeDontDropImplicitDefaultDuringMigrate = False     } -{- |-  'fixedText' defines a fixed length text field type. This corresponds to a+{- | 'fixedText' defines a fixed length text field type. This corresponds to a   "CHAR(len)" type in PostgreSQL. +  Note that values must not contain NULL codepoints, they are unsupported in+  PostgreSQL.+ @since 1.0.0.0 -} fixedText :: Int32 -> SqlType Text@@ -260,10 +258,12 @@     , sqlTypeDontDropImplicitDefaultDuringMigrate = False     } -{- |-  'boundedText' defines a variable length text field type. This corresponds to a+{- | 'boundedText' defines a variable length text field type. This corresponds to a   "VARCHAR(len)" type in PostgreSQL. +  Note that values must not contain NULL codepoints, they are unsupported in+  PostgreSQL.+ @since 1.0.0.0 -} boundedText :: Int32 -> SqlType Text@@ -278,10 +278,12 @@     , sqlTypeDontDropImplicitDefaultDuringMigrate = False     } -{- |-  'textSearchVector' defines a type for indexed text searching. It corresponds to the+{- | 'textSearchVector' defines a type for indexed text searching. It corresponds to the   "TSVECTOR" type in PostgreSQL. +  Note that values must not contain NULL codepoints, they are unsupported in+  PostgreSQL.+ @since 1.0.0.0 -} textSearchVector :: SqlType Text@@ -296,8 +298,7 @@     , sqlTypeDontDropImplicitDefaultDuringMigrate = False     } -{- |-  'uuid' defines a UUID type. It corresponds to the "UUID" type in PostgreSQL.+{- | 'uuid' defines a UUID type. It corresponds to the "UUID" type in PostgreSQL.  @since 1.0.0.0 -}@@ -319,10 +320,13 @@       , sqlTypeDontDropImplicitDefaultDuringMigrate = False       } -{- |-  'date' defines a type representing a calendar date (without time zone). It corresponds+{- | 'date' defines a type representing a calendar date (without time zone). It corresponds   to the "DATE" type in SQL. +  This type cannot represent the full range of 'Time.Day' values. PostgreSQL supports years+  from -4731 to 5874897 inclusive for this type, and sending a 'Time.Day' with a year outside+  of this range to the database will result in a PostgreSQL exception.+ @since 1.0.0.0 -} date :: SqlType Time.Day@@ -337,11 +341,14 @@     , sqlTypeDontDropImplicitDefaultDuringMigrate = False     } -{- |-  'timestamp' defines a type representing a particular point in time without time zone information,+{- | 'timestamp' defines a type representing a particular point in time without time zone information,   but can be constructed with a time zone offset.   It corresponds to the "TIMESTAMP with time zone" type in SQL. +  This type cannot represent the full range of 'Time.UTCTime' values. PostgreSQL supports years+  from -4731 to 294276 inclusive for this type, and sending a 'Time.UTCTime' with a year outside+  of this range to the database will result in a PostgreSQL exception.+   Note: This is NOT a typo. The "TIMESTAMP with time zone" type in SQL does not include   any actual time zone information. For an excellent explanation of the complexities   involving this type, please see Chris Clark's blog post about it:@@ -361,10 +368,13 @@     , sqlTypeDontDropImplicitDefaultDuringMigrate = False     } -{- |-  'timestampWithoutZone' defines a type representing a particular point in time (without time zone).+{- | 'timestampWithoutZone' defines a type representing a particular point in time (without time zone).   It corresponds to the "TIMESTAMP without time zone" type in SQL. +  This type cannot represent the full range of 'Time.LocalTime' values. PostgreSQL supports years+  from -4731 to 294276 inclusive for this type, and sending a 'Time.LocalTime' with a year outside+  of this range to the database will result in a PostgreSQL exception.+   http://blog.untrod.com/2016/08/actually-understanding-timezones-in-postgresql.html  @since 1.0.0.0@@ -381,10 +391,12 @@     , sqlTypeDontDropImplicitDefaultDuringMigrate = False     } -{- |-   'jsonb' represents any type that can be converted To and From JSON. This corresponds+{- |  'jsonb' represents any type that can be converted To and From JSON. This corresponds    to the "JSONB" type in PostgreSQL. +  Note that strings contained within must not contain NULL codepoints,+  as they are unsupported in PostgreSQL.+ @since 1.0.0.0 -} jsonb :: SqlType Text@@ -399,8 +411,7 @@     , sqlTypeDontDropImplicitDefaultDuringMigrate = False     } -{- |-  'oid' corresponds to the type used in PostgreSQL for identifying system+{- | 'oid' corresponds to the type used in PostgreSQL for identifying system   objects.  @since 1.0.0.0@@ -417,8 +428,7 @@     , sqlTypeDontDropImplicitDefaultDuringMigrate = False     } -{- |-  'foreignRefType' creates a 'SqlType' suitable for columns that will be+{- | 'foreignRefType' creates a 'SqlType' suitable for columns that will be   foreign keys referencing a column of the given 'SqlType'. For most types, the   underlying SQL type will be identical, but for special types (such as   auto-incrementing primary keys), the type constructed by 'foreignRefType' will@@ -434,8 +444,7 @@     Nothing -> sqlType     Just refExpr -> sqlType {sqlTypeExpr = refExpr, sqlTypeReferenceExpr = Nothing} -{- |-  'tryConvertSqlType' changes the Haskell type used by a 'SqlType' which+{- | 'tryConvertSqlType' changes the Haskell type used by a 'SqlType' which   changes the column type that will be used in the database schema. The   functions given will be used to convert the now Haskell type to and from the   original type when reading and writing values from the database. When reading@@ -453,8 +462,7 @@         aToB a     } -{- |-  'convertSqlType' changes the Haskell type used by a 'SqlType' in the same manner+{- | 'convertSqlType' changes the Haskell type used by a 'SqlType' in the same manner   as 'tryConvertSqlType' in cases where an @a@ can always be converted to a @b@.  @since 1.0.0.0
src/Orville/PostgreSQL/Marshall/SyntheticField.hs view
@@ -1,7 +1,9 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-}  {- |-Copyright : Flipstone Technology Partners 2023+Copyright : Flipstone Technology Partners 2023-2025 License   : MIT Stability : Stable @@ -11,63 +13,90 @@   ( SyntheticField   , syntheticFieldExpression   , syntheticFieldAlias+  , syntheticFieldName   , syntheticFieldValueFromSqlValue   , syntheticField   , nullableSyntheticField   , prefixSyntheticField+  , orderBySyntheticField   ) where  import qualified Data.ByteString.Char8 as B8 import qualified Orville.PostgreSQL.Expr as Expr-import Orville.PostgreSQL.Marshall.FieldDefinition (FieldName, byteStringToFieldName, fieldNameToByteString, stringToFieldName)+import Orville.PostgreSQL.Internal.FieldName (FieldName, byteStringToFieldName, fieldNameToByteString, stringToFieldName)+import qualified Orville.PostgreSQL.Marshall.SqlComparable as SqlComparable+import qualified Orville.PostgreSQL.Marshall.SqlType as SqlType import qualified Orville.PostgreSQL.Raw.SqlValue as SqlValue -{- |-  A 'SyntheticField' can be used to evaluate a SQL expression based on the+{- | A 'SyntheticField' can be used to evaluate a SQL expression based on the   columns of a table when records are selected from the database. Synthetic   fields are inherently read-only.  @since 1.0.0.0 -} data SyntheticField a = SyntheticField-  { _syntheticFieldExpression :: Expr.ValueExpression-  , _syntheticFieldAlias :: FieldName-  , _syntheticFieldValueFromSqlValue :: SqlValue.SqlValue -> Either String a+  { i_syntheticFieldExpression :: Expr.ValueExpression+  , i_syntheticFieldName :: FieldName+  , i_syntheticFieldType :: SqlType.SqlType a   } -{- |-  Returns the SQL expression that should be used in select statements to+{- | Marshalls a Haskell value to be in the field to its 'SqlValue.SqlValue'+  representation.++@since 1.1.0.0+-}+instance SqlComparable.SqlComparable (SyntheticField a) a where+  toComparableSqlValue field = SqlType.sqlTypeToSql (syntheticFieldType field)+  referenceValueExpression = syntheticFieldExpression++{- | Returns the SQL expression that should be used in select statements to   calculate the synthetic field.  @since 1.0.0.0 -} syntheticFieldExpression :: SyntheticField a -> Expr.ValueExpression syntheticFieldExpression =-  _syntheticFieldExpression+  i_syntheticFieldExpression -{- |-  Returns the alias that should be used in select statements to name the+{- | Returns the alias that should be used in select statements to name the   synthetic field.  @since 1.0.0.0 -}+{-# DEPRECATED syntheticFieldAlias "Use syntheticFieldName instead" #-} syntheticFieldAlias :: SyntheticField a -> FieldName syntheticFieldAlias =-  _syntheticFieldAlias+  i_syntheticFieldName -{- |-  Decodes a calculated value selected from the database to its expected+{- | Returns the name that should be used in select statements to name the+  synthetic field.++@since 1.1.0.0+-}+syntheticFieldName :: SyntheticField a -> FieldName+syntheticFieldName =+  i_syntheticFieldName++{- | Decodes a calculated value selected from the database to its expected   Haskell type. Returns a 'Left' with an error message if the decoding fails.  @since 1.0.0.0 -} syntheticFieldValueFromSqlValue :: SyntheticField a -> SqlValue.SqlValue -> Either String a syntheticFieldValueFromSqlValue =-  _syntheticFieldValueFromSqlValue+  SqlType.sqlTypeFromSql . syntheticFieldType -{- |-  Constructs a 'SyntheticField' that will select a SQL expression using+{- | The 'SqlType.SqlType' for the 'SyntheticField' determines the PostgreSQL+  data type used to define the field as well as how to marshall Haskell values+  from the database.++@since 1.1.0.0+-}+syntheticFieldType :: SyntheticField a -> SqlType.SqlType a+syntheticFieldType = i_syntheticFieldType++{- | Constructs a 'SyntheticField' that will select a SQL expression using   the given alias.  @since 1.0.0.0@@ -77,32 +106,39 @@   Expr.ValueExpression ->   -- | The alias to be used to name the calculation in SQL expressions.   String ->-  -- | A function to decode the expression result from a 'SqlValue.SqlValue'.-  (SqlValue.SqlValue -> Either String a) ->+  -- | The 'SqlType.SqlType' to be used for comparisons+  SqlType.SqlType a ->   SyntheticField a-syntheticField expression alias fromSqlValue =+syntheticField expression alias sqlType =   SyntheticField-    { _syntheticFieldExpression = expression-    , _syntheticFieldAlias = stringToFieldName alias-    , _syntheticFieldValueFromSqlValue = fromSqlValue+    { i_syntheticFieldExpression = expression+    , i_syntheticFieldName = stringToFieldName alias+    , i_syntheticFieldType = sqlType     } -{- |-  Modifies a 'SyntheticField' to allow it to decode @NULL@ values.+{- | Modifies a 'SyntheticField' to allow it to decode @NULL@ values.  @since 1.0.0.0 -} nullableSyntheticField :: SyntheticField a -> SyntheticField (Maybe a) nullableSyntheticField synthField =-  synthField-    { _syntheticFieldValueFromSqlValue = \sqlValue ->-        if SqlValue.isSqlNull sqlValue-          then Right Nothing-          else Just <$> syntheticFieldValueFromSqlValue synthField sqlValue-    }+  let+    nullableType :: SqlType.SqlType a -> SqlType.SqlType (Maybe a)+    nullableType sqlType =+      sqlType+        { SqlType.sqlTypeToSql = maybe SqlValue.sqlNull (SqlType.sqlTypeToSql sqlType)+        , SqlType.sqlTypeFromSql =+            \sqlValue ->+              if SqlValue.isSqlNull sqlValue+                then Right Nothing+                else fmap Just $ SqlType.sqlTypeFromSql sqlType sqlValue+        }+  in+    synthField+      { i_syntheticFieldType = nullableType (syntheticFieldType synthField)+      } -{- |-  Adds a prefix, followed by an underscore, to the alias used to name the+{- | Adds a prefix, followed by an underscore, to the alias used to name the   synthetic field.  @since 1.0.0.0@@ -113,5 +149,13 @@   SyntheticField a prefixSyntheticField prefix synthField =   synthField-    { _syntheticFieldAlias = byteStringToFieldName (B8.pack prefix <> "_" <> fieldNameToByteString (syntheticFieldAlias synthField))+    { i_syntheticFieldName = byteStringToFieldName (B8.pack prefix <> "_" <> fieldNameToByteString (syntheticFieldName synthField))     }++{- | Orders a query by the alias for the given synthetic field.++@since 1.1.0.0+-}+orderBySyntheticField :: SyntheticField a -> Expr.OrderByDirection -> Expr.OrderByExpr+orderBySyntheticField =+  Expr.orderByColumnName . Expr.unqualified . Expr.fromIdentifier . Expr.identifierFromBytes . fieldNameToByteString . syntheticFieldName
src/Orville/PostgreSQL/Monad/HasOrvilleState.hs view
@@ -18,8 +18,7 @@  import Orville.PostgreSQL.OrvilleState (OrvilleState) -{- |-  'HasOrvilleState' is the typeclass that Orville uses to access and manange+{- | 'HasOrvilleState' is the typeclass that Orville uses to access and manange   the connection pool and state tracking when it is being executed inside an   unknown Monad. It is a specialized version of the Reader interface so that it   can be easily implemented by application Monads that already have a Reader@@ -53,19 +52,17 @@ @since 1.0.0.0 -} class HasOrvilleState m where-  -- |-  --     Fetches the current 'OrvilleState' from the host Monad context. The-  --     equivalent of 'ask' for 'ReaderT OrvilleState'.+  -- | Fetches the current 'OrvilleState' from the host Monad context. The+  -- equivalent of 'ask' for 'ReaderT OrvilleState'.   --   -- @since 1.0.0.0   askOrvilleState :: m OrvilleState -  -- |-  --     Applies a modification to the 'OrvilleState' that is local to the given-  --     monad operation. Calls to 'askOrvilleState' made within the 'm a' provided-  --     must return the modified state. The modified state must only apply to-  --     the given 'm a' and not be persisted beyond it. The equivalent of 'local'-  --     for 'ReaderT OrvilleState'.+  -- | Applies a modification to the 'OrvilleState' that is local to the given+  -- monad operation. Calls to 'askOrvilleState' made within the 'm a' provided+  -- must return the modified state. The modified state must only apply to+  -- the given 'm a' and not be persisted beyond it. The equivalent of 'local'+  -- for 'ReaderT OrvilleState'.   --   -- @since 1.0.0.0   localOrvilleState ::@@ -75,10 +72,12 @@     m a ->     m a +-- | @since 1.0.0.0 instance Monad m => HasOrvilleState (ReaderT OrvilleState m) where   askOrvilleState = ask   localOrvilleState = local +-- | @since 1.0.0.0 instance {-# OVERLAPS #-} (Monad m, HasOrvilleState m) => HasOrvilleState (ReaderT r m) where   askOrvilleState = lift askOrvilleState   localOrvilleState f = mapReaderT (localOrvilleState f)
src/Orville/PostgreSQL/Monad/Orville.hs view
@@ -24,8 +24,7 @@ import qualified Orville.PostgreSQL.OrvilleState as OrvilleState import Orville.PostgreSQL.Raw.Connection (ConnectionPool) -{- |-  The 'Orville' Monad provides an easy starter implementation of+{- | The 'Orville' Monad provides an easy starter implementation of   'MonadOrville.MonadOrville' when you don't have a monad specific to your   application that you need to use. @@ -49,8 +48,7 @@     , ExSafe.MonadCatch     ) -{- |-  Runs an 'Orville' operation in the 'IO' monad using the given connection+{- | Runs an 'Orville' operation in the 'IO' monad using the given connection   pool.    This will run the 'Orville' operation with the@@ -66,8 +64,7 @@   runOrvilleWithState     . OrvilleState.newOrvilleState ErrorDetailLevel.defaultErrorDetailLevel -{- |-  Runs an 'Orville' operation in the 'IO' monad, starting from the provided+{- | Runs an 'Orville' operation in the 'IO' monad, starting from the provided   'OrvilleState.OrvilleState'.    Caution: If you harvest an 'OrvilleState.OrvilleState' from inside a
src/Orville/PostgreSQL/PgCatalog.hs view
@@ -1,7 +1,7 @@ {-# OPTIONS_GHC -Wno-missing-import-lists #-}  {- |-Copyright : Flipstone Technology Partners 2023+Copyright : Flipstone Technology Partners 2023-2024 License   : MIT Stability : Stable @@ -18,6 +18,10 @@ import Orville.PostgreSQL.PgCatalog.PgAttributeDefault as Export import Orville.PostgreSQL.PgCatalog.PgClass as Export import Orville.PostgreSQL.PgCatalog.PgConstraint as Export+import Orville.PostgreSQL.PgCatalog.PgDescription as Export+import Orville.PostgreSQL.PgCatalog.PgExtension as Export import Orville.PostgreSQL.PgCatalog.PgIndex as Export import Orville.PostgreSQL.PgCatalog.PgNamespace as Export+import Orville.PostgreSQL.PgCatalog.PgProc as Export import Orville.PostgreSQL.PgCatalog.PgSequence as Export+import Orville.PostgreSQL.PgCatalog.PgTrigger as Export
src/Orville/PostgreSQL/PgCatalog/DatabaseDescription.hs view
@@ -1,7 +1,7 @@ {-# LANGUAGE CPP #-}  {- |-Copyright : Flipstone Technology Partners 2023+Copyright : Flipstone Technology Partners 2023-2024 License   : MIT Stability : Stable @@ -18,7 +18,10 @@   , lookupRelationOfKind   , lookupAttribute   , lookupAttributeDefault-  , describeDatabaseRelations+  , lookupAttributeComment+  , lookupProcedure+  , lookupExtension+  , describeDatabase   ) where @@ -27,6 +30,7 @@ import Control.Applicative (liftA2) #endif import qualified Data.Map.Strict as Map+import qualified Data.Text as T import qualified Database.PostgreSQL.LibPQ as LibPQ  import qualified Orville.PostgreSQL as Orville@@ -35,26 +39,33 @@ import Orville.PostgreSQL.PgCatalog.PgAttributeDefault (PgAttributeDefault (pgAttributeDefaultAttributeNumber), attributeDefaultRelationOidField, pgAttributeDefaultTable) import Orville.PostgreSQL.PgCatalog.PgClass (PgClass (pgClassNamespaceOid, pgClassOid, pgClassRelationName), RelationKind, RelationName, namespaceOidField, pgClassRelationKind, pgClassTable, relationNameField, relationNameToString) import Orville.PostgreSQL.PgCatalog.PgConstraint (PgConstraint (pgConstraintForeignKey, pgConstraintForeignRelationOid, pgConstraintKey), constraintRelationOidField, pgConstraintTable)+import Orville.PostgreSQL.PgCatalog.PgDescription (ObjectSubId, PgDescription (pgDescriptionObjSubId), objOidField, objectSubIdFromAttributeNumber, objectSubIdZero, pgDescriptionDescription, pgDescriptionTable)+import Orville.PostgreSQL.PgCatalog.PgExtension (ExtensionName, PgExtension, extensionNameField, pgExtensionTable) import Orville.PostgreSQL.PgCatalog.PgIndex (PgIndex (pgIndexAttributeNumbers, pgIndexPgClassOid), indexIsLiveField, indexRelationOidField, pgIndexTable) import Orville.PostgreSQL.PgCatalog.PgNamespace (NamespaceName, PgNamespace (pgNamespaceOid), namespaceNameField, pgNamespaceTable)+import Orville.PostgreSQL.PgCatalog.PgProc (PgProc, ProcName, pgProcTable, procNameField, procNamespaceOidField) import Orville.PostgreSQL.PgCatalog.PgSequence (PgSequence, pgSequenceTable, sequencePgClassOidField)+import Orville.PostgreSQL.PgCatalog.PgTrigger (PgTrigger, pgTriggerTable, triggerRelationOidField) import qualified Orville.PostgreSQL.Plan as Plan import qualified Orville.PostgreSQL.Plan.Many as Many import qualified Orville.PostgreSQL.Plan.Operation as Op -{- |-  A description of selected items from a single PostgreSQL database.-  'describeDatabaseRelations' can be used to load the descriptions of request+{- | A description of selected items from a single PostgreSQL database.+  'describeDatabase' can be used to load the descriptions of request   items.  @since 1.0.0.0 -} data DatabaseDescription = DatabaseDescription   { databaseRelations :: Map.Map (NamespaceName, RelationName) RelationDescription+  -- ^ @since 1.0.0.0+  , databaseProcedures :: Map.Map (NamespaceName, ProcName) PgProc+  -- ^ @since 1.1.0.0+  , databaseExtensions :: Map.Map ExtensionName PgExtension+  -- ^ @since 1.1.0.0   } -{- |-  Lookup a relation by its qualified name in the @pg_catalog@ schema.+{- | Lookup a relation by its qualified name in the @pg_catalog@ schema.  @since 1.0.0.0 -}@@ -65,8 +76,29 @@ lookupRelation key =   Map.lookup key . databaseRelations -{- |-  Lookup a relation by its qualified name in the @pg_catalog@ schema. If the+{- | Lookup a procedure by its qualified name in the @pg_catalog@ schema.++@since 1.1.0.0+-}+lookupProcedure ::+  (NamespaceName, ProcName) ->+  DatabaseDescription ->+  Maybe PgProc+lookupProcedure key =+  Map.lookup key . databaseProcedures++{- | Lookup an extension by its name in the @pg_catalog@ schema.++@since 1.1.0.0+-}+lookupExtension ::+  ExtensionName ->+  DatabaseDescription ->+  Maybe PgExtension+lookupExtension key =+  Map.lookup key . databaseExtensions++{- | Lookup a relation by its qualified name in the @pg_catalog@ schema. If the   relation is not of the expected kind, 'Nothing' is returned.  @since 1.0.0.0@@ -85,23 +117,33 @@     Nothing ->       Nothing -{- |-  A description of a particular relation in the PostgreSQL database, including+{- | A description of a particular relation in the PostgreSQL database, including   the attributes of the relation.  @since 1.0.0.0 -} data RelationDescription = RelationDescription   { relationRecord :: PgClass+  -- ^ @since 1.0.0.0   , relationAttributes :: Map.Map AttributeName PgAttribute+  -- ^ @since 1.0.0.0   , relationAttributeDefaults :: Map.Map AttributeNumber PgAttributeDefault+  -- ^ @since 1.0.0.0   , relationConstraints :: [ConstraintDescription]+  -- ^ @since 1.0.0.0   , relationIndexes :: [IndexDescription]+  -- ^ @since 1.0.0.0+  , relationTriggers :: [PgTrigger]+  -- ^ @since 1.1.0.0   , relationSequence :: Maybe PgSequence+  -- ^ @since 1.0.0.0+  , relationComment :: Maybe T.Text+  -- ^ @since 1.1.0.0+  , relationAttributeComments :: Map.Map AttributeName T.Text+  -- ^ @since 1.1.0.0   } -{- |-  Find an attribute by name from the 'RelationDescription'.+{- | Find an attribute by name from the 'RelationDescription'.  @since 1.0.0.0 -}@@ -112,8 +154,7 @@ lookupAttribute key =   Map.lookup key . relationAttributes -{- |-  Find an attribute default from the 'RelationDescription'.+{- | Find an attribute default from the 'RelationDescription'.  @since 1.0.0.0 -}@@ -124,8 +165,18 @@ lookupAttributeDefault attr =   Map.lookup (pgAttributeNumber attr) . relationAttributeDefaults -{- |-  A description of a particular constraint in the PostgreSQL database, including+{- | Find an attribute comment by name from the 'RelationDescription'++@since 1.1.0.0+-}+lookupAttributeComment ::+  AttributeName ->+  RelationDescription ->+  Maybe T.Text+lookupAttributeComment key =+  Map.lookup key . relationAttributeComments++{- | A description of a particular constraint in the PostgreSQL database, including   the attributes and relations that it references.  @since 1.0.0.0@@ -137,8 +188,7 @@   , constraintForeignKey :: Maybe [PgAttribute]   } -{- |-  A description of a relation in the PostgreSQL database that is referenced by+{- | A description of a relation in the PostgreSQL database that is referenced by   a foreign key constraint, including the namespace that the relation belongs to.  @since 1.0.0.0@@ -148,8 +198,7 @@   , foreignRelationNamespace :: PgNamespace   } -{- |-  A description of an index in the PostgreSQL database, including the names of+{- | A description of an index in the PostgreSQL database, including the names of   the attributes included in the index and the 'PgClass' record of the index   itself (NOT the 'PgClass' of the table that the index is for). @@ -161,8 +210,7 @@   , indexMembers :: [IndexMember]   } -{- |-  A description of an index member in the PostgreSQL database. If the member+{- | A description of an index member in the PostgreSQL database. If the member   is a simple attribute, the 'PgAttribute' for that is provided. If it is an   index over an expression, no further description is currently provided. @@ -172,35 +220,47 @@   = IndexAttribute PgAttribute   | IndexExpression -{- |-  Describes the requested relations in the current database. If any of the+{- | Describes the requested relations in the current database. If any of the   relations do not exist, they will not have an entry in the returned   description.    Each 'RelationDescription' will contain all the attributes that currently   exist for that relation, according to the @pg_catalog@ tables. -@since 1.0.0.0+@since 1.1.0.0 -}-describeDatabaseRelations ::+describeDatabase ::   Orville.MonadOrville m =>   [(NamespaceName, RelationName)] ->+  [(NamespaceName, ProcName)] ->+  [ExtensionName] ->   m DatabaseDescription-describeDatabaseRelations relations = do+describeDatabase relations procedures extensions = do   manyRelations <-     Plan.execute       (Plan.planMany describeRelationByName)       relations +  manyProcedures <-+    Plan.execute+      (Plan.planMany describeProcedureByName)+      procedures++  manyExtensions <-+    Plan.execute+      (Plan.planMany describeExtensionByName)+      extensions+   let-    relationsMap =-      Map.mapMaybe id-        . Many.toMap-        $ manyRelations+    mkMap :: Ord k => Many.Many k (Maybe v) -> Map.Map k v+    mkMap =+      Map.mapMaybe id . Many.toMap    pure $     DatabaseDescription-      { databaseRelations = relationsMap+      { databaseRelations = mkMap manyRelations+      , databaseProcedures = mkMap manyProcedures+      , databaseExtensions = mkMap manyExtensions       }  describeRelationByName :: Plan.Plan scope (NamespaceName, RelationName) (Maybe RelationDescription)@@ -219,25 +279,29 @@ describeRelationByClass =   Plan.bind Plan.askParam $ \pgClass ->     Plan.bind findClassAttributes $ \attributes ->-      let-        classAndAttributes =-          mkPgClassAndAttributes+      Plan.bind findClassDescriptions $ \descriptions ->+        let+          classAndAttributes =+            mkPgClassAndAttributes+              <$> Plan.use pgClass+              <*> Plan.use attributes+        in+          RelationDescription             <$> Plan.use pgClass-            <*> Plan.use attributes-      in-        RelationDescription-          <$> Plan.use pgClass-          <*> Plan.use (fmap (indexBy pgAttributeName) attributes)-          <*> fmap (indexBy pgAttributeDefaultAttributeNumber) findClassAttributeDefaults-          <*> Plan.chain classAndAttributes findClassConstraints-          <*> Plan.chain classAndAttributes findClassIndexes-          <*> Plan.using pgClass findClassSequence+            <*> Plan.use (fmap (indexBy pgAttributeName) attributes)+            <*> fmap (indexBy pgAttributeDefaultAttributeNumber) findClassAttributeDefaults+            <*> Plan.chain classAndAttributes findClassConstraints+            <*> Plan.chain classAndAttributes findClassIndexes+            <*> Plan.using pgClass findClassTriggers+            <*> Plan.using pgClass findClassSequence+            <*> fmap (Map.lookup objectSubIdZero) (Plan.use descriptions)+            <*> liftA2 mkAttributeCommentMap (Plan.use descriptions) (Plan.use attributes)  findRelation :: Plan.Plan scope (PgNamespace, RelationName) (Maybe PgClass) findRelation =-  Plan.focusParam (\(ns, relname) -> (pgNamespaceOid ns, relname)) $-    Plan.planOperation $-      Op.findOne pgClassTable byNamespaceOidAndRelationName+  Plan.focusParam (\(ns, relname) -> (pgNamespaceOid ns, relname))+    . Plan.planOperation+    $ Op.findOne pgClassTable byNamespaceOidAndRelationName  byNamespaceOidAndRelationName :: Op.WherePlanner (LibPQ.Oid, RelationName) byNamespaceOidAndRelationName =@@ -327,9 +391,9 @@         LibPQ.Oid 0 -> Nothing         nonZero -> Just nonZero   in-    Plan.focusParam relationId $-      Plan.planMaybe $-        Plan.findOne pgClassTable oidField+    Plan.focusParam relationId+      . Plan.planMaybe+      $ Plan.findOne pgClassTable oidField  findClassIndexes :: Plan.Plan scope PgClassAndAttributes [IndexDescription] findClassIndexes =@@ -429,6 +493,61 @@ findClassSequence =   Plan.focusParam pgClassOid $     Plan.findMaybeOne pgSequenceTable sequencePgClassOidField++findClassDescriptions :: Plan.Plan scope PgClass (Map.Map ObjectSubId T.Text)+findClassDescriptions =+  let+    mkObjectSubIdMap =+      Map.fromList+        . fmap+          ( \pgDescription ->+              (pgDescriptionObjSubId pgDescription, pgDescriptionDescription pgDescription)+          )+  in+    fmap mkObjectSubIdMap . Plan.focusParam pgClassOid $ Plan.findAll pgDescriptionTable objOidField++mkAttributeCommentMap :: Map.Map ObjectSubId T.Text -> [PgAttribute] -> Map.Map AttributeName T.Text+mkAttributeCommentMap descriptionMap =+  foldMap+    ( \attribute ->+        maybe+          mempty+          (Map.singleton (pgAttributeName attribute))+          ( Map.lookup+              (objectSubIdFromAttributeNumber $ pgAttributeNumber attribute)+              descriptionMap+          )+    )++findClassTriggers :: Plan.Plan scope PgClass [PgTrigger]+findClassTriggers =+  Plan.focusParam pgClassOid $+    Plan.findAll pgTriggerTable triggerRelationOidField++describeProcedureByName :: Plan.Plan scope (NamespaceName, ProcName) (Maybe PgProc)+describeProcedureByName =+  Plan.bind (fst <$> Plan.askParam) $ \namespaceName ->+    Plan.bind (snd <$> Plan.askParam) $ \procName ->+      Plan.bind (Plan.using namespaceName findNamespace) $ \namespace ->+        let+          namespaceAndProcName =+            (,) <$> Plan.use namespace <*> Plan.use procName+        in+          Plan.chain namespaceAndProcName findProc++findProc :: Plan.Plan scope (PgNamespace, ProcName) (Maybe PgProc)+findProc =+  Plan.focusParam (\(ns, procName) -> (pgNamespaceOid ns, procName))+    . Plan.planOperation+    $ Op.findOne pgProcTable byNamespaceOidAndProcName++byNamespaceOidAndProcName :: Op.WherePlanner (LibPQ.Oid, ProcName)+byNamespaceOidAndProcName =+  Op.byFieldTuple procNamespaceOidField procNameField++describeExtensionByName :: Plan.Plan scope ExtensionName (Maybe PgExtension)+describeExtensionByName =+  Plan.findMaybeOne pgExtensionTable extensionNameField  indexBy :: Ord key => (row -> key) -> [row] -> Map.Map key row indexBy rowKey =
src/Orville/PostgreSQL/PgCatalog/OidField.hs view
@@ -16,8 +16,7 @@ import qualified Orville.PostgreSQL as Orville import qualified Orville.PostgreSQL.Marshall.SqlType as SqlType -{- |-  The @oid@ field found on many (but not all!) @pg_catalog@ tables.+{- | The @oid@ field found on many (but not all!) @pg_catalog@ tables.  @since 1.0.0.0 -}@@ -25,8 +24,7 @@ oidField =   oidTypeField "oid" -{- |-  Builds a 'Orville.FieldDefinition' with the given column name that stores+{- | Builds a 'Orville.FieldDefinition' with the given column name that stores   an @oid@ value.  @since 1.0.0.0
src/Orville/PostgreSQL/PgCatalog/PgAttribute.hs view
@@ -37,10 +37,10 @@ import qualified Database.PostgreSQL.LibPQ as LibPQ  import qualified Orville.PostgreSQL as Orville+import qualified Orville.PostgreSQL.Marshall as Marshall import Orville.PostgreSQL.PgCatalog.OidField (oidTypeField) -{- |-  The Haskell representation of data read from the @pg_catalog.pg_attribute@+{- | The Haskell representation of data read from the @pg_catalog.pg_attribute@   table. Rows in this table correspond to table columns, but also to attributes   of other items from the @pg_class@ table. @@ -65,14 +65,15 @@   , pgAttributeTypeModifier :: Int32   -- ^ Type-specific data supplied at creation time, such as the maximum length   -- of a @varchar@ column.+  , pgAttributeIdentity :: Maybe Marshall.FieldIdentityGeneration+  -- ^ Indicates whether the column is an identity and how it is generated if at all.   , pgAttributeIsDropped :: Bool   -- ^ Indicates whether the column has been dropped and is not longer valid.   , pgAttributeIsNotNull :: Bool   -- ^ Indicates whether the column has a not-null constraint.   } -{- |-  Returns the maximum length for an attribute with a variable length type,+{- | Returns the maximum length for an attribute with a variable length type,   or 'Nothing' if the length of the type is not variable.  @since 1.0.0.0@@ -108,8 +109,7 @@               then Just typeMod               else Nothing -{- |-  Determines whether the attribute represents a system column by inspecting+{- | Determines whether the attribute represents a system column by inspecting   the attribute\'s 'AttributeNumber'. Ordinary columns have attribute numbers   starting at 1. @@ -119,17 +119,24 @@ isOrdinaryColumn attr =   pgAttributeNumber attr > AttributeNumber 0 -{- |-  A Haskell type for the name of the attribute represented by a 'PgAttribute'.+{- | A Haskell type for the name of the attribute represented by a 'PgAttribute'.  @since 1.0.0.0 -} newtype AttributeName   = AttributeName T.Text-  deriving (Show, Eq, Ord, String.IsString)+  deriving+    ( -- | @since 1.0.0.0+      Show+    , -- | @since 1.0.0.0+      Eq+    , -- | @since 1.0.0.0+      Ord+    , -- | @since 1.0.0.0+      String.IsString+    ) -{- |-  Converts an 'AttributeName' to a plain 'String'.+{- | Converts an 'AttributeName' to a plain 'String'.  @since 1.0.0.0 -}@@ -137,29 +144,44 @@ attributeNameToString (AttributeName txt) =   T.unpack txt -{- |-  A Haskell type for the number of the attribute represented by a 'PgAttribute'.+{- | A Haskell type for the number of the attribute represented by a 'PgAttribute'.  @since 1.0.0.0 -} newtype AttributeNumber   = AttributeNumber Int16-  deriving (Show, Eq, Ord, Enum, Num, Integral, Real)+  deriving+    ( -- | @since 1.0.0.0+      Show+    , -- | @since 1.0.0.0+      Eq+    , -- | @since 1.0.0.0+      Ord+    , -- | @since 1.0.0.0+      Enum+    , -- | @since 1.0.0.0+      Num+    , -- | @since 1.0.0.0+      Integral+    , -- | @since 1.0.0.0+      Real+    ) -{- |-  Converts an 'AttributeNumber' to an integer.+{- | Converts an 'AttributeNumber' to an integer.++@since 1.0.0.0 -} attributeNumberToInt16 :: AttributeNumber -> Int16 attributeNumberToInt16 (AttributeNumber int) = int -{- |-  Converts an integer to an 'AttributeNumber'.+{- | Converts an integer to an 'AttributeNumber'.++@since 1.0.0.0 -} attributeNumberFromInt16 :: Int16 -> AttributeNumber attributeNumberFromInt16 = AttributeNumber -{- |-  Attoparsec parser for 'AttributeNumber'.+{- | Attoparsec parser for 'AttributeNumber'.  @since 1.0.0.0 -}@@ -167,8 +189,7 @@ attributeNumberParser =   AttoText.signed AttoText.decimal -{- |-  Encodes an 'AttributeNumber' to lazy text as a builder.+{- | Encodes an 'AttributeNumber' to lazy text as a builder.  @since 1.0.0.0 -}@@ -176,8 +197,7 @@ attributeNumberTextBuilder =   LTBI.decimal . attributeNumberToInt16 -{- |-  An Orville 'Orville.TableDefinition' for querying the+{- | An Orville 'Orville.TableDefinition' for querying the   @pg_catalog.pg_attribute@ table.  @since 1.0.0.0@@ -198,11 +218,11 @@     <*> Orville.marshallField pgAttributeTypeOid attributeTypeOidField     <*> Orville.marshallField pgAttributeLength attributeLengthField     <*> Orville.marshallField pgAttributeTypeModifier attributeTypeModifierField+    <*> Orville.marshallField pgAttributeIdentity attributeIdentityField     <*> Orville.marshallField pgAttributeIsDropped attributeIsDroppedField     <*> Orville.marshallField pgAttributeIsNotNull attributeIsNotNullField -{- |-  The @attrelid@ column of the @pg_catalog.pg_attribute@ table.+{- | The @attrelid@ column of the @pg_catalog.pg_attribute@ table.  @since 1.0.0.0 -}@@ -210,8 +230,7 @@ attributeRelationOidField =   oidTypeField "attrelid" -{- |-  The @attname@ column of the @pg_catalog.pg_attribute@ table.+{- | The @attname@ column of the @pg_catalog.pg_attribute@ table.  @since 1.0.0.0 -}@@ -220,8 +239,7 @@   Orville.coerceField $     Orville.unboundedTextField "attname" -{- |-  The @attnum@ column of the @pg_catalog.pg_attribute@ table.+{- | The @attnum@ column of the @pg_catalog.pg_attribute@ table.  @since 1.0.0.0 -}@@ -229,8 +247,7 @@ attributeNumberField =   attributeNumberTypeField "attnum" -{- |-  Builds a 'Orville.FieldDefinition' for a field with type 'AttributeNumber'.+{- | Builds a 'Orville.FieldDefinition' for a field with type 'AttributeNumber'.  @since 1.0.0.0 -}@@ -238,8 +255,7 @@ attributeNumberTypeField =   Orville.coerceField . Orville.smallIntegerField -{- |-  The @atttypid@ column of the @pg_catalog.pg_attribute@ table.+{- | The @atttypid@ column of the @pg_catalog.pg_attribute@ table.  @since 1.0.0.0 -}@@ -247,8 +263,7 @@ attributeTypeOidField =   oidTypeField "atttypid" -{- |-  The @attlen@ column of the @pg_catalog.pg_attribute@ table.+{- | The @attlen@ column of the @pg_catalog.pg_attribute@ table.  @since 1.0.0.0 -}@@ -256,8 +271,7 @@ attributeLengthField =   Orville.smallIntegerField "attlen" -{- |-  The @atttypmod@ column of the @pg_catalog.pg_attribute@ table.+{- | The @atttypmod@ column of the @pg_catalog.pg_attribute@ table.  @since 1.0.0.0 -}@@ -265,20 +279,56 @@ attributeTypeModifierField =   Orville.integerField "atttypmod" -{- |-  The @attisdropped@ column of the @pg_catalog.pg_attribute@ table.+{- | The @attidentity@ column of the @pg_catalog.pg_attribute@ table. +@since 1.1.0.0+-}+attributeIdentityField ::+  Orville.FieldDefinition+    Orville.NotNull+    (Maybe Marshall.FieldIdentityGeneration)+attributeIdentityField =+  Orville.convertField+    (Orville.tryConvertSqlType columnIdentityToPgText pgTextToColumnIdentity)+    (Orville.fixedTextField "attidentity" 1)++{- | The @attisdropped@ column of the @pg_catalog.pg_attribute@ table.+ @since 1.0.0.0 -} attributeIsDroppedField :: Orville.FieldDefinition Orville.NotNull Bool attributeIsDroppedField =   Orville.booleanField "attisdropped" -{- |-  The @attnotnull@ column of the @pg_catalog.pg_attribute@ table.+{- | The @attnotnull@ column of the @pg_catalog.pg_attribute@ table.  @since 1.0.0.0 -} attributeIsNotNullField :: Orville.FieldDefinition Orville.NotNull Bool attributeIsNotNullField =   Orville.booleanField "attnotnull"++{- | Internal, convert a 'T.Text' to a 'Maybe Marshall.FieldIdentityGeneration', allowing for the empty text case+   to be Nothing.++@since 1.1.0.0+-}+pgTextToColumnIdentity :: T.Text -> Either String (Maybe Marshall.FieldIdentityGeneration)+pgTextToColumnIdentity text =+  case T.unpack text of+    "" -> Right Nothing+    "a" -> Right (Just Marshall.GeneratedAlways)+    "d" -> Right (Just Marshall.GeneratedByDefault)+    attid -> Left ("Unrecognized PostgreSQL attribute identity: " <> attid)++{- | Internal, convert a 'Maybe Expr.ColumnIdentity' to a 'T.Text', allowing for the Nothing case to+   be the empty text.++@since 1.1.0.0+-}+columnIdentityToPgText :: Maybe Marshall.FieldIdentityGeneration -> T.Text+columnIdentityToPgText attid =+  T.pack $ case attid of+    Nothing -> ""+    (Just Marshall.GeneratedAlways) -> "a"+    (Just Marshall.GeneratedByDefault) -> "d"
src/Orville/PostgreSQL/PgCatalog/PgAttributeDefault.hs view
@@ -1,5 +1,5 @@ {- |-Copyright : Flipstone Technology Partners 2023+Copyright : Flipstone Technology Partners 2023-2024 License   : MIT Stability : Stable @@ -16,13 +16,12 @@ import qualified Database.PostgreSQL.LibPQ as LibPQ  import qualified Orville.PostgreSQL as Orville+import qualified Orville.PostgreSQL.Marshall as Marshall import Orville.PostgreSQL.PgCatalog.OidField (oidField, oidTypeField) import Orville.PostgreSQL.PgCatalog.PgAttribute (AttributeNumber, attributeNumberTypeField) import qualified Orville.PostgreSQL.Raw.RawSql as RawSql-import qualified Orville.PostgreSQL.Raw.SqlValue as SqlValue -{- |-  The Haskell representation of data read from the @pg_catalog.pg_attrdef@+{- | The Haskell representation of data read from the @pg_catalog.pg_attrdef@   table.  @since 1.0.0.0@@ -41,8 +40,7 @@   -- @adbin@ column using the PostgreSQL @pg_get_expr@ function.   } -{- |-  An Orville 'Orville.TableDefinition' for querying the+{- | An Orville 'Orville.TableDefinition' for querying the   @pg_catalog.pg_attrdef@ table.  @since 1.0.0.0@@ -62,8 +60,7 @@     <*> Orville.marshallField pgAttributeDefaultAttributeNumber attributeDefaultAttributeNumberField     <*> Orville.marshallSyntheticField attributeDefaultExpressionField -{- |-  The @adrelid@ column of the @pg_catalog.pg_attrdef@ table.+{- | The @adrelid@ column of the @pg_catalog.pg_attrdef@ table.  @since 1.0.0.0 -}@@ -71,8 +68,7 @@ attributeDefaultRelationOidField =   oidTypeField "adrelid" -{- |-  The @adnum@ column of the @pg_catalog.pg_attrdef@ table.+{- | The @adnum@ column of the @pg_catalog.pg_attrdef@ table.  @since 1.0.0.0 -}@@ -80,8 +76,7 @@ attributeDefaultAttributeNumberField =   attributeNumberTypeField "adnum" -{- |-  A syntheticField for selecting the default expression by decompiling the+{- | A syntheticField for selecting the default expression by decompiling the   @adbin@ column of the @pg_catalog.pg_attrdef@ table. The @pg_node_tree@ found   in the column is decompiled by selecting the expression   @pg_get_expr(adbin,adrelid)@.@@ -93,4 +88,4 @@   Orville.syntheticField     (RawSql.unsafeSqlExpression "pg_get_expr(adbin,adrelid)")     "expression"-    SqlValue.toText+    Marshall.unboundedText
src/Orville/PostgreSQL/PgCatalog/PgClass.hs view
@@ -26,8 +26,7 @@ import qualified Orville.PostgreSQL as Orville import Orville.PostgreSQL.PgCatalog.OidField (oidField, oidTypeField) -{- |-  The Haskell representation of data read from the @pg_catalog.pg_class@+{- | The Haskell representation of data read from the @pg_catalog.pg_class@   table. Rows in this table correspond to tables, indexes, sequences, views,   materialized views, composite types and TOAST tables. @@ -45,17 +44,24 @@   -- ^ The kind of relation (table, view, etc).   } -{- |-  A Haskell type for the name of the relation represented by a 'PgClass'.+{- | A Haskell type for the name of the relation represented by a 'PgClass'.  @since 1.0.0.0 -} newtype RelationName   = RelationName T.Text-  deriving (Show, Eq, Ord, String.IsString)+  deriving+    ( -- | @since 1.0.0.0+      Show+    , -- | @since 1.0.0.0+      Eq+    , -- | @since 1.0.0.0+      Ord+    , -- | @since 1.0.0.0+      String.IsString+    ) -{- |-  Convert a 'RelationName' to a plain 'String'.+{- | Convert a 'RelationName' to a plain 'String'.  @since 1.0.0.0 -}@@ -63,8 +69,7 @@ relationNameToString (RelationName text) =   T.unpack text -{- |-  The kind of relation represented by a 'PgClass', as described at+{- | The kind of relation represented by a 'PgClass', as described at   https://www.postgresql.org/docs/13/catalog-pg-class.html.  @since 1.0.0.0@@ -80,10 +85,14 @@   | ForeignTable   | PartitionedTable   | PartitionedIndex-  deriving (Show, Eq)+  deriving+    ( -- | @since 1.0.0.0+      Show+    , -- | @since 1.0.0.0+      Eq+    ) -{- |-  An Orville 'Orville.TableDefinition' for querying the+{- | An Orville 'Orville.TableDefinition' for querying the   @pg_catalog.pg_class@ table.  @since 1.0.0.0@@ -104,8 +113,7 @@     <*> Orville.marshallField pgClassRelationName relationNameField     <*> Orville.marshallField pgClassRelationKind relationKindField -{- |-  The @relnamespace@ column of the @pg_catalog.pg_class@ table.+{- | The @relnamespace@ column of the @pg_catalog.pg_class@ table.  @since 1.0.0.0 -}@@ -113,8 +121,7 @@ namespaceOidField =   oidTypeField "relnamespace" -{- |-  The @relname@ column of the @pg_catalog.pg_class@ table.+{- | The @relname@ column of the @pg_catalog.pg_class@ table.  @since 1.0.0.0 -}@@ -123,8 +130,7 @@   Orville.coerceField $     Orville.unboundedTextField "relname" -{- |-  The @relkind@ column of the @pg_catalog.pg_class@ table.+{- | The @relkind@ column of the @pg_catalog.pg_class@ table.  @since 1.0.0.0 -}@@ -134,8 +140,7 @@     (Orville.tryConvertSqlType relationKindToPgText pgTextToRelationKind)     (Orville.unboundedTextField "relkind") -{- |-  Converts a 'RelationKind' to the corresponding single character text+{- | Converts a 'RelationKind' to the corresponding single character text   representation used by PostgreSQL.    See also 'pgTextToRelationKind'@@ -157,8 +162,7 @@       PartitionedTable -> "p"       PartitionedIndex -> "I" -{- |-  Attempts to parse a PostgreSQL single character textual value as a+{- | Attempts to parse a PostgreSQL single character textual value as a   'RelationKind'.    See also 'relationKindToPgText'
src/Orville/PostgreSQL/PgCatalog/PgConstraint.hs view
@@ -29,8 +29,7 @@ import Orville.PostgreSQL.PgCatalog.OidField (oidField, oidTypeField) import Orville.PostgreSQL.PgCatalog.PgAttribute (AttributeNumber, attributeNumberParser, attributeNumberTextBuilder) -{- |-  The Haskell representation of data read from the @pg_catalog.pg_constraint@+{- | The Haskell representation of data read from the @pg_catalog.pg_constraint@   table. Rows in this table correspond to check, primary key, unique, foreign   key and exclusion constraints on tables. @@ -69,18 +68,25 @@   -- ^ For foreign key constraints, the on delete action type.   } -{- |-  A Haskell type for the name of the constraint represented by a+{- | A Haskell type for the name of the constraint represented by a   'PgConstraint'.  @since 1.0.0.0 -} newtype ConstraintName   = ConstraintName T.Text-  deriving (Show, Eq, Ord, String.IsString)+  deriving+    ( -- | @since 1.0.0.0+      Show+    , -- | @since 1.0.0.0+      Eq+    , -- | @since 1.0.0.0+      Ord+    , -- | @since 1.0.0.0+      String.IsString+    ) -{- |-  Converts a 'ConstraintName' to a plain 'String'.+{- | Converts a 'ConstraintName' to a plain 'String'.  @since 1.0.0.0 -}@@ -88,8 +94,7 @@ constraintNameToString (ConstraintName txt) =   T.unpack txt -{- |-  The type of constraint that a 'PgConstraint' represents, as described at+{- | The type of constraint that a 'PgConstraint' represents, as described at   https://www.postgresql.org/docs/13/catalog-pg-constraint.html.  @since 1.0.0.0@@ -101,10 +106,14 @@   | UniqueConstraint   | ConstraintTrigger   | ExclusionConstraint-  deriving (Show, Eq)+  deriving+    ( -- | @since 1.0.0.0+      Show+    , -- | @since 1.0.0.0+      Eq+    ) -{- |-  Converts a 'ConstraintType' to the corresponding single character text+{- | Converts a 'ConstraintType' to the corresponding single character text   representation used by PostgreSQL.    See also 'pgTextToConstraintType'@@ -122,8 +131,7 @@       ConstraintTrigger -> "t"       ExclusionConstraint -> "x" -{- |-  Attempts to parse a PostgreSQL single character textual value as a+{- | Attempts to parse a PostgreSQL single character textual value as a   'ConstraintType'    See also 'constraintTypeToPgText'@@ -141,8 +149,7 @@     "x" -> Right ExclusionConstraint     typ -> Left ("Unrecognized PostgreSQL constraint type: " <> typ) -{- |-  Converts a 'Maybe Orville.ForeignKeyAction' to the corresponding single character+{- | Converts a 'Maybe Orville.ForeignKeyAction' to the corresponding single character   text representation used by PostgreSQL.    See also 'pgTextToForeignKeyAction'@@ -160,8 +167,7 @@       Just Orville.SetDefault -> "d"       Nothing -> " " -{- |-  Attempts to parse a PostgreSQL single character textual value as a+{- | Attempts to parse a PostgreSQL single character textual value as a   'Maybe Orville.ForeignKeyAction'    See also 'foreignKeyActionToPgText'@@ -179,8 +185,7 @@     " " -> Right Nothing     typ -> Left ("Unrecognized PostgreSQL foreign key action type: " <> typ) -{- |-  An Orville 'Orville.TableDefinition' for querying the+{- | An Orville 'Orville.TableDefinition' for querying the   @pg_catalog.pg_constraint@ table.  @since 1.0.0.0@@ -208,8 +213,7 @@     <*> Orville.marshallField pgConstraintForeignKeyOnUpdateType constraintForeignKeyOnUpdateTypeField     <*> Orville.marshallField pgConstraintForeignKeyOnDeleteType constraintForeignKeyOnDeleteTypeField -{- |-  The @conname@ column of the @pg_constraint@ table.+{- | The @conname@ column of the @pg_constraint@ table.  @since 1.0.0.0 -}@@ -218,8 +222,7 @@   Orville.coerceField $     Orville.unboundedTextField "conname" -{- |-  The @connamespace@ column of the @pg_constraint@ table.+{- | The @connamespace@ column of the @pg_constraint@ table.  @since 1.0.0.0 -}@@ -227,8 +230,7 @@ constraintNamespaceOidField =   oidTypeField "connamespace" -{- |-  The @contype@ column of the @pg_constraint@ table.+{- | The @contype@ column of the @pg_constraint@ table.  @since 1.0.0.0 -}@@ -238,8 +240,7 @@     (Orville.tryConvertSqlType constraintTypeToPgText pgTextToConstraintType)     (Orville.unboundedTextField "contype") -{- |-  The @conrelid@ column of the @pg_constraint@ table.+{- | The @conrelid@ column of the @pg_constraint@ table.  @since 1.0.0.0 -}@@ -247,8 +248,7 @@ constraintRelationOidField =   oidTypeField "conrelid" -{- |-  The @conindid@ column of the @pg_constraint@ table.+{- | The @conindid@ column of the @pg_constraint@ table.  @since 1.0.0.0 -}@@ -256,8 +256,7 @@ constraintIndexOidField =   oidTypeField "conindid" -{- |-  The @conkey@ column of the @pg_constraint@ table.+{- | The @conkey@ column of the @pg_constraint@ table.  @since 1.0.0.0 -}@@ -268,8 +267,7 @@       (Orville.tryConvertSqlType attributeNumberListToPgArrayText pgArrayTextToAttributeNumberList)       (Orville.unboundedTextField "conkey") -{- |-  The @confrelid@ column of the @pg_constraint@ table.+{- | The @confrelid@ column of the @pg_constraint@ table.  @since 1.0.0.0 -}@@ -277,8 +275,7 @@ constraintForeignRelationOidField =   oidTypeField "confrelid" -{- |-  The @confkey@ column of the @pg_constraint@ table.+{- | The @confkey@ column of the @pg_constraint@ table.  @since 1.0.0.0 -}
+ src/Orville/PostgreSQL/PgCatalog/PgDescription.hs view
@@ -0,0 +1,103 @@+{- |+Copyright : Flipstone Technology Partners 2023+License   : MIT+Stability : Stable++@since 1.1.0.0+-}+module Orville.PostgreSQL.PgCatalog.PgDescription+  ( PgDescription (..)+  , ObjectSubId+  , objectSubIdFromAttributeNumber+  , objectSubIdZero+  , pgDescriptionTable+  , objOidField+  , objSubIdField+  , descriptionField+  ) where++import Data.Int (Int32)+import qualified Data.Text as T+import qualified Database.PostgreSQL.LibPQ as LibPQ++import qualified Orville.PostgreSQL as Orville+import Orville.PostgreSQL.PgCatalog.OidField (oidTypeField)+import Orville.PostgreSQL.PgCatalog.PgAttribute (AttributeNumber, attributeNumberToInt16)++{- | The Haskell representation of data read from the @pg_catalog.pg_description@+  table.++@since 1.1.0.0+-}+data PgDescription = PgDescription+  { pgDescriptionObjOid :: LibPQ.Oid+  -- ^ @since 1.1.0.0+  , pgDescriptionClassOid :: LibPQ.Oid+  -- ^ @since 1.1.0.0+  , pgDescriptionObjSubId :: ObjectSubId+  -- ^ @since 1.1.0.0+  , pgDescriptionDescription :: T.Text+  -- ^ @since 1.1.0.0+  }++{- | Represents the value in the @objsubid@ field. For tables, this corresponds to the+  'AttributeNumber' of the column and indicates that the @description@ field contains+  the comment for that column.++  Note that the @objsubid@ field is an @int4@ as opposed to an @int2@ like the @attnum@+  field in @pg_attribute@.++@since 1.1.0.0+-}+newtype ObjectSubId = ObjectSubId Int32+  deriving+    ( -- | @since 1.1.0.0+      Eq+    , -- | @since 1.1.0.0+      Ord+    )++{- | Convert an 'AttributeNumber' to an 'ObjectSubId'++@since 1.1.0.0+-}+objectSubIdFromAttributeNumber :: AttributeNumber -> ObjectSubId+objectSubIdFromAttributeNumber = ObjectSubId . fromIntegral . attributeNumberToInt16++{- | A 'ObjectSubId' of 0 in the @objsubid@ field for a table corresponds to the+  comment on the table.++@since 1.1.0.0+-}+objectSubIdZero :: ObjectSubId+objectSubIdZero = ObjectSubId 0++-- | @since 1.1.0.0+pgDescriptionTable :: Orville.TableDefinition (Orville.HasKey LibPQ.Oid) PgDescription PgDescription+pgDescriptionTable =+  Orville.mkTableDefinition "pg_description" (Orville.primaryKey objOidField) pgDescriptionMarshaller++-- | @since 1.1.0.0+pgDescriptionMarshaller :: Orville.SqlMarshaller PgDescription PgDescription+pgDescriptionMarshaller =+  PgDescription+    <$> Orville.marshallField pgDescriptionObjOid objOidField+    <*> Orville.marshallField pgDescriptionClassOid classOidField+    <*> Orville.marshallField pgDescriptionObjSubId objSubIdField+    <*> Orville.marshallField pgDescriptionDescription descriptionField++-- | @since 1.1.0.0+objOidField :: Orville.FieldDefinition Orville.NotNull LibPQ.Oid+objOidField = oidTypeField "objoid"++-- | @since 1.1.0.0+classOidField :: Orville.FieldDefinition Orville.NotNull LibPQ.Oid+classOidField = oidTypeField "classoid"++-- | @since 1.1.0.0+objSubIdField :: Orville.FieldDefinition Orville.NotNull ObjectSubId+objSubIdField = Orville.coerceField $ Orville.integerField "objsubid"++-- | @since 1.1.0.0+descriptionField :: Orville.FieldDefinition Orville.NotNull T.Text+descriptionField = Orville.unboundedTextField "description"
+ src/Orville/PostgreSQL/PgCatalog/PgExtension.hs view
@@ -0,0 +1,74 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++{- |+Copyright : Flipstone Technology Partners 2024+License   : MIT+Stability : Stable++@since 1.1.0.0+-}+module Orville.PostgreSQL.PgCatalog.PgExtension+  ( PgExtension (..)+  , ExtensionName+  , pgExtensionTable+  , extensionNameField+  )+where++import qualified Data.String as String+import qualified Data.Text as T++import qualified Orville.PostgreSQL as Orville++{- | The Haskell representation of data read from the @pg_catalog.pg_extension@ table.+  Rows in this table contain extended information about extensions.++@since 1.1.0.0+-}+newtype PgExtension = PgExtension+  { pgExtensionName :: ExtensionName+  -- ^ The PostgreSQL name for this extension.+  }++{- | A Haskell type for the name of the extension represented by a 'PgExtension'.++@since 1.1.0.0+-}+newtype ExtensionName+  = ExtensionName T.Text+  deriving+    ( -- | @since 1.1.0.0+      Show+    , -- | @since 1.1.0.0+      Eq+    , -- | @since 1.1.0.0+      Ord+    , -- | @since 1.1.0.0+      String.IsString+    )++{- | An Orville 'Orville.TableDefinition' for querying the+  @pg_catalog.pg_extension@ table.++@since 1.0.0.0+-}+pgExtensionTable :: Orville.TableDefinition Orville.NoKey PgExtension PgExtension+pgExtensionTable =+  Orville.setTableSchema "pg_catalog" $+    Orville.mkTableDefinitionWithoutKey+      "pg_extension"+      pgExtensionMarshaller++pgExtensionMarshaller :: Orville.SqlMarshaller PgExtension PgExtension+pgExtensionMarshaller =+  PgExtension+    <$> Orville.marshallField pgExtensionName extensionNameField++{- | The @extname@ column of the @pg_extension@ table.++@since 1.1.0.0+-}+extensionNameField :: Orville.FieldDefinition Orville.NotNull ExtensionName+extensionNameField =+  Orville.coerceField $+    Orville.boundedTextField "extname" 63
src/Orville/PostgreSQL/PgCatalog/PgIndex.hs view
@@ -24,8 +24,7 @@ import Orville.PostgreSQL.PgCatalog.OidField (oidTypeField) import Orville.PostgreSQL.PgCatalog.PgAttribute (AttributeNumber, attributeNumberParser, attributeNumberTextBuilder) -{- |-  The Haskell representation of data read from the @pg_catalog.pg_index@ table.+{- | The Haskell representation of data read from the @pg_catalog.pg_index@ table.   Rows in this table contain extended information about indices. Information   about indices is also contained in the @pg_catalog.pg_class@ table as well. @@ -54,8 +53,7 @@   -- dropped and should be ignored.   } -{- |-  An Orville 'Orville.TableDefinition' for querying the+{- | An Orville 'Orville.TableDefinition' for querying the   @pg_catalog.pg_index@ table.  @since 1.0.0.0@@ -77,8 +75,7 @@     <*> Orville.marshallField pgIndexIsPrimary indexIsPrimaryField     <*> Orville.marshallField pgIndexIsLive indexIsLiveField -{- |-  The @indexrelid@ column of the @pg_index@ table.+{- | The @indexrelid@ column of the @pg_index@ table.  @since 1.0.0.0 -}@@ -86,8 +83,7 @@ indexPgClassOidField =   oidTypeField "indexrelid" -{- |-  The @indrelid@ column of the @pg_index@ table.+{- | The @indrelid@ column of the @pg_index@ table.  @since 1.0.0.0 -}@@ -95,8 +91,7 @@ indexRelationOidField =   oidTypeField "indrelid" -{- |-  The @indkey@ column of the @pg_index@ table.+{- | The @indkey@ column of the @pg_index@ table.  @since 1.0.0.0 -}@@ -106,8 +101,7 @@     (Orville.tryConvertSqlType attributeNumberListToPgVectorText pgVectorTextToAttributeNumberList)     (Orville.unboundedTextField "indkey") -{- |-  The @indisunique@ column of the @pg_index@ table.+{- | The @indisunique@ column of the @pg_index@ table.  @since 1.0.0.0 -}@@ -115,8 +109,7 @@ indexIsUniqueField =   Orville.booleanField "indisunique" -{- |-  The @indisprimary@ column of the @pg_index@ table.+{- | The @indisprimary@ column of the @pg_index@ table.  @since 1.0.0.0 -}@@ -124,8 +117,7 @@ indexIsPrimaryField =   Orville.booleanField "indisprimary" -{- |-  The @indislive@ column of the @pg_index@ table.+{- | The @indislive@ column of the @pg_index@ table.  @since 1.0.0.0 -}
src/Orville/PostgreSQL/PgCatalog/PgNamespace.hs view
@@ -23,8 +23,7 @@ import qualified Orville.PostgreSQL as Orville import Orville.PostgreSQL.PgCatalog.OidField (oidField) -{- |-  The Haskell representation of data read from the @pg_catalog.pg_namespace@+{- | The Haskell representation of data read from the @pg_catalog.pg_namespace@   table. Namespaces in @pg_catalog@ correspond to "schema" concept in database   organization. @@ -38,17 +37,24 @@   -- ^ The name of the namespace.   } -{- |-  A Haskell type for the name of a namespace.+{- | A Haskell type for the name of a namespace.  @since 1.0.0.0 -} newtype NamespaceName   = NamespaceName T.Text-  deriving (Show, Eq, Ord, String.IsString)+  deriving+    ( -- | @since 1.0.0.0+      Show+    , -- | @since 1.0.0.0+      Eq+    , -- | @since 1.0.0.0+      Ord+    , -- | @since 1.0.0.0+      String.IsString+    ) -{- |-  Convert a 'NamespaceName' to a plain 'String'.+{- | Convert a 'NamespaceName' to a plain 'String'.  @since 1.0.0.0 -}@@ -56,8 +62,7 @@ namespaceNameToString (NamespaceName text) =   T.unpack text -{- |-  An Orville 'Orville.TableDefinition' for querying the+{- | An Orville 'Orville.TableDefinition' for querying the   @pg_catalog.pg_namespace@ table.  @since 1.0.0.0@@ -76,8 +81,7 @@     <$> Orville.marshallField pgNamespaceOid oidField     <*> Orville.marshallField pgNamespaceName namespaceNameField -{- |-  The @nspname@ column of the @pg_catalog.pg_namespace@ table.+{- | The @nspname@ column of the @pg_catalog.pg_namespace@ table.  @since 1.0.0.0 -}
+ src/Orville/PostgreSQL/PgCatalog/PgProc.hs view
@@ -0,0 +1,106 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++{- |+Copyright : Flipstone Technology Partners 2024+License   : MIT+Stability : Stable++@since 1.1.0.0+-}+module Orville.PostgreSQL.PgCatalog.PgProc+  ( PgProc (..)+  , ProcName+  , pgProcTable+  , procNameField+  , procNamespaceOidField+  ) where++import qualified Data.String as String+import qualified Data.Text as T++import qualified Database.PostgreSQL.LibPQ as LibPQ+import qualified Orville.PostgreSQL as Orville+import Orville.PostgreSQL.PgCatalog.OidField (oidField, oidTypeField)++{- | The Haskell representation of data read from the @pg_catalog.pg_proc@ table.+  Rows in this table reperesent functions, procedures, aggregate functions, and+  window functions (collectively also known as routines), such as those created+  via @CREATE FUNCTION@ or @CREATE PROCEDUCE.++@since 1.1.0.0+-}+data PgProc = PgProc+  { pgProcOid :: LibPQ.Oid+  -- ^ The PostgreSQL @oid@ for the relation.+  , pgProcNamespaceOid :: LibPQ.Oid+  -- ^ The PostgreSQL @oid@ of the namespace that the relation belongs to.+  -- References @pg_namespace.oid@.+  , pgProcName :: ProcName+  -- ^ The name of the proceduce or function.+  , pgProcSource :: T.Text+  -- ^ How the function is executed by the PostgreSQL function handler. This+  -- made the actual source code for interpreted languages, but could be almost+  -- anything depending on the language.+  }++{- | A Haskell type for the name of the trigger represented by a 'PgProc'.++@since 1.1.0.0+-}+newtype ProcName+  = ProcName T.Text+  deriving+    ( -- | @since 1.1.0.0+      Show+    , -- | @since 1.1.0.0+      Eq+    , -- | @since 1.1.0.0+      Ord+    , -- | @since 1.1.0.0+      String.IsString+    )++{- | An Orville 'Orville.TableDefinition' for querying the+  @pg_catalog.pg_proc@ table.++@since 1.1.0.0+-}+pgProcTable :: Orville.TableDefinition Orville.NoKey PgProc PgProc+pgProcTable =+  Orville.setTableSchema "pg_catalog" $+    Orville.mkTableDefinitionWithoutKey+      "pg_proc"+      pgProcMarshaller++pgProcMarshaller :: Orville.SqlMarshaller PgProc PgProc+pgProcMarshaller =+  PgProc+    <$> Orville.marshallField pgProcOid oidField+    <*> Orville.marshallField pgProcNamespaceOid procNamespaceOidField+    <*> Orville.marshallField pgProcName procNameField+    <*> Orville.marshallField pgProcSource procSourceField++{- | The @pronamespace@ column of the @pg_catalog.pg_proc@ table.++@since 1.0.0.0+-}+procNamespaceOidField :: Orville.FieldDefinition Orville.NotNull LibPQ.Oid+procNamespaceOidField =+  oidTypeField "pronamespace"++{- | The @proname@ column of the @pg_catalog.pg_proc@ table.++@since 1.1.0.0+-}+procNameField :: Orville.FieldDefinition Orville.NotNull ProcName+procNameField =+  Orville.coerceField $+    Orville.unboundedTextField "proname"++{- | The @prosrc@ column of the @pg_catalog.pg_proc@ table.++@since 1.1.0.0+-}+procSourceField :: Orville.FieldDefinition Orville.NotNull T.Text+procSourceField =+  Orville.unboundedTextField "prosrc"
src/Orville/PostgreSQL/PgCatalog/PgSequence.hs view
@@ -1,5 +1,3 @@-{-# LANGUAGE GeneralizedNewtypeDeriving #-}- {- | Copyright : Flipstone Technology Partners 2023 License   : MIT@@ -20,8 +18,7 @@ import qualified Orville.PostgreSQL as Orville import Orville.PostgreSQL.PgCatalog.OidField (oidField, oidTypeField) -{- |-  The Haskell representation of data read from the @pg_catalog.pg_sequence@+{- | The Haskell representation of data read from the @pg_catalog.pg_sequence@   table. Rows in this table are sequences in PostgreSQL.  @since 1.0.0.0@@ -46,8 +43,7 @@   -- ^ Whether the sequence cycles.   } -{- |-  An Orville 'Orville.TableDefinition' for querying the+{- | An Orville 'Orville.TableDefinition' for querying the   @pg_catalog.pg_sequence@ table.  @since 1.0.0.0@@ -72,8 +68,7 @@     <*> Orville.marshallField pgSequenceCache sequenceCacheField     <*> Orville.marshallField pgSequenceCycle sequenceCycleField -{- |-  The @seqrelid@ column of the @pg_cataglog.pg_sequence@ table.+{- | The @seqrelid@ column of the @pg_cataglog.pg_sequence@ table.  @since 1.0.0.0 -}@@ -81,8 +76,7 @@ sequencePgClassOidField =   oidTypeField "seqrelid" -{- |-  The @seqtypid@ column of the @pg_catalog.pg_sequence@ table.+{- | The @seqtypid@ column of the @pg_catalog.pg_sequence@ table.  @since 1.0.0.0 -}@@ -90,8 +84,7 @@ sequenceTypeOidField =   oidTypeField "seqtypid" -{- |-  The @seqstart@ column of the @pg_catalog.pg_sequence@ table.+{- | The @seqstart@ column of the @pg_catalog.pg_sequence@ table.  @since 1.0.0.0 -}@@ -99,8 +92,7 @@ sequenceStartField =   Orville.bigIntegerField "seqstart" -{- |-  The @seqincrement@ column of the @pg_catalog.pg_sequence@ table.+{- | The @seqincrement@ column of the @pg_catalog.pg_sequence@ table.  @since 1.0.0.0 -}@@ -108,8 +100,7 @@ sequenceIncrementField =   Orville.bigIntegerField "seqincrement" -{- |-  The @seqmax@ column of the @pg_catalog.pg_sequence@ table.+{- | The @seqmax@ column of the @pg_catalog.pg_sequence@ table.  @since 1.0.0.0 -}@@ -117,8 +108,7 @@ sequenceMaxField =   Orville.bigIntegerField "seqmax" -{- |-  The @seqmin@ column of the @pg_catalog.pg_sequence@ table.+{- | The @seqmin@ column of the @pg_catalog.pg_sequence@ table.  @since 1.0.0.0 -}@@ -126,8 +116,7 @@ sequenceMinField =   Orville.bigIntegerField "seqmin" -{- |-  The @seqcache@ column of the @pg_catalog.pg_sequence@ table.+{- | The @seqcache@ column of the @pg_catalog.pg_sequence@ table.  @since 1.0.0.0 -}@@ -135,8 +124,7 @@ sequenceCacheField =   Orville.bigIntegerField "seqcache" -{- |-  The @seqcycle@ column of the @pg_catalog.pg_sequence@ table.+{- | The @seqcycle@ column of the @pg_catalog.pg_sequence@ table.  @since 1.0.0.0 -}
+ src/Orville/PostgreSQL/PgCatalog/PgTrigger.hs view
@@ -0,0 +1,109 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++{- |+Copyright : Flipstone Technology Partners 2024+License   : MIT+Stability : Stable++@since 1.1.0.0+-}+module Orville.PostgreSQL.PgCatalog.PgTrigger+  ( PgTrigger (..)+  , TriggerName+  , triggerNameToString+  , pgTriggerTable+  , triggerRelationOidField+  ) where++import qualified Data.String as String+import qualified Data.Text as T++import qualified Database.PostgreSQL.LibPQ as LibPQ+import qualified Orville.PostgreSQL as Orville+import Orville.PostgreSQL.PgCatalog.OidField (oidField, oidTypeField)++{- | The Haskell representation of data read from the @pg_catalog.pg_trigger@+  table. Rows in this table correspond to triggers created on tables and views.+@since 1.1.0.0+-}+data PgTrigger = PgTrigger+  { pgTriggerOid :: LibPQ.Oid+  -- ^ The PostgreSQL @oid@ for the relation.+  , pgTriggerRelationOid :: LibPQ.Oid+  -- ^ The PostgreSQL @oid@ of the relation that the trigger is onto.+  -- References @pg_class.oid@.+  , pgTriggerName :: TriggerName+  -- ^ The name of the trigger.+  , pgTriggerIsInternal :: Bool+  -- ^ The whether the trigger is internal accounting to PostgreSQL.+  }++{- | A Haskell type for the name of the trigger represented by a 'PgTrigger'.++@since 1.1.0.0+-}+newtype TriggerName+  = TriggerName T.Text+  deriving+    ( -- | @since 1.1.0.0+      Show+    , -- | @since 1.1.0.0+      Eq+    , -- | @since 1.1.0.0+      Ord+    , -- | @since 1.1.0.0+      String.IsString+    )++{- | Convert a 'TriggerName' to a plain 'String'.++@since 1.1.0.0+-}+triggerNameToString :: TriggerName -> String+triggerNameToString (TriggerName name) =+  T.unpack name++{- | An Orville 'Orville.TableDefinition' for querying the+  @pg_catalog.pg_trigger@ table.++@since 1.1.0.0+-}+pgTriggerTable :: Orville.TableDefinition Orville.NoKey PgTrigger PgTrigger+pgTriggerTable =+  Orville.setTableSchema "pg_catalog" $+    Orville.mkTableDefinitionWithoutKey+      "pg_trigger"+      pgTriggerMarshaller++pgTriggerMarshaller :: Orville.SqlMarshaller PgTrigger PgTrigger+pgTriggerMarshaller =+  PgTrigger+    <$> Orville.marshallField pgTriggerOid oidField+    <*> Orville.marshallField pgTriggerRelationOid triggerRelationOidField+    <*> Orville.marshallField pgTriggerName triggerNameField+    <*> Orville.marshallField pgTriggerIsInternal triggerIsInternalField++{- | The @tgrelid@ column of the @pg_trigger@ table.++@since 1.1.0.0+-}+triggerRelationOidField :: Orville.FieldDefinition Orville.NotNull LibPQ.Oid+triggerRelationOidField =+  oidTypeField "tgrelid"++{- | The @tgname@ column of the @pg_catalog.pg_trigger@ table.++@since 1.1.0.0+-}+triggerNameField :: Orville.FieldDefinition Orville.NotNull TriggerName+triggerNameField =+  Orville.coerceField $+    Orville.unboundedTextField "tgname"++{- | The @tgisinternal@ column of the @pg_catalog.pg_trigger@ table.++@since 1.1.0.0+-}+triggerIsInternalField :: Orville.FieldDefinition Orville.NotNull Bool+triggerIsInternalField =+  Orville.booleanField "tgisinternal"
src/Orville/PostgreSQL/Plan.hs view
@@ -1,5 +1,6 @@ {-# LANGUAGE GADTs #-} {-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}  {- | Copyright : Flipstone Technology Partners 2023@@ -21,13 +22,21 @@      -- * Making a Plan to find rows in the database   , findMaybeOne+  , findMaybeOneByMarshaller   , findMaybeOneWhere+  , findMaybeOneWhereByMarshaller   , findOne+  , findOneByMarshaller   , findOneShowVia+  , findOneShowViaByMarshaller   , findOneWhere+  , findOneWhereByMarshaller   , findOneWhereShowVia+  , findOneWhereShowViaByMarshaller   , findAll+  , findAllByMarshaller   , findAllWhere+  , findAllWhereByMarshaller      -- * Creating a multi-step Plan from other Plan values   , bind@@ -38,6 +47,7 @@   , apply   , planMany   , planList+  , planTraversable   , focusParam   , planEither   , planMaybe@@ -53,7 +63,9 @@ import Control.Exception (throwIO) import Control.Monad (join) import qualified Control.Monad.IO.Class as MIO+import qualified Data.Bifunctor as Bifunctor import Data.Either (partitionEithers)+import qualified Data.Foldable as Foldable import qualified Data.List.NonEmpty as NEL  import Orville.PostgreSQL.Execution (Select)@@ -66,34 +78,28 @@ import qualified Orville.PostgreSQL.Plan.Operation as Op import qualified Orville.PostgreSQL.Schema as Schema -{- |-  A 'Plan' is an executable set of queries that can be executed to load data-  from the database, using the results of prior queries as input parameters to-  following queries in controlled ways. In particular, the "controlled" aspect-  of this allows plans that take a single input to be adapted to take multiple-  input parameters in a list without the resulting plan executing N+1 queries.-  This restriction means that while query results can be used as input-  parameters to later queries, they cannot be used to decide to run completely-  different queries based on other query results. Allowing this would prevent-  the 'Plan' structure from eliminating N+1 query loops.+{- | A 'Plan' is an executable set of queries that can be executed to load data from the database,+  using the results of prior queries as input parameters to following queries in controlled ways. In+  particular, the "controlled" aspect of this allows plans that take a single input to be adapted to+  take multiple input parameters in a list without the resulting plan executing N+1 queries.  This+  restriction means that while query results can be used as input parameters to later queries, they+  cannot be used to decide to run completely different queries based on other query+  results. Allowing this would prevent the 'Plan' structure from eliminating N+1 query loops. -  Note that during execution, queries are never combined across tables to form-  joins or subqueries. Queries are still executed in the same sequence as-  specified in the plan, just on all the inputs at once rather than in a loop.-  If you need to do a join with a plan, you can always construct your own-  custom 'Op.Operation' and use 'planOperation' to incorporate it into a plan.+  Note that during execution, queries are never combined across tables to form joins or+  subqueries. Queries are still executed in the same sequence as specified in the plan, just on all+  the inputs at once rather than in a loop.  If you need to do a join with a plan, you can always+  construct your own custom 'Op.Operation' and use 'planOperation' to incorporate it into a plan. -  The @param@ type variable indicates what type of value is expected as input-  when the plan is executed.+  The @param@ type variable indicates what type of value is expected as input when the plan is+  executed. -  The @result@ type for a plan indicates what Haskell type is produced-  when the plan is executed.+  The @result@ type for a plan indicates what Haskell type is produced when the plan is executed. -  The @scope@ type is used internally by Orville to track how the plan is-  currently executed against a single input or multiple inputs. This type-  parameter should never be specified as a concrete type in user code, but must-  be exposed as a variable to ensure that execute scope is tracked correctly-  through usages of 'bind'.+  The @scope@ type is used internally by Orville to track how the plan is currently executed against+  a single input or multiple inputs. This type parameter should never be specified as a concrete+  type in user code, but must be exposed as a variable to ensure that execute scope is tracked+  correctly through usages of 'bind'.  @since 1.0.0.0 -}@@ -121,45 +127,40 @@     Plan scope b c ->     Plan scope a c +-- | @since 1.0.0.0 instance Functor (Plan scope param) where   fmap f = Apply (Pure f) +-- | @since 1.0.0.0 instance Applicative (Plan scope param) where   pure = Pure   (<*>) = Apply -{- |-  'Execute' is a tag type used as the @scope@ variable for 'Plan' values when-  executing them via the 'execute' function.+{- | 'Execute' is a tag type used as the @scope@ variable for 'Plan' values when executing them via+  the 'execute' function.  @since 1.0.0.0 -} data Execute -{- |-  'ExecuteMany' is an internal tag type used by as the @scope@ variable for-  'Plan' values when executing them against multiple inputs via the-  'executeMany' internal function.+{- | 'ExecuteMany' is an internal tag type used by as the @scope@ variable for 'Plan' values when+  executing them against multiple inputs via the 'executeMany' internal function.  @since 1.0.0.0 -} data ExecuteMany -{- |-  A 'Planned' value is a wrapper around the results of previously-run queries-  when using the 'bind' function. At the time that you are writing a plan, you-  do not know whether the 'Plan' will be run with a single input or multiple-  inputs. A 'Planned' value may end up being either an individual item or a-  list of items. Due to this, your ability to interact with the value is-  limited to the use of 'fmap' to extract (or build) other values from the-  results. 'Planned' values can be used together with the 'use' function to-  make a 'Plan' that produces the extracted value.+{- | A 'Planned' value is a wrapper around the results of previously-run queries when using the 'bind'+  function. At the time that you are writing a plan, you do not know whether the 'Plan' will be run+  with a single input or multiple inputs. A 'Planned' value may end up being either an individual+  item or a list of items. Due to this, your ability to interact with the value is limited to the+  use of 'fmap' to extract (or build) other values from the results. 'Planned' values can be used+  together with the 'use' function to make a 'Plan' that produces the extracted value. -  Note that while 'Planned' could provide an 'Applicative' instance as well, it-  does not to avoid confusion with the 'Applicative' instance for 'Plan'-  itself. If you need to build a value from several 'Planned' values using-  'Applicative', you should call 'use' on each of the values and use the-  'Applicative' instance for 'Plan'.+  Note that while 'Planned' could provide an 'Applicative' instance as well, it does not to avoid+  confusion with the 'Applicative' instance for 'Plan' itself. If you need to build a value from+  several 'Planned' values using 'Applicative', you should call 'use' on each of the values and use+  the 'Applicative' instance for 'Plan'.  @since 1.0.0.0 -}@@ -171,10 +172,8 @@ instance Functor (Planned scope param) where   fmap = mapPlanned -{- |-  'mapPlanned' applies a function to what value or values have been produced by-  the plan. This function can also be called as 'fmap' or '<$>' thorugh the-  'Functor' instance for 'Planned'.+{- | 'mapPlanned' applies a function to what value or values have been produced by the plan. This+  function can also be called as 'fmap' or '<$>' thorugh the 'Functor' instance for 'Planned'.  @since 1.0.0.0 -}@@ -188,28 +187,25 @@     PlannedExplain ->       PlannedExplain -{- |-  'resolveOne' resolves a 'Planned' value that is known to be in the 'Execute'-  scope to its single wrapped value.+{- | 'resolveOne' resolves a 'Planned' value that is known to be in the 'Execute' scope to its single+  wrapped value.  @since 1.0.0.0 -} resolveOne :: Planned Execute param a -> a resolveOne (PlannedOne a) = a -{- |-  'resolveMany resolves a 'Planned' value that is known to be in the-  'ExecuteMany' scope to the 'Many' value wrapped inside it.+{- | 'resolveMany resolves a 'Planned' value that is known to be in the 'ExecuteMany' scope to the+  'Many' value wrapped inside it.  @since 1.0.0.0 -} resolveMany :: Planned ExecuteMany k a -> Many k a resolveMany (PlannedMany as) = as -{- |-  'planOperation' allows any primitive 'Op.Operation' to be used as an atomic step-  in a plan. When the plan is executed, the appropriate 'Op.Operation' functions-  will be used depending on the execution context.+{- | 'planOperation' allows any primitive 'Op.Operation' to be used as an atomic step in a plan. When+  the plan is executed, the appropriate 'Op.Operation' functions will be used depending on the+  execution context.  @since 1.0.0.0 -}@@ -219,11 +215,10 @@ planOperation =   PlanOp -{- |-  'planSelect' allows any Orville 'Select' query to be incorporated into a-  plan. Note that the 'Select' cannot depend on the plan's input parameters in-  this case. If the plan is executed with multiple inputs, the same set of all-  the results will be used as the results for each of the input parameters.+{- | 'planSelect' allows any Orville 'Select' query to be incorporated into a plan. Note that the+  'Select' cannot depend on the plan's input parameters in this case. If the plan is executed with+  multiple inputs, the same set of all the results will be used as the results for each of the input+  parameters.  @since 1.0.0.0 -}@@ -231,10 +226,9 @@ planSelect select =   planOperation (Op.findSelect select) -{- |-  'askParam' allows the input parameter for the plan to be retrieved as the-  result of the plan. Together with 'bind' you can use this to get access to-  the input parameter as a 'Planned' value.+{- | 'askParam' allows the input parameter for the plan to be retrieved as the result of the+  plan. Together with 'bind' you can use this to get access to the input parameter as a 'Planned'+  value.  @since 1.0.0.0 -}@@ -242,10 +236,8 @@ askParam =   planOperation Op.askParam -{- |-  'findMaybeOne' constructs a 'Plan' that will find at most one row from-  the given table where the plan's input value matches the given database-  field.+{- | 'findMaybeOne' constructs a 'Plan' that will find at most one row from the given table where the+  plan's input value matches the given database field.  @since 1.0.0.0 -}@@ -257,11 +249,22 @@ findMaybeOne tableDef fieldDef =   planOperation (Op.findOne tableDef (Op.byField fieldDef)) -{- |-  'findMaybeOneWhere' is similar to 'findMaybeOne', but allows a-  'Expr.BooleanExpr' to be specified to restrict which rows are matched by the-  database query.+{- | Construct a plan that will find at most one row from the given table where the plan's input value+  matches the fields in the provided 'Marshall.SqlMarshaller'. +@since 1.1.0.0+-}+findMaybeOneByMarshaller ::+  Ord param =>+  Schema.TableDefinition key writeEntity readEntity ->+  Marshall.SqlMarshaller param param ->+  Plan scope param (Maybe readEntity)+findMaybeOneByMarshaller tableDef paramMarshaller =+  planOperation (Op.findOne tableDef (Op.byMarshaller paramMarshaller))++{- | 'findMaybeOneWhere' is similar to 'findMaybeOne', but allows a 'Expr.BooleanExpr' to be specified+  to restrict which rows are matched by the database query.+ @since 1.0.0.0 -} findMaybeOneWhere ::@@ -273,13 +276,25 @@ findMaybeOneWhere tableDef fieldDef cond =   planOperation (Op.findOneWhere tableDef (Op.byField fieldDef) cond) -{- |-  'findOneShowVia' is similar to 'findMaybeOne', but it expects that there will-  always be a row found matching the plan's input value. If no row is found, an-  'Op.AssertionFailed' exception will be thrown. This is a useful convenience-  when looking up foreign-key associations that are expected to be enforced by-  the database itself.+{- | Similar to 'findMaybeOneByMarshaller', but allows a 'Expr.BooleanExpr' to be specified to+  restrict which rows are matched by the database query. +@since 1.1.0.0+-}+findMaybeOneWhereByMarshaller ::+  Ord param =>+  Schema.TableDefinition key writeEntity readEntity ->+  Marshall.SqlMarshaller param param ->+  Expr.BooleanExpr ->+  Plan scope param (Maybe readEntity)+findMaybeOneWhereByMarshaller tableDef paramMarshaller cond =+  planOperation (Op.findOneWhere tableDef (Op.byMarshaller paramMarshaller) cond)++{- | 'findOneShowVia' is similar to 'findMaybeOne', but it expects that there will always be a row+  found matching the plan's input value. If no row is found, an 'Op.AssertionFailed' exception will+  be thrown. This is a useful convenience when looking up foreign-key associations that are expected+  to be enforced by the database itself.+ @since 1.0.0.0 -} findOneShowVia ::@@ -293,11 +308,27 @@     (assertFound showParam tableDef fieldDef)     (findMaybeOne tableDef fieldDef) -{- |-  'findOne' is an alias to 'findOneShowVia' that uses the 'Show' instance of-  @fieldValue@ when producing a failure message in the event that the entity-  cannot be found.+{- | Similar to 'findMaybeOneByMarshaller', but expects that there will always be a row found matching+  the plan's input value. If no row is found, an 'Op.AssertionFailed' exception will be thrown. This+  is a useful convenience when looking up foreign-key associations that are expected to be enforced+  by the database itself. +@since 1.1.0.0+-}+findOneShowViaByMarshaller ::+  Ord param =>+  (param -> String) ->+  Schema.TableDefinition key writeEntity readEntity ->+  Marshall.SqlMarshaller param param ->+  Plan scope param readEntity+findOneShowViaByMarshaller showParam tableDef paramMarshaller =+  assert+    (assertFoundByMarshaller showParam tableDef)+    (findMaybeOneByMarshaller tableDef paramMarshaller)++{- | 'findOne' is an alias to 'findOneShowVia' that uses the 'Show' instance of @fieldValue@ when+  producing a failure message in the event that the entity cannot be found.+ @since 1.0.0.0 -} findOne ::@@ -307,11 +338,21 @@   Plan scope fieldValue readEntity findOne = findOneShowVia show -{- |-  'findOneWhereShowVia' is similar to 'findOneShowVia', but allows a-  'Expr.BooleanExpr' to be specified to restrict which rows are matched by the-  database query.+{- | An alias for 'findOneShowViaByMarshaller' that uses the 'Show' instance of @param@ when producing+  a failure message in the event that the entity cannot be found. +@since 1.1.0.0+-}+findOneByMarshaller ::+  (Show param, Ord param) =>+  Schema.TableDefinition key writeEntity readEntity ->+  Marshall.SqlMarshaller param param ->+  Plan scope param readEntity+findOneByMarshaller = findOneShowViaByMarshaller show++{- | 'findOneWhereShowVia' is similar to 'findOneShowVia', but allows a 'Expr.BooleanExpr' to be+  specified to restrict which rows are matched by the database query.+ @since 1.0.0.0 -} findOneWhereShowVia ::@@ -326,11 +367,26 @@     (assertFound showParam tableDef fieldDef)     (findMaybeOneWhere tableDef fieldDef cond) -{- |-  'findOneWhere' is an alias to 'findOneWhereShowVia' that uses the 'Show'-  instance of @fieldValue@ when producing a failure message in the event that-  the entity cannot be found.+{- | Similar to 'findOneShowViaByMarshaller', but allows a 'Expr.BooleanExpr' to be specified to+  restrict which rows are matched by the database query. +@since 1.1.0.0+-}+findOneWhereShowViaByMarshaller ::+  Ord param =>+  (param -> String) ->+  Schema.TableDefinition key writeEntity readEntity ->+  Marshall.SqlMarshaller param param ->+  Expr.BooleanExpr ->+  Plan scope param readEntity+findOneWhereShowViaByMarshaller showParam tableDef paramMarshaller cond =+  assert+    (assertFoundByMarshaller showParam tableDef)+    (findMaybeOneWhereByMarshaller tableDef paramMarshaller cond)++{- | 'findOneWhere' is an alias to 'findOneWhereShowVia' that uses the 'Show' instance of @fieldValue@+  when producing a failure message in the event that the entity cannot be found.+ @since 1.0.0.0 -} findOneWhere ::@@ -341,10 +397,21 @@   Plan scope fieldValue readEntity findOneWhere = findOneWhereShowVia show -{- |-  'assertFound' is an internal helper that checks that row was found where-  one was expected.+{- | An alias for 'findOneWhereShowViaByMarshaller' that uses the 'Show' instance of @param@ when+  producing a failure message in the event that the entity cannot be found. +@since 1.1.0.0+-}+findOneWhereByMarshaller ::+  (Show param, Ord param) =>+  Schema.TableDefinition key writeEntity readEntity ->+  Marshall.SqlMarshaller param param ->+  Expr.BooleanExpr ->+  Plan scope param readEntity+findOneWhereByMarshaller = findOneWhereShowViaByMarshaller show++{- | 'assertFound' is an internal helper that checks that row was found where one was expected.+ @since 1.0.0.0 -} assertFound ::@@ -369,10 +436,32 @@           , showParam param           ] -{- |-  'findAll' constructs a 'Plan' that will find all the rows from the given-  table where the plan's input value matches the given database field.+{- | An internal helper that checks that a row was found and produces an error message otherwise. +@since 1.1.0.0+-}+assertFoundByMarshaller ::+  (param -> String) ->+  Schema.TableDefinition key writeEntity readEntity ->+  param ->+  Maybe result ->+  Either String result+assertFoundByMarshaller showParam tableDef param maybeRecord =+  case maybeRecord of+    Just a ->+      Right a+    Nothing ->+      Left $+        unwords+          [ "Failed to find record in table "+          , Schema.tableIdToString $ Schema.tableIdentifier tableDef+          , " where param marshaller fields = "+          , showParam param+          ]++{- | 'findAll' constructs a 'Plan' that will find all the rows from the given table where the plan's+  input value matches the given database field.+ @since 1.0.0.0 -} findAll ::@@ -383,10 +472,22 @@ findAll tableDef fieldDef =   planOperation (Op.findAll tableDef (Op.byField fieldDef)) -{- |-  'findAllWhere' is similar to 'findAll', but allows a 'Expr.BooleanExpr' to be-  specified to restrict which rows are matched by the database query.+{- | Construct a 'Plan' that will find all the rows from the given table where the plan's input value+  matches the fields in the provided 'Marshall.SqlMarshaller'. +@since 1.1.0.0+-}+findAllByMarshaller ::+  Ord param =>+  Schema.TableDefinition key writeEntity readEntity ->+  Marshall.SqlMarshaller param param ->+  Plan scope param [readEntity]+findAllByMarshaller tableDef paramMarshaller =+  planOperation (Op.findAll tableDef (Op.byMarshaller paramMarshaller))++{- | 'findAllWhere' is similar to 'findAll', but allows a 'Expr.BooleanExpr' to be specified to+  restrict which rows are matched by the database query.+ @since 1.0.0.0 -} findAllWhere ::@@ -398,13 +499,25 @@ findAllWhere tableDef fieldDef cond =   planOperation (Op.findAllWhere tableDef (Op.byField fieldDef) cond) -{- |-  'planMany' adapts a plan that takes a single input parameter to work on-  multiple input parameters. When the new plan is executed, each query will-  execute in the same basic order, but with adjusted conditions to find all the-  rows for all inputs at once rather than running the planned queries once for-  each input.+{- | Similar to 'findAllByMarshaller', but allows a 'Expr.BooleanExpr' to be specified to restrict+  which rows are matched by the database query. +@since 1.1.0.0+-}+findAllWhereByMarshaller ::+  Ord param =>+  Schema.TableDefinition key writeEntity readEntity ->+  Marshall.SqlMarshaller param param ->+  Expr.BooleanExpr ->+  Plan scope param [readEntity]+findAllWhereByMarshaller tableDef paramMarshaller cond =+  planOperation (Op.findAllWhere tableDef (Op.byMarshaller paramMarshaller) cond)++{- | 'planMany' adapts a plan that takes a single input parameter to work on multiple input+  parameters. When the new plan is executed, each query will execute in the same basic order, but+  with adjusted conditions to find all the rows for all inputs at once rather than running the+  planned queries once for each input.+ @since 1.0.0.0 -} planMany ::@@ -413,19 +526,15 @@ planMany =   PlanMany -{- |-  'planList' lifts a plan so both its param and result become lists. This saves-  you from having to fmap in 'Many.elems' when all you want back from a 'Many'-  is the list of results inside it.+{- | 'planList' lifts a plan so both its param and result become lists. This saves you from having to+  fmap in 'Many.elems' when all you want back from a 'Many' is the list of results inside it. -  There will always be the same number of elements in the @[result]@ list as-  there are in the @[param]@ list, even if there are duplicate values in the-  input parameters. This may be counter-intuitive in the trivial case where a-  plan that queries a single table is passed to 'planList' but cannot be-  avoided due to more complicated situations where the original plan executes-  queries against multiple tables. When a plan that queries multiple tables is-  passed, the query results must be correlated based on the input parameters to-  build each @result@ value.+  There will always be the same number of elements in the @[result]@ list as there are in the+  @[param]@ list, even if there are duplicate values in the input parameters. This may be+  counter-intuitive in the trivial case where a plan that queries a single table is passed to+  'planList' but cannot be avoided due to more complicated situations where the original plan+  executes queries against multiple tables. When a plan that queries multiple tables is passed, the+  query results must be correlated based on the input parameters to build each @result@ value.  @since 1.0.0.0 -}@@ -435,29 +544,42 @@ planList plan =   Many.elems <$> planMany plan -{- |-  'focusParam' builds a plan from a function and an existing plan, taking the-  result of that function as input. This is especially useful when there is-  some structure, and a plan that only needs a part of that structure as input.-  The function argument can access part of the structure for the plan argument-  to use, so the final returned plan can take the entire structure as input.+{- | Similar to 'planList', but generalized to work with any 'Traversable'. +@since 1.1.0.0+-}+planTraversable ::+  forall t tScope param result.+  Traversable t =>+  (forall scope. Plan scope param result) ->+  Plan tScope (t param) (t result)+planTraversable plan =+  let+    lookupAll :: t param -> Many param result -> Either String (t result)+    lookupAll t m =+      traverse (Bifunctor.first (const "planTraversable invariant violated: Missing Key") . flip Many.lookup m) t+  in+    assert lookupAll $ chain (fmap Foldable.toList askParam) (planMany plan)++{- | 'focusParam' builds a plan from a function and an existing plan, taking the result of that+  function as input. This is especially useful when there is some structure, and a plan that only+  needs a part of that structure as input.  The function argument can access part of the structure+  for the plan argument to use, so the final returned plan can take the entire structure as input.+ @since 1.0.0.0 -} focusParam ::   (a -> b) ->   Plan scope b result ->   Plan scope a result-focusParam focuser plan =-  chain (focuser <$> askParam) plan+focusParam focuser =+  chain (fmap focuser askParam) -{- |-  'planEither' lets you construct a plan that branches by executing a different-  plan for the 'Left' and 'Right' sides of an 'Either' value. When used with a-  single input parameter, only one of the two plans will be used, based on the-  input parameter. When used on multiple input parameters, each of the two-  plans will be executed only once with all the 'Left' and 'Right' values-  provided as input parameters respectively.+{- | 'planEither' lets you construct a plan that branches by executing a different plan for the 'Left'+  and 'Right' sides of an 'Either' value. When used with a single input parameter, only one of the+  two plans will be used, based on the input parameter. When used on multiple input parameters, each+  of the two plans will be executed only once with all the 'Left' and 'Right' values provided as+  input parameters respectively.  @since 1.0.0.0 -}@@ -468,10 +590,9 @@ planEither =   PlanEither -{- |-  'planMaybe' lifts a plan so both its param and result become 'Maybe's. This is-  useful when modifying an existing plan to deal with optionality. Writing just-  one plan can then easily produce both the required and optional versions.+{- | 'planMaybe' lifts a plan so both its param and result become 'Maybe's. This is useful when+  modifying an existing plan to deal with optionality. Writing just one plan can then easily produce+  both the required and optional versions.  @since 1.0.0.0 -}@@ -480,21 +601,17 @@   focusParam (maybe (Left ()) Right) $     either id id <$> planEither (pure Nothing) (Just <$> plan) -{- |-  'bind' gives access to the results of a plan to use as input values to future-  plans. The plan result is given the input parameter to the provided function,-  which must produce the remaining 'Plan' to be executed. The value will be-  wrapped in the 'Planned' type, which may represent either a result or-  multiple results, depending on whether one plan is currently being executed-  with one and multiple input parameters. This ensures that the caller produces-  only a single remaining 'Plan' to be used for all inputs when there are-  multiple to eliminate the need to possibly run different queries for-  different inputs (which would an introduce N+1 query execution).+{- | 'bind' gives access to the results of a plan to use as input values to future plans. The plan+  result is given the input parameter to the provided function, which must produce the remaining+  'Plan' to be executed. The value will be wrapped in the 'Planned' type, which may represent either+  a result or multiple results, depending on whether one plan is currently being executed with one+  and multiple input parameters. This ensures that the caller produces only a single remaining+  'Plan' to be used for all inputs when there are multiple to eliminate the need to possibly run+  different queries for different inputs (which would an introduce N+1 query execution). -  The 'Planned' value (or values) provided by 'bind' have actually been-  retrieved from the database, so the value can be used multiple times when-  constructing the remaining 'Plan' without fear of causing the query to run-  multiple times.+  The 'Planned' value (or values) provided by 'bind' have actually been retrieved from the database,+  so the value can be used multiple times when constructing the remaining 'Plan' without fear of+  causing the query to run multiple times.    Also see 'use' for how to lift a 'Planned' value back into a 'Plan'. @@ -507,9 +624,8 @@ bind =   Bind -{- |-  'use' constructs a 'Plan' that always produces the 'Planned' value-  as its result, regardless of the parameter given as input to the plan.+{- | 'use' constructs a 'Plan' that always produces the 'Planned' value as its result, regardless of+  the parameter given as input to the plan.  @since 1.0.0.0 -}@@ -517,10 +633,8 @@ use =   Use -{- |-  'using' uses a 'Planned' value in the input to another 'Plan'. The-  resulting plan will ignore its input and use the 'Planned' value as-  the input to produce its result instead.+{- | 'using' uses a 'Planned' value in the input to another 'Plan'. The resulting plan will ignore its+  input and use the 'Planned' value as the input to produce its result instead.  @since 1.0.0.0 -}@@ -528,13 +642,11 @@   Planned scope param a ->   Plan scope a b ->   Plan scope param b-using planned plan =-  chain (use planned) plan+using planned =+  chain (use planned) -{- |-  'apply' applies a function produced by a plan to the value produced-  by another plan. This is usually used via the '<*>' operator through-  the 'Applicative' instance for 'Plan'.+{- | 'apply' applies a function produced by a plan to the value produced by another plan. This is+  usually used via the '<*>' operator through the 'Applicative' instance for 'Plan'.  @since 1.0.0.0 -}@@ -545,9 +657,8 @@ apply =   Apply -{- |-  'chain' connects the output of one plan to the input of another to form a-  larger plan that will execute the first followed by the second.+{- | 'chain' connects the output of one plan to the input of another to form a larger plan that will+  execute the first followed by the second.  @since 1.0.0.0 -}@@ -558,10 +669,8 @@ chain =   Chain -{- |-  'chainMaybe' connects two plans that both yield Maybes.-  If the first plan yields no result, the second is skipped.-  See also 'chain'.+{- | 'chainMaybe' connects two plans that both yield Maybes.  If the first plan yields no result, the+  second is skipped.  See also 'chain'.  @since 1.0.0.0 -}@@ -569,7 +678,7 @@   Plan scope a (Maybe b) ->   Plan scope b (Maybe c) ->   Plan scope a (Maybe c)-chainMaybe a b =+chainMaybe aPlan bPlan =   let     optionalInput ::       Plan scope a (Maybe b) ->@@ -577,14 +686,12 @@     optionalInput =       fmap join . planMaybe   in-    Chain a (optionalInput b)+    Chain aPlan (optionalInput bPlan) -{- |-  'assert' allows you to make an assertion about a plan's result that will-  throw an 'Op.AssertionFailed' exception during execution if it proves to be-  false. The first parameter is the assertion function, which should return-  either an error message to be given in the exception or the value to be used-  as the plan's result.+{- | 'assert' allows you to make an assertion about a plan's result that will throw an+  'Op.AssertionFailed' exception during execution if it proves to be false. The first parameter is+  the assertion function, which should return either an error message to be given in the exception+  or the value to be used as the plan's result.  @since 1.0.0.0 -}@@ -601,14 +708,12 @@   in     chain eitherPlan (PlanOp Op.assertRight) -{- |-  'execute' accepts the input parameter (or parameters) expected by a 'Plan'-  and runs the plan to completion, either throwing an 'Op.AssertionFailed'-  exception in the monad @m@ or producing the expected result.+{- | 'execute' accepts the input parameter (or parameters) expected by a 'Plan' and runs the plan to+  completion, either throwing an 'Op.AssertionFailed' exception in the monad @m@ or producing the+  expected result. -  If you have a plan that takes one input and want to provide a list of-  input, use 'planMany' to adapt it to a multple-input plan before calling-  'execute'.+  If you have a plan that takes one input and want to provide a list of input, use 'planMany' to+  adapt it to a multple-input plan before calling 'execute'.  @since 1.0.0.0 -}@@ -617,12 +722,11 @@   Plan Execute param result ->   param ->   m result-execute plan param =-  executeOne plan param+execute =+  executeOne -{- |-  'executeOne' is an internal helper that executes a 'Plan' with a concrete-  @scope@ type to ensure all 'Planned' values are built with 'PlannedOne'.+{- | 'executeOne' is an internal helper that executes a 'Plan' with a concrete @scope@ type to ensure+  all 'Planned' values are built with 'PlannedOne'.  @since 1.0.0.0 -}@@ -664,9 +768,8 @@       b <- executeOne planAB param       executeOne planBC b -{- |-  'executeMany' is an internal helper that executes a 'Plan' with a concrete-  @scope@ type to ensure all 'Planned' values are built with 'PlannedMany'.+{- | 'executeMany' is an internal helper that executes a 'Plan' with a concrete @scope@ type to ensure+  all 'Planned' values are built with 'PlannedMany'.  @since 1.0.0.0 -}@@ -735,9 +838,8 @@       cs <- executeMany planBC (Many.elems bs)       pure $ Many.compose cs bs -{- |-  'Explain' is a tag type used as the @scope@ variable when explaining a 'Plan'-  via the 'explain' function.+{- | 'Explain' is a tag type used as the @scope@ variable when explaining a 'Plan' via the 'explain'+  function.  @since 1.0.0.0 -}@@ -745,11 +847,9 @@   = ExplainOne   | ExplainMany -{- |-  'explain' produces a textual description of the steps outlined by-  a 'Plan' -- in most cases example SQL queries. If you want to see-  the explanation of how the plan will run with multiple input parameters,-  you can use 'planMany' to adapt it before calling 'explain'.+{- | 'explain' produces a textual description of the steps outlined by a 'Plan' -- in most cases+  example SQL queries. If you want to see the explanation of how the plan will run with multiple+  input parameters, you can use 'planMany' to adapt it before calling 'explain'.  @since 1.0.0.0 -}@@ -758,10 +858,8 @@   Exp.explanationSteps $     explainPlan ExplainOne plan -{- |-  'explainPlan' is an internal helper to executes a plan with the-  @scope@ type fixed to 'Explain' to ensure that all 'Planned'-  values are constructed with the 'PlannedExplain' constructor.+{- | 'explainPlan' is an internal helper to executes a plan with the @scope@ type fixed to 'Explain'+  to ensure that all 'Planned' values are constructed with the 'PlannedExplain' constructor.  @since 1.0.0.0 -}
src/Orville/PostgreSQL/Plan/Explanation.hs view
@@ -13,8 +13,7 @@   ) where -{- |- An 'Explanation' represents an example sequence of queries showing the steps+{- | An 'Explanation' represents an example sequence of queries showing the steps  would be executed by an Orville 'Orville.PostgreSQL.Plan.Operation.Operation'.  @since 1.0.0.0@@ -22,14 +21,15 @@ newtype Explanation   = Explanation ([String] -> [String]) +-- | @since 1.0.0.0 instance Semigroup Explanation where   (<>) = appendExplanation +-- | @since 1.0.0.0 instance Monoid Explanation where   mempty = noExplanation -{- |-Appends two 'Explanation's with the steps from the first argument being shown+{- | Appends two 'Explanation's with the steps from the first argument being shown first.  @since 1.0.0.0@@ -38,8 +38,7 @@ appendExplanation (Explanation front) (Explanation back) =   Explanation (front . back) -{- |-Constructs an empty 'Explanation'.+{- | Constructs an empty 'Explanation'.  @since 1.0.0.0 -}@@ -47,8 +46,7 @@ noExplanation =   Explanation id -{- |-Constructs an 'Explanation' with a single step.+{- | Constructs an 'Explanation' with a single step.  @since 1.0.0.0 -}@@ -56,8 +54,7 @@ explainStep str =   Explanation (str :) -{- |-Retrieves the steps contained in the 'Explanation'.+{- | Retrieves the steps contained in the 'Explanation'.  @since 1.0.0.0 -}
src/Orville/PostgreSQL/Plan/Many.hs view
@@ -1,5 +1,5 @@ {- |-Copyright : Flipstone Technology Partners 2023+Copyright : Flipstone Technology Partners 2023-2025 License   : MIT Stability : Stable @@ -25,8 +25,7 @@ import qualified Data.Map as Map import qualified Data.Maybe as Maybe -{- |-  'NotAKey' is returned from various 'Many' related functions when presented+{- | 'NotAKey' is returned from various 'Many' related functions when presented   with an input parameter that was not one of the original inputs that the   'Many' was constructed with. @@ -35,8 +34,7 @@ data NotAKey   = NotAKey -{- |-  A 'Many k a' represents a group of values keyed by list of parameters and+{- | A 'Many k a' represents a group of values keyed by list of parameters and   is used to return the results of executing an Orville Plan with a list of   input parameters. If you need to find the result of the query associated   with a particular input parameter, you can use 'lookup' to find it. If you@@ -48,11 +46,11 @@ data Many k a   = Many [k] (k -> Either NotAKey a) +-- | @since 1.0.0.0 instance Functor (Many k) where   fmap = map -{- |-  'fromKeys' constructs a 'Many' value from a list of keys and a function that+{- | 'fromKeys' constructs a 'Many' value from a list of keys and a function that   maps them to their values. The order and duplication of keys in the list will   be preserved by the 'Many' type in the relevant functions. The mapping   function provided should be a total function -- i.e. it should not produce a@@ -67,8 +65,7 @@ fromKeys =   Many -{- |-   'map' calls a function on all the values found in a 'Many' collection.+{- |  'map' calls a function on all the values found in a 'Many' collection.  @since 1.0.0.0 -}@@ -76,8 +73,7 @@ map f (Many ks keyToValue) =   Many ks (fmap f . keyToValue) -{- |-   'apply' allows you to apply many functions to many values. The function+{- |  'apply' allows you to apply many functions to many values. The function    associated with each parameter is applied to the value associated with the    same paremeter. @@ -90,16 +86,15 @@ -} apply ::   (Many param (a -> b)) ->-  (Many param a) ->-  (Many param b)+  Many param a ->+  Many param b apply manyFs manyAs =   fromKeys (keys manyFs) applyF  where   applyF param =     lookup param manyFs <*> lookup param manyAs -{- |-  'compose' uses the values of a 'Many' value as keys to a second 'Many' to+{- | 'compose' uses the values of a 'Many' value as keys to a second 'Many' to   create a 'Many' mapping from the original keys to the final values.  @since 1.0.0.0@@ -112,8 +107,7 @@     b <- lookup a manyAB     lookup b manyBC -{- |-  'keys' fetches the list of keys from a 'Many'. Note that is a list and not+{- | 'keys' fetches the list of keys from a 'Many'. Note that is a list and not   a set. 'Many' preserves the order and duplication of any key values that were   in the key list at the time of construction. @@ -123,8 +117,7 @@ keys (Many ks _) =   ks -{- |-  'elems' returns all the values that correspond to the keys of the 'Many'. The+{- | 'elems' returns all the values that correspond to the keys of the 'Many'. The   values will be returned in the same order that the keys were present at the   time of creation, though if you truly care about this it's probably better to   use 'lookup' to make that correspondence explicit.@@ -135,8 +128,7 @@ elems (Many ks keyToValue) =   Either.rights $ fmap keyToValue ks -{- |-  'toMap' converts the 'Many' into a 'Map.Map' value. If all you wanted to do+{- | 'toMap' converts the 'Many' into a 'Map.Map' value. If all you wanted to do   was find the value for a specific key, you should probably use 'lookup'   instead. @@ -153,8 +145,7 @@       Right value ->         Just (k, value) -{- |-  'lookup' returns the value for the given parameter. If the given @k@ is+{- | 'lookup' returns the value for the given parameter. If the given @k@ is   not one of the original input values that the 'Many' was constructed with,   the mapping function given at the contructor will determine what value to   return. Often this will be whatever a reasonable empty or default value for
src/Orville/PostgreSQL/Plan/Operation.hs view
@@ -2,7 +2,7 @@ {-# LANGUAGE ScopedTypeVariables #-}  {- |-Copyright : Flipstone Technology Partners 2023+Copyright : Flipstone Technology Partners 2023-2025 License   : MIT Stability : Stable @@ -15,6 +15,7 @@   , WherePlanner (..)   , byField   , byFieldTuple+  , byMarshaller   , findOne   , findOneWhere   , findAll@@ -47,8 +48,7 @@ import qualified Orville.PostgreSQL.Raw.RawSql as RawSql import qualified Orville.PostgreSQL.Schema as Schema -{- |-  'Operation' provides a stucture for building primitive operations that can be+{- | 'Operation' provides a stucture for building primitive operations that can be   incorporated into a 'Database.Orville.PostgreSQL.Plan.Plan'. An 'Operation'   provides base case implementations of the various plan execution functions.   You only need to care about this type if you want to create new custom@@ -89,8 +89,7 @@   -- interesting IO interactions should generally return an empty explanation.   } -{- |-  'AssertionFailed' may be returned from the execute functions of an+{- | 'AssertionFailed' may be returned from the execute functions of an   'Operation' to indicate that some expected invariant has failed. For example,   following a foreign key that is enforced by the database only to find that no   record exists. When an 'Operation' returns an 'AssertionFailed' value during@@ -102,10 +101,12 @@ -} newtype AssertionFailed   = AssertionFailed String-  deriving (Show)+  deriving+    ( -- | @since 1.0.0.0+      Show+    ) -{- |-  'mkAssertionFailed' builds an 'AssertionFailed' error from an error message.+{- | 'mkAssertionFailed' builds an 'AssertionFailed' error from an error message.  @since 1.0.0.0 -}@@ -113,10 +114,10 @@ mkAssertionFailed =   AssertionFailed +-- | @since 1.0.0.0 instance Exception AssertionFailed -{- |-  'askParam' simply returns the parameter given from the plan.+{- | 'askParam' simply returns the parameter given from the plan.  @since 1.0.0.0 -}@@ -129,8 +130,7 @@     , explainOperationMany = Exp.noExplanation     } -{- |-  'assertRight' returns the value on the 'Right' side of an 'Either'. If+{- | 'assertRight' returns the value on the 'Right' side of an 'Either'. If   the 'Either' is a 'Left', it raises 'AssertionFailed' with the message   from the 'Left' side of the 'Either'. @@ -169,8 +169,7 @@     , explainOperationMany = Exp.noExplanation     } -{- |-  The functions below ('findOne', 'findAll', etc) accept a 'WherePlanner'+{- | The functions below ('findOne', 'findAll', etc) accept a 'WherePlanner'   to determine how to build the where conditions for executing a 'Exec.Select'   statement as part of a plan operation. @@ -209,8 +208,7 @@   -- either an example or dummy value.   } -{- |-  Builds a 'WherePlanner' that will match on a single+{- | Builds a 'WherePlanner' that will match on a single   'FieldDefinition.FieldDefinition'.  The resulting 'WherePlanner' can be used   with functions such as 'findOne' and 'findAll' to construct an 'Operation'. @@ -227,14 +225,13 @@   in     WherePlanner       { paramMarshaller = flip Marshall.marshallField fieldDef-      , executeOneWhereCondition = \fieldValue -> Marshall.fieldEquals fieldDef fieldValue-      , executeManyWhereCondition = \fieldValues -> Marshall.fieldIn fieldDef (dedupeFieldValues fieldValues)+      , executeOneWhereCondition = Marshall.fieldEquals fieldDef+      , executeManyWhereCondition = Marshall.fieldIn fieldDef . dedupeFieldValues       , explainOneWhereCondition = Marshall.fieldEquals stringyField $ T.pack "EXAMPLE VALUE"       , explainManyWhereCondition = Marshall.fieldIn stringyField $ fmap T.pack ("EXAMPLE VALUE 1" :| ["EXAMPLE VALUE 2"])       } -{- |-  Builds a 'WherePlanner' that will match on a 2-tuple of+{- | Builds a 'WherePlanner' that will match on a 2-tuple of   'FieldDefinition.FieldDefinition's.  The resulting 'WherePlanner' can be used   with functions such as 'findOne' and 'findAll' to construct an 'Operation'. @@ -262,13 +259,14 @@         <$> Marshall.marshallField (fst . accessor) fieldDefA         <*> Marshall.marshallField (snd . accessor) fieldDefB +    packAll :: NonEmpty (String, String) -> NonEmpty (T.Text, T.Text)     packAll =       fmap (\(a, b) -> (T.pack a, T.pack b))   in     WherePlanner       { paramMarshaller = marshaller       , executeOneWhereCondition = \fieldValue -> Marshall.fieldTupleIn fieldDefA fieldDefB (fieldValue :| [])-      , executeManyWhereCondition = \fieldValues -> Marshall.fieldTupleIn fieldDefA fieldDefB (dedupeFieldValues fieldValues)+      , executeManyWhereCondition = Marshall.fieldTupleIn fieldDefA fieldDefB . dedupeFieldValues       , explainOneWhereCondition =           Marshall.fieldTupleIn             stringyFieldA@@ -278,9 +276,35 @@           Marshall.fieldTupleIn             stringyFieldA             stringyFieldB-            (packAll $ (("EXAMPLE VALUE A 1", "EXAMPLE VALUE B 1") :| [("EXAMPLE VALUE A 2", "EXAMPLE VALUE B 2")]))+            (packAll (("EXAMPLE VALUE A 1", "EXAMPLE VALUE B 1") :| [("EXAMPLE VALUE A 2", "EXAMPLE VALUE B 2")]))       } +{- | Builds a 'WherePlanner' that will match on the writable fields of a+  'Marshall.SqlMarshaller'. The resulting 'WherePlanner' can be used with+  functions such as 'findOne' and 'findAll' to construct an 'Operation'.++@since 1.1.0.0+-}+byMarshaller ::+  Ord param =>+  Marshall.SqlMarshaller param param ->+  WherePlanner param+byMarshaller marshaller =+  let+    (fieldCount, stringyMarshaller) = stringifyMarshaller marshaller+    examples = fmap (\i -> T.pack $ "EXAMPLE VALUE " <> show i) [1 .. fieldCount]+  in+    WherePlanner+      { paramMarshaller = flip Marshall.marshallNested marshaller+      , executeOneWhereCondition = Marshall.marshallerEquals marshaller+      , executeManyWhereCondition = Marshall.marshallerIn marshaller . dedupeFieldValues+      , explainOneWhereCondition = Marshall.marshallerEquals stringyMarshaller examples+      , explainManyWhereCondition =+          Marshall.marshallerIn+            stringyMarshaller+            (fmap (<> T.pack " A") examples :| [fmap (<> T.pack " B") examples])+      }+ dedupeFieldValues :: Ord a => NonEmpty a -> NonEmpty a dedupeFieldValues (first :| rest) =   let@@ -292,8 +316,7 @@   in     first :| dedupedWithoutFirst -{- |-  'findOne' builds a planning primitive that finds (at most) one row from the+{- | 'findOne' builds a planning primitive that finds (at most) one row from the   given table where the column value for the provided 'Core.FieldDefinition'   matches the plan's input parameter. When executed on multiple parameters, it   fetches all rows where the field matches the inputs and arbitrarily picks at@@ -309,8 +332,7 @@ findOne tableDef wherePlanner =   findOneWithOpts tableDef wherePlanner mempty -{- |-  'findOneWhere' is similar to 'findOne' but allows a 'Expr.BooleanExpr' to be+{- | 'findOneWhere' is similar to 'findOne' but allows a 'Expr.BooleanExpr' to be   specified that is added to the database query to restrict which rows are   returned. @@ -325,8 +347,7 @@ findOneWhere tableDef wherePlanner cond =   findOneWithOpts tableDef wherePlanner (Exec.where_ cond) -{- |-  'findOneWithOpts' is a internal helper used by 'findOne' and 'findOneWhere'.+{- | 'findOneWithOpts' is a internal helper used by 'findOne' and 'findOneWhere'.  @since 1.0.0.0 -}@@ -367,8 +388,7 @@       )       (Schema.tableMarshaller tableDef) -{- |-  'findAll' builds a planning primitive that finds all the rows from the given+{- | 'findAll' builds a planning primitive that finds all the rows from the given   table where the column value for the provided field matches the plan's input   parameter. When executed on multiple parameters, all rows are fetched in a   single query and then associated with their respective inputs after being@@ -384,8 +404,7 @@ findAll tableDef wherePlanner =   findAllWithOpts tableDef wherePlanner mempty -{- |-  'findAllWhere' is similar to 'findAll' but allows a 'Expr.BooleanExpr' to be+{- | 'findAllWhere' is similar to 'findAll' but allows a 'Expr.BooleanExpr' to be   specified that is added to the database query to restrict which rows are   returned. @@ -400,8 +419,7 @@ findAllWhere tableDef wherePlanner cond =   findAllWithOpts tableDef wherePlanner (Exec.where_ cond) -{- |-  'findAllWithOpts' is an internal helper used by 'findAll' and 'findAllWhere'.+{- | 'findAllWithOpts' is an internal helper used by 'findAll' and 'findAllWhere'.  @since 1.0.0.0 -}@@ -442,8 +460,7 @@       )       (Schema.tableMarshaller tableDef) -{- |-  'stringifyField' arbitrarily re-labels the 'Marshall.SqlType' of a field+{- | 'stringifyField' arbitrarily re-labels the 'Marshall.SqlType' of a field   definition as text. It is an internal helper function that is used for   constructing 'Expr.BooleanExpr' clauses used to generate sql when explaining   how a plan will be executed. Relabeling the type as 'T.Text' allows us to use@@ -457,8 +474,48 @@ stringifyField =   Marshall.convertField (const Marshall.unboundedText) -{- |-  'SelectOperation' is a helper type for building 'Operation' primitives that+{- | 'stringifyMarshaller' arbitrarily re-labels the 'Marshall.SqlType' of all the+  writable fields in a marshaller as 'T.Text'. Used internally to generate SQL+  for explanations. Also returns the number of re-labled fields to facilitate+  generating a list of example values.++@since 1.1.0.0+-}+stringifyMarshaller ::+  Marshall.SqlMarshaller a b ->+  (Int, Marshall.SqlMarshaller [T.Text] [T.Text])+stringifyMarshaller marshaller =+  let+    mkFieldMarshaller ::+      Marshall.FieldQualifier qualifier =>+      Maybe qualifier ->+      Marshall.FieldDefinition nullability a ->+      (Int -> Marshall.SqlMarshaller [T.Text] T.Text)+    mkFieldMarshaller mbQualifier fieldDef =+      \idx ->+        maybe id Marshall.marshallQualifyFields mbQualifier $+          Marshall.marshallField+            (!! idx)+            ( Marshall.unboundedTextField+                . Marshall.fieldNameToString+                . Marshall.fieldName+                $ fieldDef+            )++    marshallerConstructors :: [Int -> Marshall.SqlMarshaller [T.Text] T.Text]+    marshallerConstructors =+      Marshall.foldMarshallerFields marshaller [] $+        Marshall.collectFromField+          Marshall.ExcludeReadOnlyColumns+          mkFieldMarshaller++    stringyMarshaller :: Marshall.SqlMarshaller [T.Text] [T.Text]+    stringyMarshaller =+      sequenceA (zipWith ($) marshallerConstructors [(0 :: Int) ..])+  in+    (length marshallerConstructors, stringyMarshaller)++{- | 'SelectOperation' is a helper type for building 'Operation' primitives that   run 'Ex.cSelect' queries. Specifying the fields of 'SelectOperation' and then   using the 'selectOperation' function to build an 'Operation' is more   convenient than building functions to execute the queries that are required@@ -509,8 +566,7 @@   -- type as the @result@ type and 'findOne' uses 'Maybe'.   } -{- |-  'selectOperation' builds a primitive planning 'Operation' using the functions+{- | 'selectOperation' builds a primitive planning 'Operation' using the functions   given by a 'SelectOperation'. If you are implementing a custom operation that   runs a select statement, it is probably easier to use this function rather   than building the 'Operation' functions directly.@@ -533,8 +589,7 @@ explainSelect =   Exp.explainStep . BS8.unpack . RawSql.toExampleBytes . Exec.selectToQueryExpr -{- |-  'executeSelectOne' is an internal helper function that executes a+{- | 'executeSelectOne' is an internal helper function that executes a   'SelectOperation' on a single input parameter.  @since 1.0.0.0@@ -548,8 +603,7 @@   Right . produceResult selectOp     <$> (Exec.executeSelect . selectOne selectOp $ param) -{- |-  'executeSelectMany' is an internal helper function that executes a+{- | 'executeSelectMany' is an internal helper function that executes a   'SelectOperation' on multiple input parameters.  @since 1.0.0.0@@ -573,7 +627,7 @@     emptyRowsMap :: Map.Map param (Seq.Seq a)     emptyRowsMap =       Map.fromList-        . map (\param -> (param, Seq.empty))+        . fmap (\param -> (param, Seq.empty))         $ paramList      insertRow results row =@@ -600,8 +654,7 @@    pure . Right $ manyRows -{- |-  'findSelect' builds a plan 'Operation' where the select that is run does not+{- | 'findSelect' builds a plan 'Operation' where the select that is run does not   use the input parameters for the plan in any way. The 'executeOperationMany'   function of the resulting 'Operation' will run the query once and use the   entire result set as the result each of the input parameters in turn.
src/Orville/PostgreSQL/Plan/Syntax.hs view
@@ -54,8 +54,7 @@  import qualified Orville.PostgreSQL.Plan as Plan -{- |-  An operator alias of 'Plan.bind' so that it can be used with @QualifiedDo@.+{- | An operator alias of 'Plan.bind' so that it can be used with @QualifiedDo@.  @since 1.0.0.0 -}
src/Orville/PostgreSQL/Raw/Connection.hs view
@@ -2,7 +2,7 @@ {-# LANGUAGE RankNTypes #-}  {- |-Copyright : Flipstone Technology Partners 2023+Copyright : Flipstone Technology Partners 2023-2025 License   : MIT Stability : Stable @@ -30,16 +30,18 @@   , ConnectionUsedAfterCloseError   , ConnectionError   , SqlExecutionError (..)+  , transactionStatus+  , transactionStatusOrThrow   ) where  import Control.Concurrent (getNumCapabilities, threadWaitRead, threadWaitWrite)-import Control.Concurrent.MVar (MVar, newMVar, tryReadMVar, tryTakeMVar)+import Control.Concurrent.MVar (MVar, newMVar, tryTakeMVar, withMVar) import Control.Exception (Exception, mask, throwIO)-import Control.Monad (void) import qualified Data.ByteString as BS import qualified Data.ByteString.Builder as BSB import qualified Data.ByteString.Char8 as B8+import Data.IORef (IORef, newIORef, readIORef, writeIORef) import Data.Maybe (fromMaybe) #if MIN_VERSION_resource_pool(0,4,0) import Data.Pool (Pool, newPool, defaultPoolConfig, setNumStripes, withResource)@@ -53,8 +55,7 @@  import Orville.PostgreSQL.Raw.PgTextFormatValue (NULByteFoundError (NULByteFoundError), PgTextFormatValue, toBytesForLibPQ) -{- |-  An option for 'createConnectionPool' that indicates whether LibPQ should+{- | An option for 'createConnectionPool' that indicates whether LibPQ should   print notice reports for warnings to the console.  @since 1.0.0.0@@ -63,8 +64,7 @@   = EnableNoticeReporting   | DisableNoticeReporting -{- |-Orville always uses a connection pool to manage the number of open connections+{- | Orville always uses a connection pool to manage the number of open connections to the database. See 'ConnectionConfig' and 'createConnectionPool' to find how to create a 'ConnectionPool'. @@ -73,8 +73,7 @@ newtype ConnectionPool   = ConnectionPool (Pool Connection) -{- |- 'createConnectionPool' allocates a pool of connections to a PostgreSQL server.+{- | 'createConnectionPool' allocates a pool of connections to a PostgreSQL server.  @since 1.0.0.0 -}@@ -121,8 +120,7 @@       connPerStripe #endif -{- |-Values for the 'connectionPoolStripes' field of 'ConnectionOptions'.+{- | Values for the 'connectionPoolStripes' field of 'ConnectionOptions'.  @since 1.0.0.0 -}@@ -137,8 +135,7 @@     -- the runtime has.     StripeCount Int -{- |-Values for the 'connectionMaxConnections' field of 'ConnectionOptions'.+{- | Values for the 'connectionMaxConnections' field of 'ConnectionOptions'.  @since 1.0.0.0 -}@@ -155,8 +152,7 @@     -- be this value multiplied by the number of stripes.     MaxConnectionsPerStripe Int -{- |-Configuration options to pass to 'createConnectionPool' to specify the+{- | Configuration options to pass to 'createConnectionPool' to specify the parameters for the pool and the connections that it creates.  @since 1.0.0.0@@ -174,17 +170,14 @@   -- ^ Controls the number of connections available in the 'ConnectionPool'.   } -{- |-  INTERNAL: Resolves the 'StripeOption' to the actual number of stripes to use.--}+-- | INTERNAL: Resolves the 'StripeOption' to the actual number of stripes to use. determineStripeCount :: StripeOption -> IO Int determineStripeCount stripeOption =   case stripeOption of     OneStripePerCapability -> getNumCapabilities     StripeCount n -> pure n -{- |-  INTERNAL: Resolves the 'MaxConnections' to the actual number of connections+{- | INTERNAL: Resolves the 'MaxConnections' to the actual number of connections   to use per stripe. -} determineConnectionsPerStripe :: Int -> MaxConnections -> Either String Int@@ -204,19 +197,33 @@               <> show stripes               <> " stripes." -{- |-  Allocates a connection from the pool and performs an action with it. This+{- | Allocates a connection from the pool and performs an action with it. This   function will block if the maximum number of connections is reached.  @since 1.0.0.0 -} withPoolConnection :: ConnectionPool -> (Connection -> IO a) -> IO a-withPoolConnection (ConnectionPool pool) =-  withResource pool+withPoolConnection (ConnectionPool pool) action =+  let+    assertConnectionIdle connection mkMsg = do+      status <- transactionStatusOrThrow connection+      case status of+        LibPQ.TransIdle ->+          pure ()+        _ ->+          withLibPQConnectionOrFailIfClosed connection $ \libPQConn -> do+            throwConnectionError (mkMsg status) libPQConn -{- |-  'executeRaw' runs a given SQL statement returning the raw underlying result.+    actionWithTxnStatusChecks connection = do+      assertConnectionIdle connection (\status -> "Connection found in pool that was not idle. It was: " <> show status)+      result <- action connection+      assertConnectionIdle connection (\status -> "Would have returned a connection to the pool that was not idle. It was: " <> show status)+      pure result+  in+    withResource pool actionWithTxnStatusChecks +{- | 'executeRaw' runs a given SQL statement returning the raw underlying result.+  All handling of stepping through the result set is left to the caller. This  potentially leaves connections open much longer than one would expect if all  of the results are not iterated through immediately *and* the data copied.@@ -236,15 +243,29 @@     Right paramBytes ->       underlyingExecute bs paramBytes connection -{- |-  An Orville handler for a LibPQ connection.+data ConnectionState+  = OpenConnection LibPQ.Connection+  | ClosedConnection +data ConnectionContext = ConnectionContext+  { i_connUtilizationLock :: MVar ()+  -- ^ Used to serialize access to the connection for the purpose of issuing commands+  , i_connCloseLock :: MVar ()+  -- ^ Used to guarantee that only one thread will close the connection. This+  -- is separate from the utilization lock because a connection should still+  -- be closeable if it is in use by another thread but should not be closed+  -- by multiple threads simultaneously.+  , i_connState :: ConnectionState+  -- ^ The underlying connection, if open+  }++{- | An Orville handler for a LibPQ connection.+ @since 1.0.0.0 -}-newtype Connection = Connection (MVar LibPQ.Connection)+newtype Connection = Connection (IORef ConnectionContext) -{- |-  'connect' is the internal, primitive connection function.+{- | 'connect' is the internal, primitive connection function.   This should not be exposed to end users, but instead wrapped in something to create a pool. @@ -275,7 +296,15 @@         LibPQ.PollingWriting ->           checkSocketAndThreadWait conn threadWaitWrite         LibPQ.PollingOk -> do-          connectionHandle <- newMVar conn+          connUseLock <- newMVar ()+          connCloseLock <- newMVar ()+          connectionHandle <-+            newIORef+              ConnectionContext+                { i_connUtilizationLock = connUseLock+                , i_connCloseLock = connCloseLock+                , i_connState = OpenConnection conn+                }           pure (Connection connectionHandle)   in     do@@ -285,8 +314,7 @@         EnableNoticeReporting -> LibPQ.enableNoticeReporting connection       poll connection -{- |-  'close' has many subtleties to it.+{- | 'close' has many subtleties to it.    First note that async exceptions are masked.  'mask' though, only works for   things that are not interruptible@@ -295,33 +323,33 @@   From the previous link, 'tryTakeMVar' is not interruptible, where @takeMVar@   *is*.  So by using 'tryTakeMVar' along with 'mask', we should be safe from   async exceptions causing us to not finish an underlying connection.  Notice-  that the only place the MVar is ever taken is here so 'tryTakeMVar' gives us-  both the non-blocking semantics to protect from async exceptions with 'mask'-  _and_ should never truly return an empty unless two threads were racing to-  close the connection, in which case.. one of them will close the connection.+  that the only place the close lock MVar is ever taken is here so+  'tryTakeMVar' gives us both the non-blocking semantics to protect from async+  exceptions with 'mask' _and_ should never truly return an empty unless two+  threads were racing to close the connection, in which case.. one of them will+  close the connection.  @since 1.0.0.0 -} close :: Connection -> IO () close (Connection handle) =   let-    underlyingFinish :: (forall a. IO a -> IO a) -> IO (Maybe ())+    underlyingFinish :: (forall a. IO a -> IO a) -> IO ()     underlyingFinish restore = do-      underlyingConnection <- tryTakeMVar handle-      restore (traverse LibPQ.finish underlyingConnection)+      connCtx <- readIORef handle+      mCloseLock <- tryTakeMVar $ i_connCloseLock connCtx+      case (mCloseLock, i_connState connCtx) of+        (Just (), OpenConnection underlyingConnection) -> do+          writeIORef handle connCtx {i_connState = ClosedConnection}+          restore (LibPQ.finish underlyingConnection)+        _ -> pure ()   in-    void $ mask underlyingFinish+    mask underlyingFinish -{- |- 'underlyingExecute' is the internal, primitive execute function.+{- | 'underlyingExecute' is the internal, primitive execute function.    This is not intended to be directly exposed to end users, but instead wrapped-  in something using a pool.  Note there are potential dragons here in that-  this calls @tryReadMvar@ and then returns an error if the MVar is not full.-  The intent is to never expose the ability to empty the `MVar` outside of this-  module, so unless a connection has been closed it *should* never be empty.-  And a connection should be closed upon removal from a resource pool (in which-  case it can't be used for this  function in the first place).+  in something using a pool.  @since 1.0.0.0 -}@@ -330,23 +358,22 @@   [Maybe BS.ByteString] ->   Connection ->   IO LibPQ.Result-underlyingExecute bs params connection = do-  libPQConn <- readLibPQConnectionOrFailIfClosed connection-  mbResult <--    LibPQ.execParams libPQConn bs (map mkInferredTextParam params) LibPQ.Text+underlyingExecute bs params connection =+  withLibPQConnectionOrFailIfClosed connection $ \libPQConn -> do+    mbResult <-+      LibPQ.execParams libPQConn bs (fmap mkInferredTextParam params) LibPQ.Text -  case mbResult of-    Nothing -> do-      throwExecutionErrorWithoutResult libPQConn bs-    Just result -> do-      execStatus <- LibPQ.resultStatus result+    case mbResult of+      Nothing -> do+        throwExecutionErrorWithoutResult libPQConn bs+      Just result -> do+        execStatus <- LibPQ.resultStatus result -      if isRowReadableStatus execStatus-        then pure result-        else throwExecutionErrorWithResult result execStatus bs+        if isRowReadableStatus execStatus+          then pure result+          else throwExecutionErrorWithResult result execStatus bs -{- |-  Escapes and quotes a string for use as a literal within a SQL command that+{- | Escapes and quotes a string for use as a literal within a SQL command that   will be executed on the given connection. This uses the @PQescapeStringConn@   function from LibPQ, which takes the character encoding of the connection   into account. Note that while @PQescapeStringConn@ does not surround the@@ -361,22 +388,21 @@ @since 1.0.0.0 -} quoteStringLiteral :: Connection -> BS.ByteString -> IO BSB.Builder-quoteStringLiteral connection unquotedString = do-  libPQConn <- readLibPQConnectionOrFailIfClosed connection-  mbEscapedString <- LibPQ.escapeStringConn libPQConn unquotedString+quoteStringLiteral connection unquotedString =+  withLibPQConnectionOrFailIfClosed connection $ \libPQConn -> do+    mbEscapedString <- LibPQ.escapeStringConn libPQConn unquotedString -  case mbEscapedString of-    Nothing ->-      throwConnectionError "Error while escaping string literal" libPQConn-    Just escapedString ->-      let-        singleQuote =-          BSB.char8 '\''-      in-        pure (singleQuote <> BSB.byteString escapedString <> singleQuote)+    case mbEscapedString of+      Nothing ->+        throwConnectionError "Error while escaping string literal" libPQConn+      Just escapedString ->+        let+          singleQuote =+            BSB.char8 '\''+        in+          pure (singleQuote <> BSB.byteString escapedString <> singleQuote) -{- |-  Escapes and quotes a string for use as an identifier within a SQL command+{- | Escapes and quotes a string for use as an identifier within a SQL command   that will be executed on the given connection. This uses the   @PQescapeIdentifier@ function from LibPQ, which takes the character encoding   of the connection into account and also applies the quotes.@@ -388,26 +414,57 @@ @since 1.0.0.0 -} quoteIdentifier :: Connection -> BS.ByteString -> IO BSB.Builder-quoteIdentifier connection unquotedString = do-  libPQConn <- readLibPQConnectionOrFailIfClosed connection-  mbEscapedString <- LibPQ.escapeIdentifier libPQConn unquotedString+quoteIdentifier connection unquotedString =+  withLibPQConnectionOrFailIfClosed connection $ \libPQConn -> do+    mbEscapedString <- LibPQ.escapeIdentifier libPQConn unquotedString -  case mbEscapedString of-    Nothing ->-      throwConnectionError "Error while escaping identifier" libPQConn-    Just quotedString ->-      pure (BSB.byteString quotedString)+    case mbEscapedString of+      Nothing ->+        throwConnectionError "Error while escaping identifier" libPQConn+      Just quotedString ->+        pure (BSB.byteString quotedString) -readLibPQConnectionOrFailIfClosed :: Connection -> IO LibPQ.Connection-readLibPQConnectionOrFailIfClosed (Connection handle) = do-  mbConn <- tryReadMVar handle+{- | Serializes access to the underlying LibPQ connection. This is necessary+  because multiple concurrent commands issued using the same connection will+  result in a dead-lock in LibPQ. -  case mbConn of-    Nothing ->-      throwIO ConnectionUsedAfterCloseError-    Just conn ->-      pure conn+  Do not nest calls to this function with the same connection or it will+  dead-lock on the MVar.+-}+withLibPQConnectionOrFailIfClosed :: Connection -> (LibPQ.Connection -> IO a) -> IO a+withLibPQConnectionOrFailIfClosed (Connection handle) withConnection = do+  connCtx <- readIORef handle+  withMVar (i_connUtilizationLock connCtx) $ \() ->+    case i_connState connCtx of+      ClosedConnection ->+        throwIO ConnectionUsedAfterCloseError+      OpenConnection conn ->+        withConnection conn +{- | Returns 'Just' the transaction status of the current connection, as reported by @libpq@, or+  'Nothing' if the connection is currently closed. See 'Database.PostgreSQL.LibPQ.transactionStatus',+  'Database.PostgreSQL.LibPQ.TransactionStatus' for more information.++@since 1.1.0.0+-}+transactionStatus :: Connection -> IO (Maybe LibPQ.TransactionStatus)+transactionStatus (Connection handle) = do+  connCtx <- readIORef handle+  withMVar (i_connUtilizationLock connCtx) $ \() ->+    case i_connState connCtx of+      ClosedConnection ->+        pure Nothing+      OpenConnection conn ->+        fmap Just (LibPQ.transactionStatus conn)++{- | Similar to 'transactionStatus', but throws a 'ConnectionUsedAfterCloseError' if the connection is closed.++@since 1.1.0.0+-}+transactionStatusOrThrow :: Connection -> IO LibPQ.TransactionStatus+transactionStatusOrThrow conn =+  withLibPQConnectionOrFailIfClosed conn LibPQ.transactionStatus+ throwConnectionError :: String -> LibPQ.Connection -> IO a throwConnectionError message conn = do   mbLibPQError <- LibPQ.errorMessage conn@@ -463,9 +520,12 @@     LibPQ.BadResponse -> False     LibPQ.NonfatalError -> False -- NonfatalError never returned from LibPQ query execution functions. It passes them to the notice processor instead.     LibPQ.FatalError -> False+#if MIN_VERSION_postgresql_libpq(0,11,0)+    LibPQ.PipelineSync -> False+    LibPQ.PipelineAbort -> False+#endif -{- |-  Packages a bytestring parameter value (which is assumed to be a value encoded+{- | Packages a bytestring parameter value (which is assumed to be a value encoded   as text that the database can use) as a parameter for executing a query.   This uses Oid 0 to cause the database to infer the type of the paremeter and   explicitly marks the parameter as being in Text format.@@ -480,8 +540,7 @@     Just value ->       Just (LibPQ.Oid 0, value, LibPQ.Text) -{- |-  Orville throws a 'ConnectionError' on an error reported by the underlying+{- | Orville throws a 'ConnectionError' on an error reported by the underlying   LibPQ connection that does not come directly from executing SQL. This could   could represent an inability to open a new database connection, but could   also represent other errors such as an error while quoting a database@@ -510,10 +569,10 @@     in       connectionErrorMessage err <> ": " <> libPQErrorMsg +-- | @since 1.0.0.0 instance Exception ConnectionError -{- |-  Orville throws a 'SqlExecutionError' when an error is reported by the+{- | Orville throws a 'SqlExecutionError' when an error is reported by the   underlying LibPQ connection during an attempt to execute SQL.  @since 1.0.0.0@@ -531,12 +590,15 @@   , sqlExecutionErrorSqlQuery :: BS.ByteString   -- ^ The SQL query that was being run when the error occurred.   }-  deriving (Show)+  deriving+    ( -- | @since 1.0.0.0+      Show+    ) +-- | @since 1.0.0.0 instance Exception SqlExecutionError -{- |-  Orville throws as 'ConnectionUsedAfterCloseError' if it attempts to use a+{- | Orville throws as 'ConnectionUsedAfterCloseError' if it attempts to use a   'Connection' value after it has already been closed. If this occurs, it is a   bug in Orville. @@ -544,6 +606,10 @@ -} data ConnectionUsedAfterCloseError   = ConnectionUsedAfterCloseError-  deriving (Show)+  deriving+    ( -- | @since 1.0.0.0+      Show+    ) +-- | @since 1.0.0.0 instance Exception ConnectionUsedAfterCloseError
src/Orville/PostgreSQL/Raw/PgTextFormatValue.hs view
@@ -18,8 +18,7 @@ import Control.Exception (Exception) import qualified Data.ByteString as BS -{- |-  A 'PgTextFormatValue' represents raw bytes that will be passed to PostgreSQL+{- | A 'PgTextFormatValue' represents raw bytes that will be passed to PostgreSQL   via LibPQ. These bytes must conform to the TEXT format of values that   PostgreSQL expects. In all cases, PostgreSQL will be allowed to infer the   type of the value based on its usage in the query.@@ -40,20 +39,33 @@ data PgTextFormatValue   = NoAssumptionsMade BS.ByteString   | AssumedToHaveNoNULValues BS.ByteString-  deriving (Show)+  deriving+    ( -- | @since 1.0.0.0+      Show+    ) +-- | @since 1.0.0.0 instance Eq PgTextFormatValue where   left == right =     toBytesForLibPQ left == toBytesForLibPQ right +{- | The exception raised when encountering a '\NUL' byte is unexpectedly found++@since 1.0.0.0+-} data NULByteFoundError   = NULByteFoundError-  deriving (Show, Eq)+  deriving+    ( -- | @since 1.0.0.0+      Show+    , -- | @since 1.0.0.0+      Eq+    ) +-- | @since 1.0.0.0 instance Exception NULByteFoundError -{- |-  Constructs a 'PgTextFormatValue' from the given bytes directly, without checking+{- | Constructs a 'PgTextFormatValue' from the given bytes directly, without checking   whether any of the bytes are '\NUL' or not. If a 'BS.ByteString' containing   a '\NUL' byte is given, the value will be truncated at the '\NUL' when it   is passed to LibPQ.@@ -68,8 +80,7 @@ unsafeFromByteString =   AssumedToHaveNoNULValues -{- |-  Constructs a 'PgTextFormatValue' from the given bytes, which will be checked+{- | Constructs a 'PgTextFormatValue' from the given bytes, which will be checked   to ensure none of them are '\NUL' before being passed to LibPQ. If a '\NUL'   byte is found an error will be raised. @@ -79,8 +90,7 @@ fromByteString =   NoAssumptionsMade -{- |-  Converts the 'PgTextFormatValue' to bytes intended to be passed to LibPQ.+{- | Converts the 'PgTextFormatValue' to bytes intended to be passed to LibPQ.   If any '\NUL' bytes are found, 'NULByteFoundError' will be returned (unless   'unsafeFromByteString' was used to construct the value). @@ -96,8 +106,7 @@         then Left NULByteFoundError         else Right anyBytes -{- |-  Converts the 'PgTextFormatValue' back to the bytes that were used to+{- | Converts the 'PgTextFormatValue' back to the bytes that were used to   construct it, losing the information about whether it would be checked   for '\NUL' bytes or not. 
src/Orville/PostgreSQL/Raw/PgTime.hs view
@@ -1,5 +1,5 @@ {- |-Copyright : Flipstone Technology Partners 2023+Copyright : Flipstone Technology Partners 2023-2025 License   : MIT Stability : Stable @@ -15,6 +15,8 @@   ) where +import Control.Applicative ((<|>))+import Control.Monad (when) import qualified Data.Attoparsec.ByteString as AttoBS import qualified Data.Attoparsec.ByteString.Char8 as AttoB8 import qualified Data.ByteString as BS@@ -23,114 +25,149 @@ import qualified Data.Fixed as Fixed import qualified Data.Time as Time import qualified Data.Word as Word+import qualified Text.Printf as Printf -{- |-  Renders a 'Time.Day' value to a textual representation for PostgreSQL.+{- | Renders a 'Time.Day' value to a textual representation for PostgreSQL.  @since 1.0.0.0 -} dayToPostgreSQL :: Time.Day -> B8.ByteString-dayToPostgreSQL =-  B8.pack . Time.showGregorian+dayToPostgreSQL date =+  case mkCommonEraDay date of+    (ce, ced) ->+      B8.pack (renderCommonEraDay ced <> renderCommonEraSuffix ce) -{- |-  An Attoparsec parser for parsing 'Time.Day' from YYYY-MM-DD format. Parsing-  fails if given an invalid 'Time.Day'.+{- | An Attoparsec parser for parsing 'Time.Day' from a string in PostgreSQL's ISO style+  (YYYY-MM-DD[ BC]) format. Parsing fails if given an invalid 'Time.Day'.  @since 1.0.0.0 -} day :: AttoB8.Parser Time.Day day = do-  (y, yearCount) <- decimalWithCount <* AttoB8.char '-'-  if yearCount < 4-    then fail "invalid date format"-    else do-      m <- twoDigits <* AttoB8.char '-'-      d <- twoDigits-      maybe (fail "invalid date format") pure $ Time.fromGregorianValid y m d--{- |-  An Attoparsec parser for parsing 2-digit integral numbers.--@since 1.0.0.0--}-twoDigits :: Integral a => AttoB8.Parser a-twoDigits = do-  tens <- AttoB8.digit-  ones <- AttoB8.digit-  pure $ fromChar tens * 10 + fromChar ones+  ced <- commonEraDay+  ce <- parseCommonEraSuffix+  commonEraDayToISO8601Day ce ced -fromChar :: Integral a => Char -> a-fromChar c = fromIntegral $ Char.ord c - Char.ord '0'+commonEraDay :: AttoB8.Parser CommonEraDay+commonEraDay = do+  (y, yearCount) <- decimalWithCount <* AttoB8.char '-'+  when (yearCount < 4) (fail "invalid date format")+  m <- twoDigits <* AttoB8.char '-'+  d <- twoDigits+  pure $ CommonEraDay y m d -{- |-  Renders a 'Time.UTCTime' value to a textual representation for PostgreSQL.+{- | Renders a 'Time.UTCTime' value to a textual representation for PostgreSQL.  @since 1.0.0.0 -} utcTimeToPostgreSQL :: Time.UTCTime -> B8.ByteString-utcTimeToPostgreSQL =-  B8.pack . Time.formatTime Time.defaultTimeLocale "%0Y-%m-%d %H:%M:%S%Q+00"+utcTimeToPostgreSQL time =+  let+    -- DiffTime lacks support for %Q (fractional seconds), so be sure to format+    -- via a type that supports %Q.+    formattedTime =+      Time.formatTime Time.defaultTimeLocale "%0H:%0M:%0S%Q+00"+        . Time.timeToTimeOfDay+        $ Time.utctDayTime time+  in+    case mkCommonEraDay (Time.utctDay time) of+      (ce, ced) ->+        B8.pack $+          renderCommonEraDay ced <> " " <> formattedTime <> renderCommonEraSuffix ce -{- |-  An Attoparsec parser for parsing 'Time.UTCTime' from an ISO-8601 style-  datetime and timezone with a few PostgreSQL-specific exceptions. See-  'localTime' for more details.+{- | An Attoparsec parser for 'Time.UTCTime' from a PostgreSQL ISO style timestamptz format.  @since 1.0.0.0 -} utcTime :: AttoB8.Parser Time.UTCTime utcTime = do-  lt <- localTime-  sign <- AttoB8.satisfy (\char -> char == '+' || char == '-' || char == 'Z')-  if sign == 'Z'-    then pure $ Time.localTimeToUTC Time.utc lt-    else do-      hour <- twoDigits-      minute <- AttoB8.option 0 $ AttoB8.choice [AttoB8.char ':' *> twoDigits, twoDigits]-      second <- AttoB8.option 0 $ AttoB8.char ':' *> twoDigits-      let-        offsetSeconds :: Int-        offsetSeconds = (second + minute * 60 + hour * 3600) * if sign == '+' then (-1) else 1-        offsetNominalDiffTime = fromIntegral offsetSeconds-        diffTime = Time.timeOfDayToTime (Time.localTimeOfDay lt)-        utcTimeWithoutOffset = Time.UTCTime (Time.localDay lt) diffTime-      pure $ Time.addUTCTime offsetNominalDiffTime utcTimeWithoutOffset+  ced <- commonEraDay <* AttoB8.char ' '+  time <- timeOfDay+  offset <- signedTimezoneOffset+  ce <- parseCommonEraSuffix+  validDay <- commonEraDayToISO8601Day ce ced+  pure $ Time.addUTCTime offset (Time.UTCTime validDay (Time.timeOfDayToTime time)) -{- |-  Renders a 'Time.LocalTime' value to a textual representation for PostgreSQL.+signedTimezoneOffset :: AttoB8.Parser Time.NominalDiffTime+signedTimezoneOffset = do+  sign <- AttoB8.satisfy (\char -> char == '+' || char == '-')+  case sign of+    '+' -> fmap negate timezoneOffset+    _ -> timezoneOffset +timezoneOffset :: AttoB8.Parser Time.NominalDiffTime+timezoneOffset = do+  h <- twoDigits+  m <- AttoB8.option 0 (AttoB8.char ':' *> twoDigits)+  s <- AttoB8.option 0 (AttoB8.char ':' *> twoDigits)+  pure . fromIntegral $ (s :: Int) + m * 60 + h * 3600++{- | Renders a 'Time.LocalTime' value to a textual representation for PostgreSQL.+ @since 1.0.0.0 -} localTimeToPostgreSQL :: Time.LocalTime -> B8.ByteString-localTimeToPostgreSQL =-  B8.pack . Time.formatTime Time.defaultTimeLocale "%0Y-%m-%d %H:%M:%S%Q"+localTimeToPostgreSQL time =+  let+    formattedTime =+      Time.formatTime Time.defaultTimeLocale "%0H:%0M:%0S%Q" $+        Time.localTimeOfDay time+  in+    case mkCommonEraDay (Time.localDay time) of+      (ce, ced) ->+        B8.pack $+          renderCommonEraDay ced <> " " <> formattedTime <> renderCommonEraSuffix ce -{- |-  An Attoparsec parser for parsing 'Time.LocalTime' from an ISO-8601 style-  datetime with a few exceptions. The separator between the date and time-  is always @\' \'@ and never @\'T\'@.+{- | An Attoparsec parser for 'Time.LocalTime' from PostgreSQL's ISO style timestamp format.  @since 1.0.0.0 -} localTime :: AttoB8.Parser Time.LocalTime localTime = do-  Time.LocalTime <$> day <* AttoB8.char ' ' <*> timeOfDay+  ced <- commonEraDay <* AttoB8.char ' '+  time <- timeOfDay+  ce <- parseCommonEraSuffix+  validDay <- commonEraDayToISO8601Day ce ced+  pure $ Time.LocalTime validDay time -{- |-  An Attoparsec parser for parsing 'Time.TimeOfDay' from an ISO-8601 style time.+{- | An Attoparsec parser for 'Time.TimeOfDay' from PostgreSQL's ISO style time format.  @since 1.0.0.0 -} timeOfDay :: AttoB8.Parser Time.TimeOfDay timeOfDay = do   h <- twoDigits <* AttoB8.char ':'-  m <- twoDigits-  s <- AttoB8.option 0 (AttoB8.char ':' *> seconds)-  maybe (fail "invalid time format") pure $ Time.makeTimeOfDayValid h m s+  m <- twoDigits <* AttoB8.char ':'+  s <- seconds+  case Time.makeTimeOfDayValid h m s of+    Nothing -> fail "invalid time format"+    Just validTime -> pure validTime -{- |-  An Attoparsec parser for parsing a base-10 number. Returns the number of+{- | An Attoparsec parser for parsing 'Fixed.Pico' from SS[.sss] format. This can+  handle more resolution than PostgreSQL uses, and will truncate the seconds+  fraction if more than 12 digits are present.++@since 1.0.0.0+-}+seconds :: AttoB8.Parser Fixed.Pico+seconds = do+  s <- twoDigits+  (dec, charCount) <- AttoB8.option (0, 0) (AttoB8.char '.' *> decimalWithCount)+  if charCount >= 12+    then pure . Fixed.MkFixed $ (s * 10 ^ (12 :: Int)) + (dec `div` 10 ^ (charCount - 12))+    else pure . Fixed.MkFixed $ (s * 10 ^ (12 :: Int)) + (dec * 10 ^ (12 - charCount))++{- | An Attoparsec parser for parsing 2-digit integral numbers.++@since 1.0.0.0+-}+twoDigits :: Integral a => AttoB8.Parser a+twoDigits = do+  tens <- fmap (fromIntegral . Char.digitToInt) AttoB8.digit+  ones <- fmap (fromIntegral . Char.digitToInt) AttoB8.digit+  pure $ tens * 10 + ones++{- | An Attoparsec parser for parsing a base-10 number. Returns the number of   digits consumed. Based off of 'AttoB8.decimal'.  @since 1.0.0.0@@ -143,17 +180,45 @@ appendDigit :: Integral a => a -> Word.Word8 -> a appendDigit a w = a * 10 + fromIntegral (w - 48) -{- |-  An Attoparsec parser for parsing 'Fixed.Pico' from SS[.sss] format. This can-  handle more resolution than PostgreSQL uses, and will truncate the seconds-  fraction if more than 12 digits are present.+data CommonEra+  = BCE+  | CE -@since 1.0.0.0--}-seconds :: AttoB8.Parser Fixed.Pico-seconds = do-  s <- twoDigits-  (dec, charCount) <- AttoB8.option (0, 0) (AttoB8.char '.' *> decimalWithCount)-  if charCount >= 12-    then pure $ Fixed.MkFixed $ (s * 10 ^ (12 :: Int)) + (dec `div` 10 ^ (charCount - 12))-    else pure $ Fixed.MkFixed $ (s * 10 ^ (12 :: Int)) + (dec * 10 ^ (12 - charCount))+renderCommonEraSuffix :: CommonEra -> String+renderCommonEraSuffix ce =+  case ce of+    BCE -> " BC"+    CE -> mempty++parseCommonEraSuffix :: AttoB8.Parser CommonEra+parseCommonEraSuffix =+  (BCE <$ AttoB8.string (B8.pack " BC") <|> pure CE) <* AttoB8.endOfInput++data CommonEraDay = CommonEraDay+  { _commonEraDayYear :: Integer+  , _commonEraDayMonth :: Int+  , _commonEraDayDay :: Int+  }++mkCommonEraDay :: Time.Day -> (CommonEra, CommonEraDay)+mkCommonEraDay date =+  case Time.toGregorian date of+    (y, m, d) ->+      fmap+        (\cey -> CommonEraDay cey m d)+        (if y <= 0 then (BCE, 1 - y) else (CE, y))++renderCommonEraDay :: CommonEraDay -> String+renderCommonEraDay (CommonEraDay y m d) =+  Printf.printf "%04d-%02d-%02d" y m d++commonEraDayToISO8601Day :: MonadFail m => CommonEra -> CommonEraDay -> m Time.Day+commonEraDayToISO8601Day ce (CommonEraDay y m d) =+  let+    iso8601Year = case ce of+      BCE -> 1 - y+      CE -> y+  in+    case Time.fromGregorianValid iso8601Year m d of+      Nothing -> fail "invalid date"+      Just valid -> pure valid
src/Orville/PostgreSQL/Raw/RawSql.hs view
@@ -1,6 +1,6 @@ {- | -Copyright : Flipstone Technology Partners 2023+Copyright : Flipstone Technology Partners 2023-2025 License   : MIT Stability : Stable @@ -19,6 +19,7 @@   , execute   , executeVoid   , connectionQuoting+  , appendWithCommaSpace      -- * Fragments provided for convenience   , space@@ -32,6 +33,7 @@   , stringLiteral   , identifier   , parenthesized+  , nullLiteral      -- * Integer values as literals   , intDecLiteral@@ -61,6 +63,7 @@ import Data.Functor.Identity (Identity (Identity, runIdentity)) import qualified Data.Int as Int import qualified Data.List as List+import qualified Data.List.NonEmpty as NE import qualified Data.Text as T import qualified Data.Text.Encoding as TextEnc import qualified Database.PostgreSQL.LibPQ as LibPQ@@ -70,8 +73,7 @@ import Orville.PostgreSQL.Raw.SqlValue (SqlValue) import qualified Orville.PostgreSQL.Raw.SqlValue as SqlValue -{- |-  'RawSql' provides a type for efficiently constructing raw SQL statements from+{- | 'RawSql' provides a type for efficiently constructing raw SQL statements from   smaller parts and then executing them. It also supports using placeholder   values to pass parameters with a query without having to interpolate them as   part of the actual SQL state and being exposed to SQL injection.@@ -84,18 +86,33 @@   | StringLiteral BS.ByteString   | Identifier BS.ByteString   | Append RawSql RawSql+  | Empty +-- | @since 1.0.0.0 instance Semigroup RawSql where   (SqlSection builderA) <> (SqlSection builderB) =     SqlSection (builderA <> builderB)+  Empty <> otherB = otherB+  otherA <> Empty = otherA   otherA <> otherB =     Append otherA otherB +-- | @since 1.0.0.0 instance Monoid RawSql where-  mempty = SqlSection mempty+  mempty = Empty -{- |- 'SqlExpression' provides a common interface for converting types to and from+{- | Append two 'RawSql' values together with 'commaSpace' in between, unless+  either of the values are empty.++@since 1.1.0.0+-}+appendWithCommaSpace :: RawSql -> RawSql -> RawSql+appendWithCommaSpace a b = case (a, b) of+  (Empty, x) -> x+  (x, Empty) -> x+  (x, y) -> x <> commaSpace <> y++{- | 'SqlExpression' provides a common interface for converting types to and from  'RawSql', either via 'toRawSql' and 'unsafeFromRawSql', or the convenience  function 'unsafeSqlExpression'. Orville defines a large number of types that  represent various fragments of SQL statements as well as functions to help@@ -112,12 +129,12 @@   toRawSql :: a -> RawSql   unsafeFromRawSql :: RawSql -> a +-- | @since 1.0.0.0 instance SqlExpression RawSql where   toRawSql = id   unsafeFromRawSql = id -{- |-A convenience function for creating an arbitrary 'SqlExpression' from a+{- | A convenience function for creating an arbitrary 'SqlExpression' from a 'String'. Great care should be exercised when using this function as it cannot provide any sort of guarantee that the string passed is usable to generate valid SQL via the rest of Orville's 'Orville.PostgreSQL.Expr' API.@@ -135,8 +152,7 @@ unsafeSqlExpression =   unsafeFromRawSql . fromString -{- |-  Provides procedures for quoting parts of a raw SQL query so that they can be+{- | Provides procedures for quoting parts of a raw SQL query so that they can be   safely executed. Quoting may be done in some 'Monad' m, allowing for the use   of quoting operations provided by 'Conn.Connection', which operates in the   'IO' monad.@@ -150,8 +166,7 @@   , quoteIdentifier :: BS.ByteString -> m BSB.Builder   } -{- |-  Quoting done in pure Haskell that is suitable for showing SQL examples,+{- | Quoting done in pure Haskell that is suitable for showing SQL examples,   but is not guaranteed to be sufficient for all database connections. For   quoting that is based on the actual connection to the database, see   'connectionQuoting'.@@ -188,8 +203,7 @@         <> BSB.byteString (B8.unfoldr quote (Right unquoted))         <> quoteBytes -{- |-  Quoting done in IO using the quoting functions provided by the connection,+{- | Quoting done in IO using the quoting functions provided by the connection,   which can apply quoting based on the specific connection properties.    If you don't have a connection available and are only planning on using the@@ -204,8 +218,7 @@     , quoteIdentifier = Conn.quoteIdentifier connection     } -{- |-  Constructs the actual SQL bytestring and parameter values that will be passed+{- | Constructs the actual SQL bytestring and parameter values that will be passed   to the database to execute a 'RawSql' query. Any string literals that are   included in the SQL expression will be quoted using the given quoting   directive.@@ -225,8 +238,7 @@     , DList.toList (paramValues finalProgress)     ) -{- |-  Builds the bytes that represent the raw SQL. These bytes may not be executable+{- | Builds the bytes that represent the raw SQL. These bytes may not be executable   on their own, because they may contain placeholders that must be filled in,   but can be useful for inspecting SQL queries. @@ -236,8 +248,7 @@ toExampleBytes =   fst . runIdentity . toBytesAndParams exampleQuoting -{- |-  This is an internal datatype used during the SQL building process to track+{- | This is an internal datatype used during the SQL building process to track   how many params have been seen so that placeholder indices (e.g. '$1', etc)   can be generated to include in the SQL. @@ -248,8 +259,7 @@   , paramValues :: DList (Maybe PgTextFormatValue)   } -{- |-  An initial value for 'ParamsProgress' that indicates no params have been been+{- | An initial value for 'ParamsProgress' that indicates no params have been been   encountered yet.  @since 1.0.0.0@@ -261,8 +271,7 @@     , paramValues = DList.empty     } -{- |-  Adds a parameter value to the end of the params list, tracking the count+{- | Adds a parameter value to the end of the params list, tracking the count   of parameters as it does so.  @since 1.0.0.0@@ -274,8 +283,7 @@     , paramValues = DList.snoc values newValue     } -{- |-  Constructs a bytestring builder that can be executed to get the bytes for a+{- | Constructs a bytestring builder that can be executed to get the bytes for a   section of 'RawSql'. This function takes and returns a 'ParamsProgress' so   that placeholder indices (e.g. '$1') and their corresponding parameter values   can be tracked across multiple sections of raw SQL.@@ -298,20 +306,57 @@     Identifier unquotedIdentifier -> do       quotedIdentifier <- quoteIdentifier quoting unquotedIdentifier       pure (quotedIdentifier, progress)-    Parameter value ->-      let-        newProgress = snocParam progress (SqlValue.toPgValue value)-        placeholder = BSB.stringUtf8 "$" <> BSB.intDec (paramCount newProgress)-      in-        pure (placeholder, newProgress)+    Parameter value -> pure $ buildParameterSql value progress     Append first second -> do       (firstBuilder, nextProgress) <- buildSqlWithProgress quoting progress first       (secondBuilder, finalProgress) <- buildSqlWithProgress quoting nextProgress second       pure (firstBuilder <> secondBuilder, finalProgress)+    Empty ->+      pure (mempty, progress) -{- |-  Constructs a 'RawSql' from a 'String' value using UTF-8 encoding.+buildParameterSql ::+  SqlValue.SqlValue ->+  ParamsProgress ->+  (BSB.Builder, ParamsProgress)+buildParameterSql =+  let+    mkSingleValue mbPgVal currentProgress =+      let+        newProgress = snocParam currentProgress mbPgVal+        placeholder = BSB.stringUtf8 "$" <> BSB.intDec (paramCount newProgress)+      in+        (placeholder, newProgress) +    mkRowValue ::+      NE.NonEmpty (ParamsProgress -> (BSB.Builder, ParamsProgress)) ->+      ParamsProgress ->+      (BSB.Builder, ParamsProgress)+    mkRowValue (mkFirstRowVal NE.:| mkRowVals) currentProgress =+      let+        (firstRowVal, firstRowProgress) = mkFirstRowVal currentProgress+        (remainingRowVals, newProgress) =+          foldr+            ( \mkNextRow mkRemainingRows rowProgress ->+                let+                  (valBuilder, newRowProgress) = mkNextRow rowProgress+                  (remainingBuilder, remainingProgress) = mkRemainingRows newRowProgress+                in+                  (BSB.stringUtf8 "," <> valBuilder <> remainingBuilder, remainingProgress)+            )+            (\p -> (mempty, p))+            mkRowVals+            firstRowProgress+        placeholder = BSB.stringUtf8 "(" <> firstRowVal <> remainingRowVals <> BSB.stringUtf8 ")"+      in+        (placeholder, newProgress)+  in+    SqlValue.foldSqlValue+      (mkSingleValue . Just)+      mkRowValue+      (mkSingleValue Nothing)++{- | Constructs a 'RawSql' from a 'String' value using UTF-8 encoding.+   Note that because the string is treated as raw SQL, it is completely up to   the caller to protected againt SQL-injection attacks when using this   function. Never use this function with input read from an untrusted source.@@ -322,8 +367,7 @@ fromString =   SqlSection . BSB.stringUtf8 -{- |-  Constructs a 'RawSql' from a 'T.Text' value using UTF-8 encoding.+{- | Constructs a 'RawSql' from a 'T.Text' value using UTF-8 encoding.    Note that because the text is treated as raw SQL, it is completely up to the   caller to protected againt SQL-injection attacks when using this function.@@ -335,8 +379,7 @@ fromText =   SqlSection . TextEnc.encodeUtf8Builder -{- |-  Constructs a 'RawSql' from a 'BS.ByteString' value, which is assumed to be+{- | Constructs a 'RawSql' from a 'BS.ByteString' value, which is assumed to be   encoded sensibly for the database to handle.    Note that because the string is treated as raw SQL, it is completely up to@@ -349,8 +392,7 @@ fromBytes =   SqlSection . BSB.byteString -{- |-  Includes an input parameter in the 'RawSql' statement that will be passed+{- | Includes an input parameter in the 'RawSql' statement that will be passed   using placeholders (e.g. '$1') rather than being included directly in the SQL   statement. This is the correct way to include input from untrusted sources as   part of a 'RawSql' query. The parameter must be formatted in a textual@@ -363,8 +405,7 @@ parameter =   Parameter -{- |-  Includes a bytestring value as a string literal in the SQL statement. The+{- | Includes a bytestring value as a string literal in the SQL statement. The   string literal will be quoted and escaped for you; the value provided should   not include surrounding quotes or quote special characters. @@ -379,8 +420,7 @@ stringLiteral =   StringLiteral -{- |-  Includes a bytestring value as an identifier in the SQL statement. The+{- | Includes a bytestring value as an identifier in the SQL statement. The   identifier will be quoted and escaped for you; the value provided should not   include surrounding quotes or quote special characters. @@ -390,8 +430,7 @@ identifier =   Identifier -{- |-  Concatenates a list of 'RawSql' values using another 'RawSql' value as the+{- | Concatenates a list of 'RawSql' values using another 'RawSql' value as the   separator between the items.  @since 1.0.0.0@@ -400,11 +439,10 @@ intercalate separator =   mconcat     . List.intersperse separator-    . map toRawSql+    . fmap toRawSql     . Fold.toList -{- |-  Executes a 'RawSql' value using the 'Conn.executeRaw' function. Make sure+{- | Executes a 'RawSql' value using the 'Conn.executeRaw' function. Make sure   to read the documentation of 'Conn.executeRaw' for caveats and warnings.   Use with caution. @@ -418,8 +456,7 @@   (sqlBytes, params) <- toBytesAndParams (connectionQuoting connection) sql   Conn.executeRaw connection sqlBytes params -{- |-  Executes a 'RawSql' value using the 'Conn.executeRawVoid' function. Make sure+{- | Executes a 'RawSql' value using the 'Conn.executeRawVoid' function. Make sure   to read the documentation of 'Conn.executeRawVoid' for caveats and warnings.   Use with caution. @@ -429,43 +466,66 @@ @since 1.0.0.0 -} executeVoid :: SqlExpression sql => Conn.Connection -> sql -> IO ()-executeVoid connection sql = do-  void $ execute connection sql+executeVoid connection =+  void . execute connection --- | Just a plain old space, provided for convenience.+{- | Just a plain old space, provided for convenience.++@since 1.0.0.0+-} space :: RawSql space = fromString " " --- | Just a plain old comma, provided for convenience.+{- | Just a plain old comma, provided for convenience.++@since 1.0.0.0+-} comma :: RawSql comma = fromString "," --- | Comma space separator, provided for convenience.+{- | Comma space separator, provided for convenience.++@since 1.0.0.0+-} commaSpace :: RawSql commaSpace = fromString ", " --- | Just a plain old left paren, provided for convenience.+{- | Just a plain old left paren, provided for convenience.++@since 1.0.0.0+-} leftParen :: RawSql leftParen = fromString "(" --- | Just a plain old right paren, provided for convenience.+{- | Just a plain old right paren, provided for convenience.++@since 1.0.0.0+-} rightParen :: RawSql rightParen = fromString ")" --- | Just a plain period, provided for convenience.+{- | Just a plain period, provided for convenience.++@since 1.0.0.0+-} dot :: RawSql dot = fromString "." --- | Just a plain double quote, provided for convenience.+{- | Just a plain double quote, provided for convenience.++@since 1.0.0.0+-} doubleQuote :: RawSql doubleQuote = fromString "\"" --- | Just two colons, provided for convenience.+{- | Just two colons, provided for convenience.++@since 1.0.0.0+-} doubleColon :: RawSql doubleColon = fromString "::" -{- |-  Constructs a 'RawSql' from an 'Int.Int8' value. The integral value is+{- | Constructs a 'RawSql' from an 'Int.Int8' value. The integral value is   included directly in the SQL string, not passed as a parameter. When dealing   with user input, it is better to use 'parameter' whenever possible. @@ -475,8 +535,7 @@ int8DecLiteral =   SqlSection . BSB.int8Dec -{- |-  Constructs a 'RawSql' from an 'Int.Int16' value. The integral value is+{- | Constructs a 'RawSql' from an 'Int.Int16' value. The integral value is   included directly in the SQL string, not passed as a parameter. When dealing   with user input, it is better to use 'parameter' whenever possible. @@ -486,8 +545,7 @@ int16DecLiteral =   SqlSection . BSB.int16Dec -{- |-  Constructs a 'RawSql' from an 'Int.Int32' value. The integral value is+{- | Constructs a 'RawSql' from an 'Int.Int32' value. The integral value is   included directly in the SQL string, not passed as a parameter. When dealing   with user input, it is better to use 'parameter' whenever possible. @@ -497,8 +555,7 @@ int32DecLiteral =   SqlSection . BSB.int32Dec -{- |-  Constructs a 'RawSql' from an 'Int.Int64' value. The integral value is+{- | Constructs a 'RawSql' from an 'Int.Int64' value. The integral value is   included directly in the SQL string, not passed as a parameter. When dealing   with user input, it is better to use 'parameter' whenever possible. @@ -508,8 +565,7 @@ int64DecLiteral =   SqlSection . BSB.int64Dec -{- |-  Constructs a 'RawSql' from an 'Int' value. The integral value is included+{- | Constructs a 'RawSql' from an 'Int' value. The integral value is included   directly in the SQL string, not passed as a parameter. When dealing with user   input, it is better to use 'parameter' whenever possible. @@ -519,8 +575,14 @@ intDecLiteral =   SqlSection . BSB.intDec -{- |-  Constructs a 'RawSql' by putting parentheses around an arbitrary expression.+{- | Constructs a 'RawSql' representing the SQL @NULL@ literal.++@since 1.1.0.0+-}+nullLiteral :: RawSql+nullLiteral = fromString "NULL"++{- | Constructs a 'RawSql' by putting parentheses around an arbitrary expression.   The result is returned as a 'RawSql'. It is up to the caller to decide   whether it should be wrapped in a more-specific expression type. 
src/Orville/PostgreSQL/Raw/SqlValue.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE OverloadedStrings #-}+ {- |  Copyright : Flipstone Technology Partners 2023@@ -11,6 +13,7 @@ -} module Orville.PostgreSQL.Raw.SqlValue   ( SqlValue+  , foldSqlValue   , isSqlNull   , sqlNull   , fromInt8@@ -45,9 +48,9 @@   , toUTCTime   , fromLocalTime   , toLocalTime+  , fromRow   , fromRawBytes   , fromRawBytesNullable-  , toPgValue   ) where @@ -58,6 +61,7 @@ import qualified Data.ByteString.Builder as BSB import qualified Data.ByteString.Lazy as LBS import Data.Int (Int16, Int32, Int64, Int8)+import qualified Data.List.NonEmpty as NE import qualified Data.Text as T import qualified Data.Text.Encoding as TextEnc import qualified Data.Time as Time@@ -68,8 +72,7 @@ import qualified Orville.PostgreSQL.Raw.PgTextFormatValue as PgTextFormatValue import qualified Orville.PostgreSQL.Raw.PgTime as PgTime -{- |-  'SqlValue' represents a value that is in encoded format for use with LibPQ.+{- | 'SqlValue' represents a value that is in encoded format for use with LibPQ.   It is used both for values passed to LibPQ and values parsed from LibPQ. The   conversion functions in "Orville.PostgreSQL.Raw.SqlValue" can be used to   convert to and from the value.@@ -78,22 +81,46 @@ -} data SqlValue   = SqlValue PgTextFormatValue+  | SqlRowValue (NE.NonEmpty SqlValue)   | SqlNull-  deriving (Eq)+  deriving+    ( -- | @since 1.0.0.0+      Eq+    ) -{- |-  Checks whether the 'SqlValue' represents a SQL NULL value in the database.+{- | Covert a 'SqlValue' to an 'r', handling a plain value, a row, and a null independently.  @since 1.0.0.0 -}+foldSqlValue ::+  (PgTextFormatValue -> r) ->+  (NE.NonEmpty r -> r) ->+  r ->+  SqlValue ->+  r+foldSqlValue onValue onRow onNull = go+ where+  go sqlValue =+    case sqlValue of+      SqlValue value ->+        onValue value+      SqlRowValue values ->+        onRow $ fmap go values+      SqlNull ->+        onNull++{- | Checks whether the 'SqlValue' represents a SQL NULL value in the database.++@since 1.0.0.0+-} isSqlNull :: SqlValue -> Bool isSqlNull sqlValue =   case sqlValue of     SqlValue _ -> False+    SqlRowValue _ -> False     SqlNull -> True -{- |-  A value of 'SqlValue' that will be interpreted as a SQL NULL value when+{- | A value of 'SqlValue' that will be interpreted as a SQL NULL value when   passed to the database.  @since 1.0.0.0@@ -102,24 +129,7 @@ sqlNull =   SqlNull -{- |-  Converts a 'SqlValue' to its underlying raw bytes as it will be represented-  when sent to the database. The output should be recognizable as similar to-  values you would write in a query. If the value represents a SQL NULL value,-  'Nothing' is returned.--@since 1.0.0.0--}-toPgValue :: SqlValue -> Maybe PgTextFormatValue-toPgValue sqlValue =-  case sqlValue of-    SqlValue value ->-      Just value-    SqlNull ->-      Nothing--{- |-  Creates a 'SqlValue' from a raw bytestring as if the bytes had been returned+{- | Creates a 'SqlValue' from a raw bytestring as if the bytes had been returned   by the database. This function does not interpret the bytes in any way, but   using decode functions on them might fail depending on whether the bytes can   be parsed as the requested type.@@ -128,14 +138,17 @@   function. See 'fromRawBytesNullable' for how to represent a nullable   raw value. +  Warning: Will throw NULByteFoundError if there is a NULL byte. For arbitrary+           binary data see:+           https://www.postgresql.org/docs/current/datatype-binary.html#DATATYPE-BINARY-BYTEA-HEX-FORMAT+ @since 1.0.0.0 -} fromRawBytes :: BS.ByteString -> SqlValue fromRawBytes =   SqlValue . PgTextFormatValue.fromByteString -{- |-  Creates a 'SqlValue' from a raw bytestring. If 'Nothing' is specified as the+{- | Creates a 'SqlValue' from a raw bytestring. If 'Nothing' is specified as the   input parameter then the resulting 'SqlValue' will represent a NULL value in   SQL. Otherwise, the bytes given are used in the same way as 'fromRawBytes'. @@ -145,8 +158,7 @@ fromRawBytesNullable =   maybe sqlNull fromRawBytes -{- |-  Encodes an 'Int8' value for use with the database.+{- | Encodes an 'Int8' value for use with the database.  @since 1.0.0.0 -}@@ -154,8 +166,7 @@ fromInt8 =   fromBSBuilderWithNoNULs BSB.int8Dec -{- |-  Attempts to decode a 'SqlValue' as a Haskell 'Int8' value. If decoding fails,+{- | Attempts to decode a 'SqlValue' as a Haskell 'Int8' value. If decoding fails,   'Nothing' is returned.  @since 1.0.0.0@@ -164,8 +175,7 @@ toInt8 =   toParsedValue (AttoB8.signed AttoB8.decimal) -{- |-  Encodes an 'Int16' value for use with the database.+{- | Encodes an 'Int16' value for use with the database.  @since 1.0.0.0 -}@@ -173,8 +183,7 @@ fromInt16 =   fromBSBuilderWithNoNULs BSB.int16Dec -{- |-  Attempts to decode a 'SqlValue' as a Haskell 'Int16' value. If decoding+{- | Attempts to decode a 'SqlValue' as a Haskell 'Int16' value. If decoding   fails, 'Nothing' is returned.  @since 1.0.0.0@@ -183,8 +192,7 @@ toInt16 =   toParsedValue (AttoB8.signed AttoB8.decimal) -{- |-  Encodes an 'Int32' value for use with the database.+{- | Encodes an 'Int32' value for use with the database.  @since 1.0.0.0 -}@@ -192,8 +200,7 @@ fromInt32 =   fromBSBuilderWithNoNULs BSB.int32Dec -{- |-  Attempts to decode a 'SqlValue' as a Haskell 'Int32' value. If decoding+{- | Attempts to decode a 'SqlValue' as a Haskell 'Int32' value. If decoding   fails, 'Nothing' is returned.  @since 1.0.0.0@@ -202,8 +209,7 @@ toInt32 =   toParsedValue (AttoB8.signed AttoB8.decimal) -{- |-  Encodes an 'Int64' value for use with the database.+{- | Encodes an 'Int64' value for use with the database.  @since 1.0.0.0 -}@@ -211,8 +217,7 @@ fromInt64 =   fromBSBuilderWithNoNULs BSB.int64Dec -{- |-  Attempts to decode a 'SqlValue' as a Haskell 'Int' value. If decoding fails,+{- | Attempts to decode a 'SqlValue' as a Haskell 'Int' value. If decoding fails,   'Nothing' is returned.  @since 1.0.0.0@@ -221,8 +226,7 @@ toInt64 =   toParsedValue (AttoB8.signed AttoB8.decimal) -{- |-  Encodes an 'Int' value for use with the database.+{- | Encodes an 'Int' value for use with the database.  @since 1.0.0.0 -}@@ -230,8 +234,7 @@ fromInt =   fromBSBuilderWithNoNULs BSB.intDec -{- |-  Attempts to decode a 'SqlValue' as a Haskell 'Int' value. If decoding fails,+{- | Attempts to decode a 'SqlValue' as a Haskell 'Int' value. If decoding fails,   'Nothing' is returned.  @since 1.0.0.0@@ -240,8 +243,7 @@ toInt =   toParsedValue (AttoB8.signed AttoB8.decimal) -{- |-  Encodes a 'Word8' value for use with the database.+{- | Encodes a 'Word8' value for use with the database.  @since 1.0.0.0 -}@@ -249,8 +251,7 @@ fromWord8 =   fromBSBuilderWithNoNULs BSB.word8Dec -{- |-  Attempts to decode a 'SqlValue' as a Haskell 'Word8' value. If decoding+{- | Attempts to decode a 'SqlValue' as a Haskell 'Word8' value. If decoding   fails, 'Nothing' is returned.  @since 1.0.0.0@@ -259,8 +260,7 @@ toWord8 =   toParsedValue (AttoB8.signed AttoB8.decimal) -{- |-  Encodes a 'Word16' value for use with the database.+{- | Encodes a 'Word16' value for use with the database.  @since 1.0.0.0 -}@@ -268,8 +268,7 @@ fromWord16 =   fromBSBuilderWithNoNULs BSB.word16Dec -{- |-  Attempts to decode a 'SqlValue' as a Haskell 'Word16' value. If decoding+{- | Attempts to decode a 'SqlValue' as a Haskell 'Word16' value. If decoding   fails, 'Nothing' is returned.  @since 1.0.0.0@@ -278,8 +277,7 @@ toWord16 =   toParsedValue (AttoB8.signed AttoB8.decimal) -{- |-  Encodes a 'Word32' value for use with the database.+{- | Encodes a 'Word32' value for use with the database.  @since 1.0.0.0 -}@@ -287,8 +285,7 @@ fromWord32 =   fromBSBuilderWithNoNULs BSB.word32Dec -{- |-  Attempts to decode a 'SqlValue' as a Haskell 'Word32' value. If decoding+{- | Attempts to decode a 'SqlValue' as a Haskell 'Word32' value. If decoding   fails, 'Nothing' is returned.  @since 1.0.0.0@@ -297,8 +294,7 @@ toWord32 =   toParsedValue (AttoB8.signed AttoB8.decimal) -{- |-  Encodes a 'Word64' value for use with the database.+{- | Encodes a 'Word64' value for use with the database.  @since 1.0.0.0 -}@@ -306,8 +302,7 @@ fromWord64 =   fromBSBuilderWithNoNULs BSB.word64Dec -{- |-  Attempts to decode a 'SqlValue' as a Haskell 'Word64' value. If decoding+{- | Attempts to decode a 'SqlValue' as a Haskell 'Word64' value. If decoding   fails, 'Nothing' is returned.  @since 1.0.0.0@@ -316,8 +311,7 @@ toWord64 =   toParsedValue (AttoB8.signed AttoB8.decimal) -{- |-  Encodes a 'Word' value for use with the database.+{- | Encodes a 'Word' value for use with the database.  @since 1.0.0.0 -}@@ -325,8 +319,7 @@ fromWord =   fromBSBuilderWithNoNULs BSB.wordDec -{- |-  Attempts to decode a 'SqlValue' as a Haskell 'Word' value. If decoding fails,+{- | Attempts to decode a 'SqlValue' as a Haskell 'Word' value. If decoding fails,   'Nothing' is returned.  @since 1.0.0.0@@ -335,8 +328,7 @@ toWord =   toParsedValue (AttoB8.signed AttoB8.decimal) -{- |-  Encodes a 'Double' value for use with the database.+{- | Encodes a 'Double' value for use with the database.  @since 1.0.0.0 -}@@ -344,8 +336,7 @@ fromDouble =   fromBSBuilderWithNoNULs BSB.doubleDec -{- |-  Attempts to decode a 'SqlValue' as a Haskell 'Double' value. If decoding+{- | Attempts to decode a 'SqlValue' as a Haskell 'Double' value. If decoding   fails, 'Nothing' is returned.  @since 1.0.0.0@@ -354,8 +345,7 @@ toDouble =   toParsedValue (AttoB8.signed AttoB8.double) -{- |-  Encodes a 'Bool' value for use with the database.+{- | Encodes a 'Bool' value for use with the database.  @since 1.0.0.0 -}@@ -366,8 +356,7 @@       True -> BSB.char8 't'       False -> BSB.char8 'f' -{- |-  Attempts to decode a 'SqlValue' as a Haskell 'Bool' value. If decoding fails,+{- | Attempts to decode a 'SqlValue' as a Haskell 'Bool' value. If decoding fails,   'Nothing' is returned.  @since 1.0.0.0@@ -381,8 +370,7 @@       'f' -> pure False       _ -> fail "Invalid boolean character value" -{- |-  Encodes a 'T.Text' value as UTF-8 so that it can be used with the database.+{- | Encodes a 'T.Text' value as UTF-8 so that it can be used with the database.  @since 1.0.0.0 -}@@ -390,8 +378,7 @@ fromText =   SqlValue . PgTextFormatValue.fromByteString . TextEnc.encodeUtf8 -{- |-  Attempts to decode a 'SqlValue' as UTF-8 text. If the decoding fails,+{- | Attempts to decode a 'SqlValue' as UTF-8 text. If the decoding fails,   'Nothing' is returned.    Note: This decoding _only_ fails if the bytes returned from the database@@ -406,8 +393,7 @@       Right t -> Right t       Left err -> Left $ Exc.displayException err -{- |-  Encodes a 'Time.Day' value as text in YYYY-MM-DD format so that it can be+{- | Encodes a 'Time.Day' value as text in YYYY-MM-DD format so that it can be   used with the database.  @since 1.0.0.0@@ -416,8 +402,7 @@ fromDay =   SqlValue . PgTextFormatValue.unsafeFromByteString . PgTime.dayToPostgreSQL -{- |-  Attempts to decode a 'SqlValue' as into a 'Time.Day' value by parsing it+{- | Attempts to decode a 'SqlValue' as into a 'Time.Day' value by parsing it   from YYYY-MM-DD format. If the decoding fails, 'Nothing' is returned.  @since 1.0.0.0@@ -426,8 +411,7 @@ toDay =   toParsedValue PgTime.day -{- |-  Encodes a 'Time.UTCTime' in ISO-8601 format for use with the database.+{- | Encodes a 'Time.UTCTime' in ISO-8601 format for use with the database.  @since 1.0.0.0 -}@@ -437,8 +421,7 @@     . PgTextFormatValue.unsafeFromByteString     . PgTime.utcTimeToPostgreSQL -{- |-  Encodes a 'Time.LocalTime' in ISO-8601 format for use with the database.+{- | Encodes a 'Time.LocalTime' in ISO-8601 format for use with the database.  @since 1.0.0.0 -}@@ -448,8 +431,7 @@     . PgTextFormatValue.unsafeFromByteString     . PgTime.localTimeToPostgreSQL -{- |-  Attempts to decode a 'SqlValue' as a 'Time.LocalTime' formatted in ISO-8601+{- | Attempts to decode a 'SqlValue' as a 'Time.LocalTime' formatted in ISO-8601   format in the default locale. If the decoding fails, 'Nothing' is returned.  @since 1.0.0.0@@ -458,8 +440,7 @@ toLocalTime =   toParsedValue PgTime.localTime -{- |-  Attempts to decode a 'SqlValue' as a 'Time.UTCTime' formatted in ISO-8601+{- | Attempts to decode a 'SqlValue' as a 'Time.UTCTime' formatted in ISO-8601   format with time zone. If the decoding fails, 'Nothing' is returned.  @since 1.0.0.0@@ -468,8 +449,15 @@ toUTCTime =   toParsedValue PgTime.utcTime -{- |-  An internal helper function that constructs a 'SqlValue' via a bytestring+{- | Construct a 'SqlValue' representing a Postgres row value comprising the values in the list.++@since 1.1.0.0+-}+fromRow :: NE.NonEmpty SqlValue -> SqlValue+fromRow =+  SqlRowValue++{- | An internal helper function that constructs a 'SqlValue' via a bytestring   'BS8.Builder'.  @since 1.0.0.0@@ -482,8 +470,7 @@     . BSB.toLazyByteString     . builder -{- |-  An internal helper function that parses 'SqlValue' via an Attoparsec parser.+{- | An internal helper function that parses 'SqlValue' via an Attoparsec parser.  @since 1.0.0.0 -}@@ -495,8 +482,7 @@ toParsedValue parser =   toBytesValue (AttoBS.parseOnly parser) -{- |-  An internal helper function that parses the bytes from a 'SqlValue' with the+{- | An internal helper function that parses the bytes from a 'SqlValue' with the   given parsing function. If the 'SqlValue' is NULL, this function returns an   error an a 'Left' value. If the parsing function fails (by returning 'Left'),   the error it returns in returned by this function.@@ -514,6 +500,8 @@       case sqlValue of         SqlNull ->           Left "Unexpected SQL NULL value"+        SqlRowValue _ ->+          Left "Unexpected SQL row value"         SqlValue bytes ->           byteParser (PgTextFormatValue.toByteString bytes) 
src/Orville/PostgreSQL/Schema.hs view
@@ -1,7 +1,7 @@ {-# OPTIONS_GHC -Wno-missing-import-lists #-}  {- |-Copyright : Flipstone Technology Partners 2023+Copyright : Flipstone Technology Partners 2023-2024 License   : MIT Stability : Stable @@ -19,10 +19,18 @@   , module Orville.PostgreSQL.Schema.PrimaryKey   , module Orville.PostgreSQL.Schema.IndexDefinition   , module Orville.PostgreSQL.Schema.ConstraintDefinition+  , module Orville.PostgreSQL.Schema.TriggerDefinition      -- * Defining Sequences   , module Orville.PostgreSQL.Schema.SequenceDefinition   , module Orville.PostgreSQL.Schema.SequenceIdentifier++    -- * Definining Functions+  , module Orville.PostgreSQL.Schema.FunctionDefinition+  , module Orville.PostgreSQL.Schema.FunctionIdentifier++    -- * Using PostgreSQL Extensions+  , module Orville.PostgreSQL.Schema.ExtensionIdentifier   ) where @@ -30,9 +38,13 @@ -- appear in the generated haddock documentation.  import Orville.PostgreSQL.Schema.ConstraintDefinition+import Orville.PostgreSQL.Schema.ExtensionIdentifier+import Orville.PostgreSQL.Schema.FunctionDefinition+import Orville.PostgreSQL.Schema.FunctionIdentifier import Orville.PostgreSQL.Schema.IndexDefinition import Orville.PostgreSQL.Schema.PrimaryKey import Orville.PostgreSQL.Schema.SequenceDefinition import Orville.PostgreSQL.Schema.SequenceIdentifier import Orville.PostgreSQL.Schema.TableDefinition import Orville.PostgreSQL.Schema.TableIdentifier+import Orville.PostgreSQL.Schema.TriggerDefinition
src/Orville/PostgreSQL/Schema/ConstraintDefinition.hs view
@@ -38,8 +38,7 @@ import qualified Orville.PostgreSQL.Internal.FieldName as FieldName import qualified Orville.PostgreSQL.Schema.TableIdentifier as TableIdentifier -{- |-  A collection of constraints to be added to a table. This collection is+{- | A collection of constraints to be added to a table. This collection is   indexed by 'ConstraintMigrationKey'. If multiple constraints with the same   'ConstraintMigrationKey' are added, the most recently-added one will be kept   and the previous one dropped.@@ -48,18 +47,21 @@ -} newtype TableConstraints   = TableConstraints (Map.Map ConstraintMigrationKey ConstraintDefinition)-  deriving (Semigroup, Monoid)+  deriving+    ( -- | @since 1.0.0.0+      Semigroup+    , -- | @since 1.0.0.0+      Monoid+    ) -{- |-  Constructs an empty 'TableConstraints'.+{- | Constructs an empty 'TableConstraints'.  @since 1.0.0.0 -} emptyTableConstraints :: TableConstraints emptyTableConstraints = TableConstraints Map.empty -{- |-  Adds a 'ConstraintDefinition' to an existing 'TableConstraints'. If a+{- | Adds a 'ConstraintDefinition' to an existing 'TableConstraints'. If a   constraint already exists with the same 'ConstraintMigrationKey', it is   replaced with the new constraint. @@ -73,8 +75,7 @@       constraint       constraintMap -{- |-  Gets the list of 'ConstraintDefinition's that have been added to the+{- | Gets the list of 'ConstraintDefinition's that have been added to the   'TableConstraints'.  @since 1.0.0.0@@ -83,8 +84,7 @@ tableConstraintKeys (TableConstraints constraints) =   Map.keysSet constraints -{- |-  Gets the list of 'ConstraintDefinition's that have been added to the+{- | Gets the list of 'ConstraintDefinition's that have been added to the   'TableConstraints'.  @since 1.0.0.0@@ -93,8 +93,7 @@ tableConstraintDefinitions (TableConstraints constraints) =   Map.elems constraints -{- |-  Defines a constraint that can be added to a+{- | Defines a constraint that can be added to a   'Orville.PostgreSQL.TableDefinition'. Use one of the constructor functions   below (such as 'uniqueConstraint') to construct the constraint definition you   wish to have and then use 'Orville.PostgreSQL.addTableConstraints' to add@@ -104,12 +103,11 @@ @since 1.0.0.0 -} data ConstraintDefinition = ConstraintDefinition-  { _constraintSqlExpr :: Expr.TableConstraint-  , _constraintMigrationKey :: ConstraintMigrationKey+  { i_constraintSqlExpr :: Expr.TableConstraint+  , i_constraintMigrationKey :: ConstraintMigrationKey   } -{- |-  The key used by Orville to determine whether a constraint should be added to+{- | The key used by Orville to determine whether a constraint should be added to   a table when performing auto-migrations. For most use cases, the constructor   functions that build a 'ConstraintDefinition' will create this automatically   for you.@@ -124,10 +122,16 @@   , constraintKeyForeignKeyOnUpdateAction :: Maybe ForeignKeyAction   , constraintKeyForeignKeyOnDeleteAction :: Maybe ForeignKeyAction   }-  deriving (Eq, Ord, Show)+  deriving+    ( -- | @since 1.0.0.0+      Eq+    , -- | @since 1.0.0.0+      Ord+    , -- | @since 1.0.0.0+      Show+    ) -{- |-  The kind of constraint that is described by a 'ConstraintMigrationKey' (e.g.+{- | The kind of constraint that is described by a 'ConstraintMigrationKey' (e.g.   unique, foreign key).  @since 1.0.0.0@@ -135,26 +139,30 @@ data ConstraintKeyType   = UniqueConstraint   | ForeignKeyConstraint-  deriving (Eq, Ord, Show)+  deriving+    ( -- | @since 1.0.0.0+      Eq+    , -- | @since 1.0.0.0+      Ord+    , -- | @since 1.0.0.0+      Show+    ) -{- |-  Gets the 'ConstraintMigrationKey' for the 'ConstraintDefinition'.+{- | Gets the 'ConstraintMigrationKey' for the 'ConstraintDefinition'.  @since 1.0.0.0 -} constraintMigrationKey :: ConstraintDefinition -> ConstraintMigrationKey-constraintMigrationKey = _constraintMigrationKey+constraintMigrationKey = i_constraintMigrationKey -{- |-  Gets the SQL expression that will be used to add the constraint to the table.+{- | Gets the SQL expression that will be used to add the constraint to the table.  @since 1.0.0.0 -} constraintSqlExpr :: ConstraintDefinition -> Expr.TableConstraint-constraintSqlExpr = _constraintSqlExpr+constraintSqlExpr = i_constraintSqlExpr -{- |-  Constructs a 'ConstraintDefinition' for a @UNIQUE@ constraint on the given+{- | Constructs a 'ConstraintDefinition' for a @UNIQUE@ constraint on the given   columns.  @since 1.0.0.0@@ -176,12 +184,11 @@         }   in     ConstraintDefinition-      { _constraintSqlExpr = expr-      , _constraintMigrationKey = migrationKey+      { i_constraintSqlExpr = expr+      , i_constraintMigrationKey = migrationKey       } -{- |-  A 'ForeignReference' represents one part of a foreign key. The entire foreign+{- | A 'ForeignReference' represents one part of a foreign key. The entire foreign   key may comprise multiple columns. The 'ForeignReference' defines a single   column in the key and which column it references in the foreign table. @@ -192,8 +199,7 @@   , foreignFieldName :: FieldName.FieldName   } -{- |-  Constructs a 'ForeignReference'.+{- | Constructs a 'ForeignReference'.  @since 1.0.0.0 -}@@ -209,8 +215,7 @@     , foreignFieldName = foreignName     } -{- |-  Defines the options for a foreign key constraint. To construct+{- | Defines the options for a foreign key constraint. To construct   'ForeignKeyOptions', perform a record update on 'defaultForeignKeyOptions'.  @since 1.0.0.0@@ -222,8 +227,7 @@   -- ^ The @ON DELETE@ action for the foreign key.   } -{- |-  The default 'ForeignKeyOptions', containing 'NoAction' for both+{- | The default 'ForeignKeyOptions', containing 'NoAction' for both   'foreignKeyOptionsOnUpdate' and 'foreignKeyOptionsOnDelete'.  @since 1.0.0.0@@ -235,8 +239,7 @@     , foreignKeyOptionsOnDelete = NoAction     } -{- |-  The actions that can be set on 'ForeignKeyOptions'.+{- | The actions that can be set on 'ForeignKeyOptions'.  @since 1.0.0.0 -}@@ -246,7 +249,14 @@   | Cascade   | SetNull   | SetDefault-  deriving (Show, Eq, Ord)+  deriving+    ( -- | @since 1.0.0.0+      Show+    , -- | @since 1.0.0.0+      Eq+    , -- | @since 1.0.0.0+      Ord+    )  foreignKeyActionToExpr :: ForeignKeyAction -> Maybe Expr.ForeignKeyActionExpr foreignKeyActionToExpr action = case action of@@ -256,8 +266,7 @@   SetNull -> Just Expr.setNullExpr   SetDefault -> Just Expr.setDefaultExpr -{- |-  Builds a 'ConstraintDefinition' for a @FOREIGN KEY@ constraint.+{- | Builds a 'ConstraintDefinition' for a @FOREIGN KEY@ constraint.  @since 1.0.0.0 -}@@ -270,8 +279,7 @@ foreignKeyConstraint foreignTableId foreignReferences =   foreignKeyConstraintWithOptions foreignTableId foreignReferences defaultForeignKeyOptions -{- |-  Builds a 'ConstraintDefinition' for a @FOREIGN KEY@ constraint, with+{- | Builds a 'ConstraintDefinition' for a @FOREIGN KEY@ constraint, with   ON UPDATE and ON DELETE actions.  @since 1.0.0.0@@ -318,6 +326,6 @@         }   in     ConstraintDefinition-      { _constraintSqlExpr = expr-      , _constraintMigrationKey = migrationKey+      { i_constraintSqlExpr = expr+      , i_constraintMigrationKey = migrationKey       }
+ src/Orville/PostgreSQL/Schema/ExtensionIdentifier.hs view
@@ -0,0 +1,61 @@+{- |+Copyright : Flipstone Technology Partners 2024+License   : MIT+Stability : Stable++@since 1.1.0.0+-}+module Orville.PostgreSQL.Schema.ExtensionIdentifier+  ( ExtensionIdentifier+  , nameToExtensionId+  , extensionIdName+  , extensionIdToString+  )+where++import qualified Orville.PostgreSQL.Expr as Expr++{- | An identifier used by Orville to identify a particular extension++@since 1.1.0.0+-}+newtype ExtensionIdentifier = ExtensionIdentifier+  { i_extensionName :: String+  }+  deriving+    ( -- | @since 1.1.0.0+      Eq+    , -- | @since 1.1.0.0+      Ord+    , -- | @since 1.1.0.0+      Show+    )++{- | Constructs a 'ExtensionIdentifier' with the given extension name string.++@since 1.1.0.0+-}+nameToExtensionId :: String -> ExtensionIdentifier+nameToExtensionId name =+  ExtensionIdentifier+    { i_extensionName = name+    }++{- | Returns the 'Expr.ExtensionName' that should be used to refer to the extension in SQL queries.++@since 1.1.0.0+-}+extensionIdName :: ExtensionIdentifier -> Expr.ExtensionName+extensionIdName =+  Expr.extensionName . extensionIdToString++{- | Converts a 'ExtensionIdentifier' to a 'String' for descriptive purposes. The+  name will be qualified if a schema name has been set for the identifier.++  Note: You should not use this function for building SQL expressions. Use+  'extensionIdName' instead for that.++@since 1.1.0.0+-}+extensionIdToString :: ExtensionIdentifier -> String+extensionIdToString = i_extensionName
+ src/Orville/PostgreSQL/Schema/FunctionDefinition.hs view
@@ -0,0 +1,115 @@+{- |+Copyright : Flipstone Technology Partners 2024+License   : MIT+Stability : Stable++@since 1.1.0.0+-}+module Orville.PostgreSQL.Schema.FunctionDefinition+  ( FunctionDefinition+  , setFunctionSchema+  , functionIdentifier+  , functionName+  , functionSource+  , mkTriggerFunction+  , mkCreateFunctionExpr+  ) where++import qualified Orville.PostgreSQL.Expr as Expr+import Orville.PostgreSQL.Schema.FunctionIdentifier (FunctionIdentifier, functionIdQualifiedName, setFunctionIdSchema, unqualifiedNameToFunctionId)++{- | Contains the definition of a PostgreSQL function for Orville to use when creating+  the function. Currently only functionality for creating trigger functions for use+  with 'Orville.PostgreSQL.Schema.TriggerDefinition' is supported. You can create a+  'FunctionDefintion' for a trigger via 'mkTriggerFunction'.++  When a 'FunctionDefinition' is included in a schema item list for auto-migration+  the function will be created if it does not exist, or recreated if the source+  code for the function does not match what is found in the database catalog.++@since 1.1.0.0+-}+data FunctionDefinition = FunctionDefinition+  { i_functionIdentifier :: FunctionIdentifier+  , i_functionReturnType :: Expr.ReturnType+  , i_functionLanguage :: Expr.LanguageName+  , i_functionSource :: String+  }++{- | Sets the function's schema to the name in the given 'String', which will be+  treated as a SQL identifier. If a function has a schema name set, it will be+  included as a qualifier on the function name for all queries involving the+  function.++@since 1.1.0.0+-}+setFunctionSchema ::+  String ->+  FunctionDefinition ->+  FunctionDefinition+setFunctionSchema schemaName functionDef =+  functionDef+    { i_functionIdentifier = setFunctionIdSchema schemaName (i_functionIdentifier functionDef)+    }++{- | Retrieves the 'FunctionIdentifier' for this function, which is set by the+  name provided to 'mkTriggerFunction' and any calls made to+  'setFunctionSchema' thereafter.+@since 1.1.0.0+-}+functionIdentifier :: FunctionDefinition -> FunctionIdentifier+functionIdentifier =+  i_functionIdentifier++{- | Retrieves the 'Expr.Qualified' 'Expr.FunctionName' for the function that+  should be used to build SQL expressions involving it.++@since 1.1.0.0+-}+functionName :: FunctionDefinition -> Expr.QualifiedOrUnqualified Expr.FunctionName+functionName =+  functionIdQualifiedName . i_functionIdentifier++{- | Retrieves the source that was passed to 'mkTriggerFunction' when the+  'FunctionDefinition' was created.++@since 1.1.0.0+-}+functionSource :: FunctionDefinition -> String+functionSource =+  i_functionSource++{- | Constructs a 'FunctionDefinition' that will create a PostgreSQL function+  with a return type of @trigger@ using the specified lanugage and function+  body.+@since 1.1.0.0+-}+mkTriggerFunction ::+  String ->+  Expr.LanguageName ->+  String ->+  FunctionDefinition+mkTriggerFunction name language source =+  FunctionDefinition+    { i_functionIdentifier = unqualifiedNameToFunctionId name+    , i_functionReturnType = Expr.returnTypeTrigger+    , i_functionLanguage = language+    , i_functionSource = source+    }++{- | Builds a 'Expr.CreateFunctionExpr' that will create a SQL function matching+  the given 'FunctionDefinition' when it is executed.++@since 1.1.0.0+-}+mkCreateFunctionExpr ::+  FunctionDefinition ->+  Maybe Expr.OrReplace ->+  Expr.CreateFunctionExpr+mkCreateFunctionExpr functionDef maybeOrReplace =+  Expr.createFunction+    maybeOrReplace+    (functionName functionDef)+    (Expr.returns (i_functionReturnType functionDef))+    (Expr.language (i_functionLanguage functionDef))+    (Expr.asDefinition (i_functionSource functionDef))
+ src/Orville/PostgreSQL/Schema/FunctionIdentifier.hs view
@@ -0,0 +1,128 @@+{- |+Copyright : Flipstone Technology Partners 2024+License   : MIT+Stability : Stable++@since 1.1.0.0+-}+module Orville.PostgreSQL.Schema.FunctionIdentifier+  ( FunctionIdentifier+  , unqualifiedNameToFunctionId+  , setFunctionIdSchema+  , functionIdQualifiedName+  , functionIdUnqualifiedName+  , functionIdSchemaName+  , functionIdToString+  , functionIdUnqualifiedNameString+  , functionIdSchemaNameString+  )+where++import qualified Orville.PostgreSQL.Expr as Expr++{- | An identifier used by Orville to identify a particular function in a particular+  schema.++@since 1.1.0.0+-}+data FunctionIdentifier = FunctionIdentifier+  { i_functionIdName :: String+  , i_functionIdSchema :: Maybe String+  }+  deriving+    ( -- | @since 1.1.0.0+      Eq+    , -- | @since 1.1.0.0+      Ord+    , -- | @since 1.1.0.0+      Show+    )++{- | Constructs a 'FunctionIdentifier' where the function's name will not be qualified+  by a particular schema.++@since 1.1.0.0+-}+unqualifiedNameToFunctionId :: String -> FunctionIdentifier+unqualifiedNameToFunctionId name =+  FunctionIdentifier+    { i_functionIdName = name+    , i_functionIdSchema = Nothing+    }++{- | Sets the schema of the 'FunctionIdentifier'. Wherever applicable, references to+  the function will be qualified by the given schema name.++@since 1.1.0.0+-}+setFunctionIdSchema :: String -> FunctionIdentifier -> FunctionIdentifier+setFunctionIdSchema schema functionId =+  functionId+    { i_functionIdSchema = Just schema+    }++{- | Returns the 'Expr.QualifiedOrUnqualified Expr.FunctionName' that should be used to refer to the+  function in SQL queries where a potentially qualified reference is appropriate.++@since 1.1.0.0+-}+functionIdQualifiedName :: FunctionIdentifier -> Expr.QualifiedOrUnqualified Expr.FunctionName+functionIdQualifiedName functionId =+  case functionIdSchemaName functionId of+    Nothing ->+      Expr.unqualified (functionIdUnqualifiedName functionId)+    Just schemaName ->+      Expr.untrackQualified $+        Expr.qualifyFunction+          schemaName+          (functionIdUnqualifiedName functionId)++{- | Returns the unqualified 'Expr.FunctionName' that should be used to refer to the+  function in SQL queries where an unqualified reference is appropriate.++@since 1.1.0.0+-}+functionIdUnqualifiedName :: FunctionIdentifier -> Expr.FunctionName+functionIdUnqualifiedName =+  Expr.functionName . i_functionIdName++{- | Returns the 'Expr.SchemaName' (if any) that should be used to qualify+  references to the function in SQL queries.++@since 1.1.0.0+-}+functionIdSchemaName :: FunctionIdentifier -> Maybe Expr.SchemaName+functionIdSchemaName =+  fmap Expr.schemaName . i_functionIdSchema++{- | Retrieves the unqualified name of the function as a 'String'.++@since 1.1.0.0+-}+functionIdUnqualifiedNameString :: FunctionIdentifier -> String+functionIdUnqualifiedNameString =+  i_functionIdName++{- | Retrieves the schema name of the function as a 'String'.++@since 1.1.0.0+-}+functionIdSchemaNameString :: FunctionIdentifier -> Maybe String+functionIdSchemaNameString =+  i_functionIdSchema++{- | Converts a 'FunctionIdentifier' to a 'String' for descriptive purposes. The+  name will be qualified if a schema name has been set for the identifier.++  Note: You should not use this function for building SQL expressions. Use+  'functionIdQualifiedName' instead for that.++@since 1.1.0.0+-}+functionIdToString :: FunctionIdentifier -> String+functionIdToString functionId =+  case i_functionIdSchema functionId of+    Nothing ->+      i_functionIdName functionId+    Just schema ->+      schema <> "." <> i_functionIdName functionId
src/Orville/PostgreSQL/Schema/IndexDefinition.hs view
@@ -18,6 +18,10 @@   , Expr.IndexUniqueness (UniqueIndex, NonUniqueIndex)   , IndexDefinition.indexCreateExpr   , IndexDefinition.IndexCreationStrategy (Transactional, Concurrent)+  , IndexDefinition.IndexMigrationKey (AttributeBasedIndexKey, NamedIndexKey)+  , IndexDefinition.AttributeBasedIndexMigrationKey (AttributeBasedIndexMigrationKey, indexKeyUniqueness, indexKeyColumns)+  , IndexDefinition.NamedIndexMigrationKey+  , IndexDefinition.indexMigrationKey   ) where 
src/Orville/PostgreSQL/Schema/PrimaryKey.hs view
@@ -2,7 +2,7 @@ {-# LANGUAGE RankNTypes #-}  {- |-Copyright : Flipstone Technology Partners 2023+Copyright : Flipstone Technology Partners 2023-2024 License   : MIT Stability : Stable @@ -29,11 +29,10 @@  import qualified Orville.PostgreSQL.Expr as Expr import qualified Orville.PostgreSQL.Internal.Extra.NonEmpty as ExtraNonEmpty-import Orville.PostgreSQL.Marshall.FieldDefinition (FieldDefinition, FieldName, NotNull, fieldColumnName, fieldEquals, fieldIn, fieldName, fieldNameToString, fieldValueToSqlValue)+import Orville.PostgreSQL.Marshall.FieldDefinition (FieldDefinition, FieldName, NotNull, fieldEquals, fieldIn, fieldName, fieldNameToColumnName, fieldNameToString, fieldValueToSqlValue) import qualified Orville.PostgreSQL.Raw.SqlValue as SqlValue -{- |-  A Haskell description of the 'FieldDefinition's that make up the primary+{- | A Haskell description of the 'FieldDefinition's that make up the primary   key of a SQL table. This type supports composite primary keys as well   as singular ones. @@ -42,8 +41,7 @@ data PrimaryKey key   = PrimaryKey (PrimaryKeyPart key) [PrimaryKeyPart key] -{- |-  A 'PrimaryKeyPart' describes one field of a composite primary key. Values+{- | A 'PrimaryKeyPart' describes one field of a composite primary key. Values   are built using 'primaryKeyPart' and then used with 'compositePrimaryKey'   to build a 'PrimaryKey'. @@ -52,8 +50,7 @@ data PrimaryKeyPart key   = forall part. PrimaryKeyPart (key -> part) (FieldDefinition NotNull part) -{- |-  'primaryKeyDescription' builds a user-readable representation of the+{- | 'primaryKeyDescription' builds a user-readable representation of the   primary key for use in error messages and such. It is a comma-delimited   list of the names of the fields that make up the primary key. @@ -66,8 +63,7 @@     . toList     . primaryKeyFieldNames -{- |-  Retrieves the names of the fields that are part of the primary key.+{- | Retrieves the names of the fields that are part of the primary key.  @since 1.0.0.0 -}@@ -75,13 +71,12 @@ primaryKeyFieldNames =   let     partName :: (part -> key) -> FieldDefinition NotNull a -> FieldName-    partName _ field =-      fieldName field+    partName _ =+      fieldName   in     mapPrimaryKeyParts partName -{- |-  'primaryKeyToSql' converts a Haskell value for a primary key into the+{- | 'primaryKeyToSql' converts a Haskell value for a primary key into the   (possibly multiple) SQL values that represent the primary key in the   database. @@ -91,8 +86,7 @@ primaryKeyToSql keyDef key =   mapPrimaryKeyParts (partSqlValue key) keyDef -{- |-  'partSqlValue' is an internal helper function that builds the+{- | 'partSqlValue' is an internal helper function that builds the   'SqlValue.SqlValue' for one part of a (possible composite) primary key.  @since 1.0.0.0@@ -101,8 +95,7 @@ partSqlValue key getPart partField =   fieldValueToSqlValue partField (getPart key) -{- |-  'primaryKey' constructs a single-field primary key from the 'FieldDefinition'+{- | 'primaryKey' constructs a single-field primary key from the 'FieldDefinition'   that corresponds to the primary key's column. This is generally used while   building a 'Orville.PostgreSQL.TableDefinition'. @@ -112,8 +105,7 @@ primaryKey fieldDef =   PrimaryKey (PrimaryKeyPart id fieldDef) [] -{- |-  'compositePrimaryKey' constructs a multi-field primary key from the given+{- | 'compositePrimaryKey' constructs a multi-field primary key from the given   parts, each of which corresponds to one field in the primary key. You should   use this while building a 'Orville.PostgreSQL.TableDefinition' for a table   that you want to have a multi-column primary key. See 'primaryKeyPart' for@@ -130,8 +122,7 @@ compositePrimaryKey =   PrimaryKey -{- |-  'primaryKeyPart' constructs a building block for a composite primary key+{- | 'primaryKeyPart' constructs a building block for a composite primary key   based on a 'FieldDefinition' and an accessor function to extract the value for   that field from the Haskell @key@ type that represents the overall composite   key. 'PrimaryKeyPart' values built using this function are usually then@@ -146,8 +137,7 @@ primaryKeyPart =   PrimaryKeyPart -{- |-  'mapPrimaryKeyParts' provides a way to access the innards of a 'PrimaryKey'+{- | 'mapPrimaryKeyParts' provides a way to access the innards of a 'PrimaryKey'   definition to extract information. The given function will be called on   each part of the primary key in order and the list of results is returned.   Note that single-field and multi-field primary keys are treated the same by@@ -171,8 +161,7 @@   in     fmap doPart (first :| rest) -{- |-  Builds a 'Expr.PrimaryKeyExpr' that is suitable to be used when creating+{- | Builds a 'Expr.PrimaryKeyExpr' that is suitable to be used when creating   a table to define the primary key on the table.  @since 1.0.0.0@@ -181,12 +170,11 @@ mkPrimaryKeyExpr keyDef =   let     names =-      mapPrimaryKeyParts (\_ field -> fieldColumnName field) keyDef+      mapPrimaryKeyParts (\_ field -> fieldNameToColumnName $ fieldName field) keyDef   in     Expr.primaryKeyExpr names -{- |-  'primaryKeyEquals' builds a 'Expr.BooleanExpr' that will match the row where+{- | 'primaryKeyEquals' builds a 'Expr.BooleanExpr' that will match the row where   the primary key is equal to the given value. For single-field primary keys,   this is equivalent to 'fieldEquals', but 'primaryKeyEquals' also handles   composite primary keys.@@ -199,8 +187,7 @@     Expr.andExpr     (mapPrimaryKeyParts (partEquals key) keyDef) -{- |-  'primaryKeyIn' builds a 'Expr.BooleanExpr' that will match rows where the+{- | 'primaryKeyIn' builds a 'Expr.BooleanExpr' that will match rows where the   primary key is contained in the given list. For single-field primary keys,   this is equivalent to 'fieldIn', but 'primaryKeyIn' also handles composite   primary keys.@@ -217,8 +204,7 @@         Expr.orExpr         (fmap (primaryKeyEquals keyDef) keys) -{- |-  INTERNAL: builds the where condition for a single part of the key+{- | INTERNAL: builds the where condition for a single part of the key  @since 1.0.0.0 -}
src/Orville/PostgreSQL/Schema/SequenceDefinition.hs view
@@ -32,8 +32,7 @@ import qualified Orville.PostgreSQL.Expr as Expr import Orville.PostgreSQL.Schema.SequenceIdentifier (SequenceIdentifier, sequenceIdQualifiedName, setSequenceIdSchema, unqualifiedNameToSequenceId) -{- |-  Contains the definition of a SQL sequence for Orville to use when creating+{- | Contains the definition of a SQL sequence for Orville to use when creating   the sequence and fetching values from it. You can create a   'SequenceDefinition' with default values via 'mkSequenceDefinition' and then   use the various set functions that are provided if you need to set specific@@ -50,10 +49,14 @@   , i_sequenceCache :: Int64   , i_sequenceCycle :: Bool   }-  deriving (Eq, Show)+  deriving+    ( -- | @since 1.0.0.0+      Eq+    , -- | @since 1.0.0.0+      Show+    ) -{- |-  Constructs an ascending 'SequenceDefinition' with increment 1 and cache+{- | Constructs an ascending 'SequenceDefinition' with increment 1 and cache   1 that does not cycle. The sequence will start at 1 and count to the   largest 'Int64' value. @@ -71,8 +74,7 @@     , i_sequenceCycle = False     } -{- |-  Sets the sequence's schema to the name in the given 'String', which will be+{- | Sets the sequence's schema to the name in the given 'String', which will be   treated as a SQL identifier. If a sequence has a schema name set, it will be   included as a qualifier on the sequence name for all queries involving the   sequence.@@ -88,8 +90,7 @@     { i_sequenceIdentifier = setSequenceIdSchema schemaName (i_sequenceIdentifier sequenceDef)     } -{- |-  Retrieves the 'SequenceIdentifier' for this sequence, which is set by the+{- | Retrieves the 'SequenceIdentifier' for this sequence, which is set by the   name provided to 'mkSequenceDefinition' and any calls made to   'setSequenceSchema' thereafter. @@ -98,26 +99,23 @@ sequenceIdentifier :: SequenceDefinition -> SequenceIdentifier sequenceIdentifier = i_sequenceIdentifier -{- |-  Retrieves the 'Expr.Qualified' 'Expr.SequenceName' for the sequence that+{- | Retrieves the 'Expr.Qualified' 'Expr.SequenceName' for the sequence that   should be used to build SQL expressions involving it.  @since 1.0.0.0 -}-sequenceName :: SequenceDefinition -> Expr.Qualified Expr.SequenceName+sequenceName :: SequenceDefinition -> Expr.QualifiedOrUnqualified Expr.SequenceName sequenceName =   sequenceIdQualifiedName . i_sequenceIdentifier -{- |-  Retrieves the increment value for the sequence.+{- | Retrieves the increment value for the sequence.  @since 1.0.0.0 -} sequenceIncrement :: SequenceDefinition -> Int64 sequenceIncrement = i_sequenceIncrement -{- |-  Sets the increment value for the sequence. The increment cannot be set to+{- | Sets the increment value for the sequence. The increment cannot be set to   @0@ (PostgreSQL will raise an error when trying to create or modify the   sequence in this case). @@ -130,8 +128,7 @@ setSequenceIncrement n sequenceDef =   sequenceDef {i_sequenceIncrement = n} -{- |-  Retrieves the min value of the sequence. If no explicit minimum has been set,+{- | Retrieves the min value of the sequence. If no explicit minimum has been set,   this returns @1@ for ascending sequences and 'minBound' for 'Int64' for   descending sequences. @@ -146,8 +143,7 @@         then 1         else minBound -{- |-  Sets the min value for the sequence.+{- | Sets the min value for the sequence.  @since 1.0.0.0 -}@@ -155,8 +151,7 @@ setSequenceMinValue n sequenceDef =   sequenceDef {i_sequenceMinValue = Just n} -{- |-  Retrieves the max value of the sequence. If no explicit maximum has been set,+{- | Retrieves the max value of the sequence. If no explicit maximum has been set,   this returns 'maxBound' for 'Int64' for ascending sequences and @-1@ for   descending sequences. @@ -171,8 +166,7 @@         then maxBound         else -1 -{- |-  Sets the max value for the sequence.+{- | Sets the max value for the sequence.  @since 1.0.0.0 -}@@ -180,8 +174,7 @@ setSequenceMaxValue n sequenceDef =   sequenceDef {i_sequenceMaxValue = Just n} -{- |-  Retrieves the start value for the sequence. If no explicit start value has+{- | Retrieves the start value for the sequence. If no explicit start value has   been set, this returns 'sequenceMinValue' for ascending sequences and   'sequenceMaxValue' for descending sequences. @@ -196,8 +189,7 @@         then sequenceMinValue sequenceDef         else sequenceMaxValue sequenceDef -{- |-  Sets the sequence start value. The start value must be at least the+{- | Sets the sequence start value. The start value must be at least the   minimum value and no greater than the maximum value.  @since 1.0.0.0@@ -206,8 +198,7 @@ setSequenceStart n sequenceDef =   sequenceDef {i_sequenceStart = Just n} -{- |-  Retrieves the number of sequence values that will be pre-allocated by+{- | Retrieves the number of sequence values that will be pre-allocated by   PostgreSQL.  @since 1.0.0.0@@ -215,8 +206,7 @@ sequenceCache :: SequenceDefinition -> Int64 sequenceCache = i_sequenceCache -{- |-  Sets the number of sequence values that will be pre-allocated by PostgreSQL.+{- | Sets the number of sequence values that will be pre-allocated by PostgreSQL.  @since 1.0.0.0 -}@@ -224,8 +214,7 @@ setSequenceCache n sequenceDef =   sequenceDef {i_sequenceCache = n} -{- |-  Indicates whether the sequence will wrap around when it reaches the maximum+{- | Indicates whether the sequence will wrap around when it reaches the maximum   value (for ascending sequences) or minimum value (for descending sequences).   When 'False', any attempts to get the next value of the sequence while at the   limit will result in an error.@@ -235,8 +224,7 @@ sequenceCycle :: SequenceDefinition -> Bool sequenceCycle = i_sequenceCycle -{- |-  Sets the 'sequenceCycle' value for the sequence. 'True' indicates that the+{- | Sets the 'sequenceCycle' value for the sequence. 'True' indicates that the   sequence will cycle. 'False' will cause an error to be raised if the next   sequence value is requested while already at the limit. @@ -246,8 +234,7 @@ setSequenceCycle b sequenceDef =   sequenceDef {i_sequenceCycle = b} -{- |-  Builds a 'Expr.CreateSequenceExpr' that will create a SQL sequence matching+{- | Builds a 'Expr.CreateSequenceExpr' that will create a SQL sequence matching   the given 'SequenceDefinition' when it is executed.  @since 1.0.0.0
src/Orville/PostgreSQL/Schema/SequenceIdentifier.hs view
@@ -20,8 +20,7 @@  import qualified Orville.PostgreSQL.Expr as Expr -{- |-  An identifier used by Orville to identify a particular sequence in a particular+{- | An identifier used by Orville to identify a particular sequence in a particular   schema.  @since 1.0.0.0@@ -30,10 +29,16 @@   { i_sequenceIdName :: String   , i_sequenceIdSchema :: Maybe String   }-  deriving (Eq, Ord, Show)+  deriving+    ( -- | @since 1.0.0.0+      Eq+    , -- | @since 1.0.0.0+      Ord+    , -- | @since 1.0.0.0+      Show+    ) -{- |-  Constructs a 'SequenceIdentifier' where the sequence's name will not be qualified+{- | Constructs a 'SequenceIdentifier' where the sequence's name will not be qualified   by a particular schema.  @since 1.0.0.0@@ -45,8 +50,7 @@     , i_sequenceIdSchema = Nothing     } -{- |-  Sets the schema of the 'SequenceIdentifier'. Wherever applicable, references+{- | Sets the schema of the 'SequenceIdentifier'. Wherever applicable, references   to the sequence will be qualified by the given schema name.  @since 1.0.0.0@@ -57,20 +61,23 @@     { i_sequenceIdSchema = Just schema     } -{- |-  Returns the 'Expr.Qualified Expr.SequenceName' that should be used to refer to the+{- | Returns the 'Expr.Qualified Expr.SequenceName' that should be used to refer to the   sequence in SQL queries.  @since 1.0.0.0 -}-sequenceIdQualifiedName :: SequenceIdentifier -> Expr.Qualified Expr.SequenceName+sequenceIdQualifiedName :: SequenceIdentifier -> Expr.QualifiedOrUnqualified Expr.SequenceName sequenceIdQualifiedName sequenceId =-  Expr.qualifySequence-    (sequenceIdSchemaName sequenceId)-    (sequenceIdUnqualifiedName sequenceId)+  case sequenceIdSchemaName sequenceId of+    Nothing ->+      Expr.unqualified (sequenceIdUnqualifiedName sequenceId)+    Just schemaName ->+      Expr.untrackQualified $+        Expr.qualifySequence+          schemaName+          (sequenceIdUnqualifiedName sequenceId) -{- |-  Returns the unqualified 'Expr.SequenceName' that should be used to refer to the+{- | Returns the unqualified 'Expr.SequenceName' that should be used to refer to the   sequence in SQL queries where an unqualified reference is appropriate.  @since 1.0.0.0@@ -79,8 +86,7 @@ sequenceIdUnqualifiedName =   Expr.sequenceName . i_sequenceIdName -{- |-  Returns the 'Expr.SchemaName' (if any) that should be used to qualify+{- | Returns the 'Expr.SchemaName' (if any) that should be used to qualify   references to the sequence in SQL queries.  @since 1.0.0.0@@ -89,8 +95,7 @@ sequenceIdSchemaName =   fmap Expr.schemaName . i_sequenceIdSchema -{- |-  Retrieves the unqualified name of the sequence as a 'String'.+{- | Retrieves the unqualified name of the sequence as a 'String'.  @since 1.0.0.0 -}@@ -98,8 +103,7 @@ sequenceIdUnqualifiedNameString =   i_sequenceIdName -{- |-  Retrieves the schema name of the sequence as a 'String'.+{- | Retrieves the schema name of the sequence as a 'String'.  @since 1.0.0.0 -}@@ -107,8 +111,7 @@ sequenceIdSchemaNameString =   i_sequenceIdSchema -{- |-  Converts a 'SequenceIdentifier' for a 'String' for descriptive purposes. The+{- | Converts a 'SequenceIdentifier' for a 'String' for descriptive purposes. The   name will be qualified if a schema name has been set for the identifier.    Note: You should not use this function for building SQL expressions. Use
src/Orville/PostgreSQL/Schema/TableDefinition.hs view
@@ -2,7 +2,7 @@ {-# LANGUAGE RankNTypes #-}  {- |-Copyright : Flipstone Technology Partners 2023+Copyright : Flipstone Technology Partners 2023-2025 License   : MIT Stability : Stable @@ -18,11 +18,15 @@   , columnsToDrop   , tableIdentifier   , tableName+  , tableComment   , setTableSchema+  , setTableComment   , tableConstraints   , addTableConstraints   , tableIndexes   , addTableIndexes+  , tableTriggers+  , addTableTriggers   , tablePrimaryKey   , tableMarshaller   , mapTableMarshaller@@ -36,22 +40,22 @@   ) where -import Data.List.NonEmpty (NonEmpty, toList)+import qualified Data.List.NonEmpty as NE import qualified Data.Map.Strict as Map import qualified Data.Set as Set+import qualified Data.Text as T  import Orville.PostgreSQL.Execution.ReturningOption (ReturningOption (WithReturning, WithoutReturning)) import qualified Orville.PostgreSQL.Expr as Expr import Orville.PostgreSQL.Internal.IndexDefinition (IndexDefinition, IndexMigrationKey, indexMigrationKey)-import Orville.PostgreSQL.Marshall.FieldDefinition (fieldColumnDefinition, fieldColumnName, fieldValueToSqlValue)+import Orville.PostgreSQL.Marshall.FieldDefinition (fieldColumnDefinition, fieldColumnName, fieldValueToSqlValue, qualifiedFieldColumnName, qualifyField) import Orville.PostgreSQL.Marshall.SqlMarshaller (AnnotatedSqlMarshaller, MarshallerField (Natural, Synthetic), ReadOnlyColumnOption (ExcludeReadOnlyColumns, IncludeReadOnlyColumns), SqlMarshaller, annotateSqlMarshaller, annotateSqlMarshallerEmptyAnnotation, collectFromField, foldMarshallerFields, mapSqlMarshaller, marshallerDerivedColumns, marshallerTableConstraints, unannotatedSqlMarshaller)-import Orville.PostgreSQL.Raw.SqlValue (SqlValue) import Orville.PostgreSQL.Schema.ConstraintDefinition (ConstraintDefinition, TableConstraints, addConstraint, constraintSqlExpr, emptyTableConstraints, tableConstraintDefinitions) import Orville.PostgreSQL.Schema.PrimaryKey (PrimaryKey, mkPrimaryKeyExpr, primaryKeyFieldNames) import Orville.PostgreSQL.Schema.TableIdentifier (TableIdentifier, setTableIdSchema, tableIdQualifiedName, unqualifiedNameToTableId)+import Orville.PostgreSQL.Schema.TriggerDefinition (TriggerDefinition, TriggerMigrationKey, triggerMigrationKey) -{- |-  Contains the definition of a SQL table for Orville to use for generating+{- | Contains the definition of a SQL table for Orville to use for generating   queries and marshalling Haskell values to and from the database.    * @key@ is a Haskell type used to indicate whether the table has a primary@@ -73,10 +77,11 @@   , i_tableColumnsToDrop :: Set.Set String   , i_tableConstraintsFromTable :: TableConstraints   , i_tableIndexes :: Map.Map IndexMigrationKey IndexDefinition+  , i_tableTriggers :: Map.Map TriggerMigrationKey TriggerDefinition+  , i_tableComment :: Maybe T.Text   } -{- |-  'HasKey' is a type with no constructors. It is used only at the type level+{- | 'HasKey' is a type with no constructors. It is used only at the type level   as the @key@ parameter to the 'TableDefinition' type to indicate that the   table has a primary key and what the Haskell type of the primary key is. @@ -84,8 +89,7 @@ -} data HasKey key -{- |-  'NoKey' is a type with no constructors. It is used only at the type level+{- | 'NoKey' is a type with no constructors. It is used only at the type level   as the @key@ parameter to the 'TableDefinition' type to indicate that the   table does not have a primary key. @@ -93,8 +97,7 @@ -} data NoKey -{- |-  INTERNAL: Use at the value level to track whether the 'TableDefinition' has a+{- | INTERNAL: Use at the value level to track whether the 'TableDefinition' has a   primary key. The @key@ parameter matches the @key@ parameter of   'TableDefinition' @@ -104,8 +107,7 @@   TableHasKey :: PrimaryKey keyType -> TablePrimaryKey (HasKey keyType)   TableHasNoKey :: TablePrimaryKey NoKey -{- |-  Constructs a new 'TableDefinition' with the basic fields required for+{- | Constructs a new 'TableDefinition' with the basic fields required for   operation. For convenience, this function accepts a 'PrimaryKey' even though   this is not required for all Orville operations to work. If you need to   create a table without any primary key, see 'mkTableDefinitionWithoutKey'.@@ -124,14 +126,15 @@   TableDefinition     { i_tableIdentifier = unqualifiedNameToTableId name     , i_tablePrimaryKey = TableHasKey primaryKey-    , i_tableMarshaller = annotateSqlMarshaller (toList $ primaryKeyFieldNames primaryKey) marshaller+    , i_tableMarshaller = annotateSqlMarshaller (NE.toList $ primaryKeyFieldNames primaryKey) marshaller     , i_tableColumnsToDrop = Set.empty     , i_tableConstraintsFromTable = emptyTableConstraints     , i_tableIndexes = Map.empty+    , i_tableTriggers = Map.empty+    , i_tableComment = Nothing     } -{- |-  Constructs a new 'TableDefinition' with the minimal fields required for+{- | Constructs a new 'TableDefinition' with the minimal fields required for   operation. Note: tables created via this function will not have a primary   key. Certain Orville functions require a primary key. Attempting to call   functions requiring a primary key will fail to compile when using a table@@ -153,10 +156,11 @@     , i_tableColumnsToDrop = Set.empty     , i_tableConstraintsFromTable = emptyTableConstraints     , i_tableIndexes = Map.empty+    , i_tableTriggers = Map.empty+    , i_tableComment = Nothing     } -{- |-  Annotates a 'TableDefinition' with a direction to drop columns if they are+{- | Annotates a 'TableDefinition' with a direction to drop columns if they are   found in the database. Orville does not drop columns during auto-migration   unless they are explicitly requested to be dropped via 'dropColumns'. @@ -179,8 +183,7 @@     { i_tableColumnsToDrop = i_tableColumnsToDrop tableDef <> Set.fromList columns     } -{- |-  Returns the set of columns that have been marked as dropped by 'dropColumns'.+{- | Returns the set of columns that have been marked as dropped by 'dropColumns'.  @since 1.0.0.0 -}@@ -188,8 +191,7 @@ columnsToDrop =   i_tableColumnsToDrop -{- |-  Returns the table's 'TableIdentifier'.+{- | Returns the table's 'TableIdentifier'.  @since 1.0.0.0 -}@@ -197,19 +199,25 @@ tableIdentifier =   i_tableIdentifier -{- |-  Returns the table's name as an expression that can be used to build SQL+{- | Returns the table's name as an expression that can be used to build SQL   statements. If the table has a schema name set, the name will be qualified   with it.  @since 1.0.0.0 -}-tableName :: TableDefinition key writeEntity readEntity -> Expr.Qualified Expr.TableName+tableName :: TableDefinition key writeEntity readEntity -> Expr.QualifiedOrUnqualified Expr.TableName tableName =   tableIdQualifiedName . i_tableIdentifier -{- |-  Sets the table's schema to the name in the given 'String', which will be+{- | Returns 'Just' the comment of the table, or 'Nothing' if it has not been set.++@since 1.1.0.0+-}+tableComment :: TableDefinition key writeEntity readEntity -> Maybe T.Text+tableComment =+  i_tableComment++{- | Sets the table's schema to the name in the given 'String', which will be   treated as a SQL identifier. If a table has a schema name set, it will be   included as a qualifier on the table name for all queries involving the   table.@@ -225,8 +233,20 @@     { i_tableIdentifier = setTableIdSchema schemaName (i_tableIdentifier tableDef)     } -{- |-  Retrieves all the table constraints that have been added to the table either+{- | Sets the table's comment.++@since 1.1.0.0+-}+setTableComment ::+  T.Text ->+  TableDefinition key writeEntity readEntity ->+  TableDefinition key writeEntity readEntity+setTableComment comment tableDef =+  tableDef+    { i_tableComment = Just comment+    }++{- | Retrieves all the table constraints that have been added to the table either   via 'addTableConstraints' or that are found on   'Orville.PostgreSQL.FieldDefinition's included with this table's   'SqlMarshaller'.@@ -240,8 +260,7 @@   tableConstraintsFromMarshaller     <> tableConstraintsFromTable -{- |-  Retrieves all the table constraints that have been added to the table via+{- | Retrieves all the table constraints that have been added to the table via   'addTableConstraints'. This does NOT include any table constraints from the   table's 'SqlMarshaller'. @@ -253,8 +272,7 @@ tableConstraintsFromTable =   i_tableConstraintsFromTable -{- |-  Retrieves all the table constraints that were included in the table's+{- | Retrieves all the table constraints that were included in the table's   'SqlMarshaller' when it was created. This does NOT include any table   constraints added via 'addTableConstraints'. @@ -268,8 +286,7 @@     . unannotatedSqlMarshaller     . i_tableMarshaller -{- |-  Adds the given table constraints to the table definition. It's also possible+{- | Adds the given table constraints to the table definition. It's also possible   to add constraints that apply to only one column, adding them to the   'Orville.PostgreSQL.FieldDefinition's that are included in the table's   'SqlMarshaller'.@@ -297,8 +314,7 @@           constraintDefs     } -{- |-  Retrieves all the table indexes that have been added to the table via+{- | Retrieves all the table indexes that have been added to the table via   'addTableIndexes'.  @since 1.0.0.0@@ -309,8 +325,7 @@ tableIndexes =   i_tableIndexes -{- |-  Adds the given table indexes to the table definition.+{- | Adds the given table indexes to the table definition.    Note: If multiple indexes are added with the same 'IndexMigrationKey', only   the last one that is added will be part of the 'TableDefinition'. Any@@ -324,15 +339,52 @@   TableDefinition key writeEntity readEntity addTableIndexes indexDefs tableDef =   let-    addIndex index indexMap =-      Map.insert (indexMigrationKey index) index indexMap+    addIndex index =+      Map.insert (indexMigrationKey index) index   in     tableDef       { i_tableIndexes = foldr addIndex (i_tableIndexes tableDef) indexDefs       } -{- |-  Returns the primary key for the table, as defined at construction via+{- | Retrieves all the table indexes that have been added to the table via+  'addTableTriggers'.++@since 1.1.0.0+-}+tableTriggers ::+  TableDefinition key writeEntity readEntity ->+  Map.Map TriggerMigrationKey TriggerDefinition+tableTriggers =+  i_tableTriggers++{- | Adds the given table triggers to the table definition.++  Note: If multiple wriggers are added with the same 'Expr.TriggerName', only+  the last one that is added will be part of the 'TableDefinition'. Any+  previously-added constraint with the same key is replaced by the new one.++  Also Note: Orville does not currently support migrating triggers based on+  their definition structure. If a trigger with the same name already exists+  on the table at the time of migration nothing will be done to updated it.+  To cause Orville to create a new trigger matching the definition in the code,+  you need to change the name of the trigger.++@since 1.1.0.0+-}+addTableTriggers ::+  [TriggerDefinition] ->+  TableDefinition key writeEntity readEntity ->+  TableDefinition key writeEntity readEntity+addTableTriggers triggerDefs tableDef =+  let+    addTrigger trigger =+      Map.insert (triggerMigrationKey trigger) trigger+  in+    tableDef+      { i_tableTriggers = foldr addTrigger (i_tableTriggers tableDef) triggerDefs+      }++{- | Returns the primary key for the table, as defined at construction via   'mkTableDefinition'.  @since 1.0.0.0@@ -342,8 +394,7 @@   case i_tablePrimaryKey def of     TableHasKey primaryKey -> primaryKey -{- |-  Returns the marshaller for the table, as defined at construction via+{- | Returns the marshaller for the table, as defined at construction via   'mkTableDefinition'.  @since 1.0.0.0@@ -351,8 +402,7 @@ tableMarshaller :: TableDefinition key writeEntity readEntity -> AnnotatedSqlMarshaller writeEntity readEntity tableMarshaller = i_tableMarshaller -{- |-  Applies the provided function to the underlying 'SqlMarshaller' of the+{- | Applies the provided function to the underlying 'SqlMarshaller' of the   'TableDefinition'.  @since 1.0.0.0@@ -364,8 +414,7 @@ mapTableMarshaller f tableDef =   tableDef {i_tableMarshaller = mapSqlMarshaller f $ i_tableMarshaller tableDef} -{- |-  Builds a 'Expr.CreateTableExpr' that will create a SQL table matching the+{- | Builds a 'Expr.CreateTableExpr' that will create a SQL table matching the   given 'TableDefinition' when it is executed.  @since 1.0.0.0@@ -380,8 +429,7 @@     (mkTablePrimaryKeyExpr tableDef)     (map constraintSqlExpr . tableConstraintDefinitions . tableConstraints $ tableDef) -{- |-  Builds the 'Expr.ColumnDefinitions' for all the fields described by the+{- | Builds the 'Expr.ColumnDefinitions' for all the fields described by the   table definition's 'SqlMarshaller'.  @since 1.0.0.0@@ -393,10 +441,9 @@   foldMarshallerFields     (unannotatedSqlMarshaller $ tableMarshaller tableDef)     []-    (collectFromField IncludeReadOnlyColumns fieldColumnDefinition)+    (collectFromField IncludeReadOnlyColumns (const fieldColumnDefinition)) -{- |-  Builds the 'Expr.PrimaryKeyExpr' for this table, or none if this table has no+{- | Builds the 'Expr.PrimaryKeyExpr' for this table, or none if this table has no   primary key.  @since 1.0.0.0@@ -411,8 +458,7 @@     TableHasNoKey ->       Nothing -{- |-  When 'WithReturning' is given, builds a 'Expr.ReturningExpr' that will+{- | When 'WithReturning' is given, builds a 'Expr.ReturningExpr' that will   return all the columns in the given 'TableDefinition'.  @since 1.0.0.0@@ -434,8 +480,7 @@         . tableMarshaller         $ tableDef -{- |-  Builds an 'Expr.InsertExpr' that will insert the given entities into the SQL+{- | Builds an 'Expr.InsertExpr' that will insert the given entities into the SQL   table when it is executed. A @RETURNING@ clause will either be included to   return the inserted rows or not, depending on the 'ReturningOption' given. @@ -444,9 +489,10 @@ mkInsertExpr ::   ReturningOption returningClause ->   TableDefinition key writeEntity readEntity ->-  NonEmpty writeEntity ->+  Maybe Expr.OnConflictExpr ->+  NE.NonEmpty writeEntity ->   Expr.InsertExpr-mkInsertExpr returningOption tableDef entities =+mkInsertExpr returningOption tableDef maybeOnConflict entities =   let     marshaller =       unannotatedSqlMarshaller $ tableMarshaller tableDef@@ -461,10 +507,10 @@       (tableName tableDef)       (Just insertColumnList)       insertSource+      maybeOnConflict       (mkTableReturningClause returningOption tableDef) -{- |-  Builds an 'Expr.InsertColumnList' that specifies the columns for an+{- | Builds an 'Expr.InsertColumnList' that specifies the columns for an   insert statement in the order that they appear in the given 'SqlMarshaller'.    In normal circumstances you will want to build the complete insert statement@@ -477,11 +523,19 @@   SqlMarshaller writeEntity readEntity ->   Expr.InsertColumnList mkInsertColumnList marshaller =-  Expr.insertColumnList $-    foldMarshallerFields marshaller [] (collectFromField ExcludeReadOnlyColumns fieldColumnName)+  Expr.insertColumnList+    . foldMarshallerFields marshaller []+    $ collectFromField+      ExcludeReadOnlyColumns+      ( \mbQ -> case mbQ of+          Nothing -> Expr.unqualified . fieldColumnName+          Just qualifier ->+            Expr.untrackQualified+              . qualifiedFieldColumnName+              . qualifyField qualifier+      ) -{- |-  Builds an 'Expr.InsertSource' that will insert the given entities with their+{- | Builds an 'Expr.InsertSource' that will insert the given entities with their   values specified in the order that the fields appear in the given   'SqlMarshaller' (which matches the order of column names produced by   'mkInsertColumnList').@@ -490,36 +544,53 @@   via 'mkInsertExpr', but this is exported in case you are composing SQL   yourself and need the column list of an insert as a fragment. +  If the 'SqlMarshaller' does not contain any fields, the resulting 'InsertSource'+  will insert a default row using @VALUES(DEFAULT)@.+ @since 1.0.0.0 -} mkInsertSource ::   SqlMarshaller writeEntity readEntity ->-  NonEmpty writeEntity ->+  NE.NonEmpty writeEntity ->   Expr.InsertSource mkInsertSource marshaller entities =   let     encodeRow =-      foldMarshallerFields marshaller (const []) collectSqlValue+      foldMarshallerFields marshaller (const Nothing) collectSqlValue   in-    Expr.insertSqlValues $ map encodeRow (toList entities)+    Expr.valuesExprInsertSource $+      case traverse encodeRow entities of+        Nothing ->+          Expr.valuesExpr+            (Expr.valuesExprRow (pure Expr.valuesExprDefaultValue) <$ entities)+            Nothing+            Nothing+            Nothing+            Nothing+        Just valExprs ->+          Expr.valuesExprFromValueExpressions valExprs -{- |-  An internal helper function that collects the 'SqlValue' encoded value for a-  field from a Haskell entity, adding it a list of 'SqlValue's that is being-  built.+{- | An internal helper function that collects the 'SqlValue' encoded value for a field from a Haskell+  entity as a 'ValueExpression', adding it a list of 'ValueExpresion's that is being built.  @since 1.0.0.0 -} collectSqlValue ::   MarshallerField entity ->-  (entity -> [SqlValue]) ->+  (entity -> Maybe (NE.NonEmpty Expr.ValueExpression)) ->   entity ->-  [SqlValue]+  Maybe (NE.NonEmpty Expr.ValueExpression) collectSqlValue entry encodeRest entity =   case entry of-    Natural fieldDef (Just accessor) ->-      fieldValueToSqlValue fieldDef (accessor entity) : (encodeRest entity)-    Natural _ Nothing ->+    Natural _ fieldDef (Just accessor) ->+      let+        self = Expr.valueExpression $ fieldValueToSqlValue fieldDef (accessor entity)+      in+        maybe+          (Just $ pure self)+          (Just . NE.cons self)+          (encodeRest entity)+    Natural _ _ Nothing ->       encodeRest entity     Synthetic _ ->       encodeRest entity
src/Orville/PostgreSQL/Schema/TableIdentifier.hs view
@@ -20,8 +20,7 @@  import qualified Orville.PostgreSQL.Expr as Expr -{- |-  An identifier used by Orville to identify a particular table in a particular+{- | An identifier used by Orville to identify a particular table in a particular   schema.  @since 1.0.0.0@@ -30,10 +29,16 @@   { i_tableIdName :: String   , i_tableIdSchema :: Maybe String   }-  deriving (Eq, Ord, Show)+  deriving+    ( -- | @since 1.0.0.0+      Eq+    , -- | @since 1.0.0.0+      Ord+    , -- | @since 1.0.0.0+      Show+    ) -{- |-  Constructs a 'TableIdentifier' where the table's name will not be qualified+{- | Constructs a 'TableIdentifier' where the table's name will not be qualified   by a particular schema.  @since 1.0.0.0@@ -45,8 +50,7 @@     , i_tableIdSchema = Nothing     } -{- |-  Sets the schema of the 'TableIdentifier'. Wherever applicable, references to+{- | Sets the schema of the 'TableIdentifier'. Wherever applicable, references to   the table will be qualified by the given schema name.  @since 1.0.0.0@@ -57,20 +61,23 @@     { i_tableIdSchema = Just schema     } -{- |-  Returns the 'Expr.Qualified Expr.TableName' that should be used to refer to+{- | Returns the 'Expr.Qualified Expr.TableName' that should be used to refer to   the table in SQL queries.  @since 1.0.0.0 -}-tableIdQualifiedName :: TableIdentifier -> Expr.Qualified Expr.TableName+tableIdQualifiedName :: TableIdentifier -> Expr.QualifiedOrUnqualified Expr.TableName tableIdQualifiedName tableId =-  Expr.qualifyTable-    (tableIdSchemaName tableId)-    (tableIdUnqualifiedName tableId)+  case tableIdSchemaName tableId of+    Nothing ->+      Expr.unqualified (tableIdUnqualifiedName tableId)+    Just schemaName ->+      Expr.untrackQualified $+        Expr.qualifyTable+          schemaName+          (tableIdUnqualifiedName tableId) -{- |-  Returns the unqualified 'Expr.TableName' that should be used to refer to the+{- | Returns the unqualified 'Expr.TableName' that should be used to refer to the   table in SQL queries where an unqualified reference is appropriate.  @since 1.0.0.0@@ -79,8 +86,7 @@ tableIdUnqualifiedName =   Expr.tableName . i_tableIdName -{- |-  Returns the 'Expr.SchemaName' (if any) that should be used to qualify+{- | Returns the 'Expr.SchemaName' (if any) that should be used to qualify   references to the table in SQL queries.  @since 1.0.0.0@@ -89,8 +95,7 @@ tableIdSchemaName =   fmap Expr.schemaName . i_tableIdSchema -{- |-  Retrieves the unqualified name of the table as a 'String'.+{- | Retrieves the unqualified name of the table as a 'String'.  @since 1.0.0.0 -}@@ -98,8 +103,7 @@ tableIdUnqualifiedNameString =   i_tableIdName -{- |-  Retrieves the schema name of the table as a 'String'.+{- | Retrieves the schema name of the table as a 'String'.  @since 1.0.0.0 -}@@ -107,8 +111,7 @@ tableIdSchemaNameString =   i_tableIdSchema -{- |-  Converts a 'TableIdentifier' to a 'String' for descriptive purposes. The+{- | Converts a 'TableIdentifier' to a 'String' for descriptive purposes. The   name will be qualified if a schema name has been set for the identifier.    Note: You should not use this function for building SQL expressions. Use
+ src/Orville/PostgreSQL/Schema/TriggerDefinition.hs view
@@ -0,0 +1,192 @@+{- |+Copyright : Flipstone Technology Partners 2024+License   : MIT+Stability : Stable++@since 1.1.0.0+-}+module Orville.PostgreSQL.Schema.TriggerDefinition+  ( TriggerDefinition+  , beforeInsert+  , afterInsert+  , beforeUpdate+  , afterUpdate+  , beforeDelete+  , afterDelete+  , mkTriggerDefinition+  , TriggerMigrationKey (NamedTriggerKey)+  , triggerMigrationKey+  , mkCreateTriggerExpr+  ) where++import Data.List.NonEmpty (NonEmpty ((:|)))++import qualified Orville.PostgreSQL.Expr as Expr++{- | Defines a trigger that can be added to a+  'Orville.PostgreSQL.TableDefinition'. Use one of the constructor functions+  below (such as 'mkNamedTriggerDefinition') to construct the constraint+  definition you wish to have and then use 'Orville.PostgreSQL.addTableTriggers'+  to add them to your table definition. Orville will then add the trigger next+  time you run auto-migrations.++  Note that currently Orville will only add triggers when it sees that no trigger+  with the same name exists. If you want to change the definition of the trigger+  you should also change the name that you specify when making the 'TriggerDefinition'+  so that Orville will drop the old trigger and create a new trigger with the new+  name.++@since 1.1.0.0+-}+data TriggerDefinition = TriggerDefinition+  { i_triggerMigrationKey :: TriggerMigrationKey+  , i_triggerCreateExpr ::+      Maybe Expr.OrReplace ->+      Expr.QualifiedOrUnqualified Expr.TableName ->+      Expr.CreateTriggerExpr+  }++{- | Orville uses 'TriggerMigration' values while performing auto migrations to+  determine whether an trigger needs to be added or dropped. For most use cases+  the constructor functions that build an 'TriggerDefinition' will create this+  automatically for you.++@since 1.1.0.0+-}+newtype TriggerMigrationKey+  = NamedTriggerKey String+  deriving+    ( -- | @since 1.1.0.0+      Eq+    , -- | @since 1.1.0.0+      Ord+    )++{- | Gets the 'TriggerMigrationKey' for the 'TriggerDefinition'++@since 1.1.0.0+-}+triggerMigrationKey :: TriggerDefinition -> TriggerMigrationKey+triggerMigrationKey =+  i_triggerMigrationKey++{- | Builds a 'TriggerDefinition' that will execute the named function for each row+  being inserted before it is inserted.++@since 1.1.0.0+-}+beforeInsert :: String -> Expr.QualifiedOrUnqualified Expr.FunctionName -> TriggerDefinition+beforeInsert name =+  mkTriggerDefinition+    name+    Expr.triggerBefore+    (Expr.triggerOnInsert :| [])+    Expr.triggerForEachRow++{- | Builds a 'TriggerDefinition' that will execute the named function for each row+  being inserted after it is inserted.++@since 1.1.0.0+-}+afterInsert :: String -> Expr.QualifiedOrUnqualified Expr.FunctionName -> TriggerDefinition+afterInsert name =+  mkTriggerDefinition+    name+    Expr.triggerAfter+    (Expr.triggerOnInsert :| [])+    Expr.triggerForEachRow++{- | Builds a 'TriggerDefinition' that will execute the named function for each row+  being updated before it is updated.++@since 1.1.0.0+-}+beforeUpdate :: String -> Expr.QualifiedOrUnqualified Expr.FunctionName -> TriggerDefinition+beforeUpdate name =+  mkTriggerDefinition+    name+    Expr.triggerBefore+    (Expr.triggerOnUpdate :| [])+    Expr.triggerForEachRow++{- | Builds a 'TriggerDefinition' that will execute the named function for each row+  being updated after it is updated.++@since 1.1.0.0+-}+afterUpdate :: String -> Expr.QualifiedOrUnqualified Expr.FunctionName -> TriggerDefinition+afterUpdate name =+  mkTriggerDefinition+    name+    Expr.triggerAfter+    (Expr.triggerOnUpdate :| [])+    Expr.triggerForEachRow++{- | Builds a 'TriggerDefinition' that will execute the named function for each row+  being deleted before it is deleted.++@since 1.1.0.0+-}+beforeDelete :: String -> Expr.QualifiedOrUnqualified Expr.FunctionName -> TriggerDefinition+beforeDelete name =+  mkTriggerDefinition+    name+    Expr.triggerBefore+    (Expr.triggerOnDelete :| [])+    Expr.triggerForEachRow++{- | Builds a 'TriggerDefinition' that will execute the named function for each row+  being deleted after it is deleted.++@since 1.1.0.0+-}+afterDelete :: String -> Expr.QualifiedOrUnqualified Expr.FunctionName -> TriggerDefinition+afterDelete name =+  mkTriggerDefinition+    name+    Expr.triggerAfter+    (Expr.triggerOnDelete :| [])+    Expr.triggerForEachRow++{- | Constructs a 'TriggerDefinition' definining a trigger with the specified name.+  Note that orville is currently not capable of migrating triggers based on their+  structure. The trigger will be created if no trigger exists on the table with+  the specificed name. If a trigger already exists with the same name on the table+  then nothing will be done.++@since 1.1.0.0+-}+mkTriggerDefinition ::+  String ->+  Expr.TriggerTiming ->+  NonEmpty Expr.TriggerEvent ->+  Expr.TriggerFireScope ->+  Expr.QualifiedOrUnqualified Expr.FunctionName ->+  TriggerDefinition+mkTriggerDefinition name timing events fireScope functionName =+  TriggerDefinition+    { i_triggerMigrationKey = NamedTriggerKey name+    , i_triggerCreateExpr =+        \orReplace tableName ->+          Expr.createTrigger+            orReplace+            (Expr.triggerName name)+            timing+            events+            tableName+            fireScope+            functionName+    }++{- | Builds a 'Expr.CreateTriggerExpr' that will create a SQL trigger matching+  the given 'TriggerDefinition' when it is executed.++@since 1.1.0.0+-}+mkCreateTriggerExpr ::+  TriggerDefinition ->+  Maybe Expr.OrReplace ->+  Expr.QualifiedOrUnqualified Expr.TableName ->+  Expr.CreateTriggerExpr+mkCreateTriggerExpr =+  i_triggerCreateExpr
src/Orville/PostgreSQL/UnliftIO.hs view
@@ -39,8 +39,7 @@  import qualified Control.Monad.IO.Unlift as UL -{- |-  'liftWithConnectionViaUnliftIO' can be used as the implementation of+{- | 'liftWithConnectionViaUnliftIO' can be used as the implementation of   'Orville.PostgreSQL.liftWithConnection' for   'Orville.PostgreSQL.MonadOrvilleControl' when the 'Monad' implements   'UL.MonadUnliftIO'.@@ -55,8 +54,7 @@ liftWithConnectionViaUnliftIO ioWithConn action =   UL.withRunInIO $ \runInIO -> ioWithConn (runInIO . action) -{- |-  'liftCatchViaUnliftIO' can be used as the implementation of+{- | 'liftCatchViaUnliftIO' can be used as the implementation of   'Orville.PostgreSQL.liftCatch' for 'Orville.PostgreSQL.MonadOrvilleControl'   when the 'Monad' implements 'UL.MonadUnliftIO'. @@ -73,10 +71,9 @@   UL.liftIO $     ioCatch       (UL.unliftIO unlio action)-      (\ex -> UL.unliftIO unlio (handler ex))+      (UL.unliftIO unlio . handler) -{- |-  'liftMaskViaUnliftIO' can be used as the implementation of+{- | 'liftMaskViaUnliftIO' can be used as the implementation of   'Orville.PostgreSQL.liftMask' for 'Orville.PostgreSQL.MonadOrvilleControl'   when the 'Monad' implements 'UL.MonadUnliftIO'. 
test/Main.hs view
@@ -16,17 +16,25 @@ import qualified Test.Cursor as Cursor import qualified Test.EntityOperations as EntityOperations import qualified Test.Execution as Execution+import qualified Test.Expr.Aggregate as ExprAggregate+import qualified Test.Expr.ConditionalExpr as ExprConditional import qualified Test.Expr.Count as ExprCount import qualified Test.Expr.Cursor as ExprCursor import qualified Test.Expr.GroupBy as ExprGroupBy import qualified Test.Expr.GroupByOrderBy as ExprGroupByOrderBy import qualified Test.Expr.InsertUpdateDelete as ExprInsertUpdateDelete+import qualified Test.Expr.Join as ExprJoin import qualified Test.Expr.Math as ExprMath import qualified Test.Expr.OrderBy as ExprOrderBy import qualified Test.Expr.SequenceDefinition as ExprSequenceDefinition import qualified Test.Expr.TableDefinition as ExprTableDefinition+import qualified Test.Expr.TableReferenceList as ExprTableReferenceList+import qualified Test.Expr.TextSearch as ExprTextSearch import qualified Test.Expr.Time as ExprTime+import qualified Test.Expr.Trigger as ExprTrigger+import qualified Test.Expr.Vacuum as ExprVacuum import qualified Test.Expr.Where as ExprWhere+import qualified Test.Expr.Window as ExprWindow import qualified Test.FieldDefinition as FieldDefinition import qualified Test.MarshallError as MarshallError import qualified Test.PgCatalog as PgCatalog@@ -53,8 +61,10 @@       [ Connection.connectionTests pool       , RawSql.rawSqlTests pool       , Execution.executionTests pool+      , PgTime.pgTimeTests pool       , SqlType.sqlTypeTests pool       , PostgreSQLAxioms.postgreSQLAxiomTests pool+      , ExprAggregate.aggregateTests pool       , ExprInsertUpdateDelete.insertUpdateDeleteTests pool       , ExprWhere.whereTests pool       , ExprOrderBy.orderByTests pool@@ -66,6 +76,13 @@       , ExprCount.countTests pool       , ExprMath.mathTests pool       , ExprTime.timeTests pool+      , ExprTrigger.triggerTests pool+      , ExprVacuum.vacuumTests+      , ExprJoin.joinTests+      , ExprTableReferenceList.tableReferenceListTests+      , ExprConditional.conditionalTests+      , ExprWindow.windowTests+      , ExprTextSearch.textSearchTests pool       , FieldDefinition.fieldDefinitionTests pool       , SqlMarshaller.sqlMarshallerTests       , MarshallError.marshallErrorTests pool@@ -80,7 +97,6 @@       , AutoMigration.autoMigrationTests pool       , Cursor.cursorTests pool       , SqlCommenter.sqlCommenterTests pool-      , PgTime.pgTimeTests pool       ]    Monad.unless (Property.allPassed summary) SE.exitFailure
test/Test/AutoMigration.hs view
@@ -17,6 +17,7 @@ import qualified Data.List.NonEmpty as NEL import qualified Data.Maybe as Maybe import qualified Data.String as String+import qualified Data.Text as T import Hedgehog ((===)) import qualified Hedgehog as HH import qualified Hedgehog.Gen as Gen@@ -29,8 +30,10 @@ import qualified Orville.PostgreSQL.Raw.Connection as Conn import qualified Orville.PostgreSQL.Raw.RawSql as RawSql import qualified Orville.PostgreSQL.Schema as Schema+import qualified Orville.PostgreSQL.Schema.TableDefinition as TableDefinition  import qualified Test.Entities.Foo as Foo+import Test.Orphans () import qualified Test.PgAssert as PgAssert import qualified Test.PgGen as PgGen import qualified Test.Property as Property@@ -40,7 +43,8 @@ autoMigrationTests pool =   Property.group     "AutoMigration"-    [ prop_raisesErrorIfMigrationLockIsLocked pool+    [ prop_altersColumnAddIdentity pool+    , prop_raisesErrorIfMigrationLockIsLocked pool     , prop_releasesMigrationLockOnError pool     , prop_createsMissingTables pool     , prop_dropsRequestedTables pool@@ -58,20 +62,40 @@     , prop_altersModifiedSequences pool     , prop_addsAndRemovesMixedIndexes pool     , prop_arbitrarySchemaInitialMigration pool+    , prop_createsMissingFunctions pool+    , prop_recreatesAlteredFunctions pool+    , prop_dropsRequestedFunctions pool+    , prop_createsMissingTriggers pool+    , prop_dropsUnrequestedTriggers pool+    , prop_loadsMissingExtensions pool+    , prop_unloadsPresentExtensions pool+    , prop_addsTableComment pool+    , prop_removesTableComment pool+    , prop_modifiesTableComment pool+    , prop_addsColumnCommmentsOnCreateTable pool+    , prop_modifiesColumnComments pool     ]  prop_raisesErrorIfMigrationLockIsLocked :: Property.NamedDBProperty prop_raisesErrorIfMigrationLockIsLocked =   Property.singletonNamedDBProperty "Raises an error when the migration lock is hold" $ \pool -> do+    let+      testLockOptions =+        AutoMigration.defaultLockOptions+          { AutoMigration.maxLockAttempts = 3+          , AutoMigration.delayBetweenLockAttemptsMicros = 1000+          , AutoMigration.lockDelayVariationMicros = 0+          }+     errOrSuccess <-       HH.evalIO $         Orville.runOrville pool $-          AutoMigration.withMigrationLock AutoMigration.defaultLockId $+          AutoMigration.withMigrationLock testLockOptions $             MIO.liftIO $               ExSafe.try $                 Orville.runOrville pool $                   AutoMigration.withMigrationLock-                    AutoMigration.defaultLockId+                    testLockOptions                     (pure ())      case errOrSuccess of@@ -95,14 +119,14 @@             Orville.runOrville pool $               ExSafe.handle (\SimulatedError -> pure ()) $                 AutoMigration.withMigrationLock-                  AutoMigration.defaultLockId+                  AutoMigration.defaultLockOptions                   (ExSafe.throwM SimulatedError)            -- If the lock is not released by the previous 'withMigrationLock'           -- this second call will fail to acquire the lock and the test will           -- fail           AutoMigration.withMigrationLock-            AutoMigration.defaultLockId+            AutoMigration.defaultLockOptions             (pure ())  data SimulatedError = SimulatedError@@ -160,9 +184,154 @@     PgAssert.assertTableDoesNotExist pool (Orville.tableIdUnqualifiedNameString fooTableId)     migrationPlanStepStrings secondTimePlan === [] +prop_addsTableComment :: Property.NamedDBProperty+prop_addsTableComment =+  Property.singletonNamedDBProperty "Adds a comment to a table" $ \pool -> do+    let+      comment = String.fromString "This is a comment"++      fooTableWithComment = Orville.setTableComment comment Foo.table++      fooTableId =+        Orville.tableIdentifier fooTableWithComment++    firstTimePlan <-+      HH.evalIO $+        Orville.runOrville pool $ do+          Orville.executeVoid Orville.DDLQuery $ TestTable.dropTableDefSql fooTableWithComment+          AutoMigration.generateMigrationPlan AutoMigration.defaultOptions [AutoMigration.SchemaTable fooTableWithComment]++    secondTimePlan <-+      HH.evalIO $+        Orville.runOrville pool $ do+          AutoMigration.executeMigrationPlan AutoMigration.defaultOptions firstTimePlan+          AutoMigration.generateMigrationPlan AutoMigration.defaultOptions [AutoMigration.SchemaTable fooTableWithComment]++    length (AutoMigration.migrationPlanSteps firstTimePlan) === 2+    PgAssert.assertTableHasComment pool (Orville.tableIdUnqualifiedNameString fooTableId) comment+    migrationPlanStepStrings secondTimePlan === []++prop_removesTableComment :: Property.NamedDBProperty+prop_removesTableComment =+  Property.singletonNamedDBProperty "Removes a comment from a table" $ \pool -> do+    let+      comment = String.fromString "This is a comment"++      fooTableId =+        Orville.tableIdentifier Foo.table++    firstTimePlan <-+      HH.evalIO $+        Orville.runOrville pool $ do+          Orville.executeVoid Orville.DDLQuery $ TestTable.dropTableDefSql Foo.table+          Orville.executeVoid Orville.DDLQuery $ Schema.mkCreateTableExpr Foo.table+          Orville.executeVoid Orville.DDLQuery $ Expr.commentTableExpr (Orville.tableName Foo.table) (Just $ Expr.commentText comment)+          AutoMigration.generateMigrationPlan AutoMigration.defaultOptions [AutoMigration.SchemaTable Foo.table]++    secondTimePlan <-+      HH.evalIO $+        Orville.runOrville pool $ do+          AutoMigration.executeMigrationPlan AutoMigration.defaultOptions firstTimePlan+          AutoMigration.generateMigrationPlan AutoMigration.defaultOptions [AutoMigration.SchemaTable Foo.table]++    length (AutoMigration.migrationPlanSteps firstTimePlan) === 1+    PgAssert.assertTableDoesNotHaveComment pool (Orville.tableIdUnqualifiedNameString fooTableId)+    migrationPlanStepStrings secondTimePlan === []++prop_modifiesTableComment :: Property.NamedDBProperty+prop_modifiesTableComment =+  Property.singletonNamedDBProperty "Modifies the comment on a table" $ \pool -> do+    let+      oldComment = String.fromString "This is a comment"+      newComment = String.fromString "This is a new comment"++      fooTableId =+        Orville.tableIdentifier Foo.table++      fooTableWithNewComment = Orville.setTableComment newComment Foo.table++    HH.evalIO $+      Orville.runOrville pool $ do+        Orville.executeVoid Orville.DDLQuery $ TestTable.dropTableDefSql Foo.table+        Orville.executeVoid Orville.DDLQuery $ Schema.mkCreateTableExpr Foo.table+        Orville.executeVoid Orville.DDLQuery $ Expr.commentTableExpr (Orville.tableName Foo.table) (Just $ Expr.commentText oldComment)++    PgAssert.assertTableHasComment pool (Orville.tableIdUnqualifiedNameString fooTableId) oldComment++    firstTimePlan <-+      HH.evalIO $+        Orville.runOrville pool $+          AutoMigration.generateMigrationPlan AutoMigration.defaultOptions [AutoMigration.SchemaTable fooTableWithNewComment]++    secondTimePlan <-+      HH.evalIO $+        Orville.runOrville pool $ do+          AutoMigration.executeMigrationPlan AutoMigration.defaultOptions firstTimePlan+          AutoMigration.generateMigrationPlan AutoMigration.defaultOptions [AutoMigration.SchemaTable fooTableWithNewComment]++    length (AutoMigration.migrationPlanSteps firstTimePlan) === 1+    PgAssert.assertTableHasComment pool (Orville.tableIdUnqualifiedNameString fooTableId) newComment+    migrationPlanStepStrings secondTimePlan === []++prop_addsColumnCommmentsOnCreateTable :: Property.NamedDBProperty+prop_addsColumnCommmentsOnCreateTable =+  Property.singletonNamedDBProperty "Adds column comments when creating table" $ \pool -> do+    let+      columnsAndComments = [("foo", Just "foo comment"), ("bar", Nothing), ("baz", Just "baz comment")]+      tableName = "migration_test"+      tableDef = mkIntListTableWithComments tableName columnsAndComments++    firstTimePlan <-+      HH.evalIO $+        Orville.runOrville pool $ do+          Orville.executeVoid Orville.DDLQuery $ TestTable.dropTableDefSql tableDef+          AutoMigration.generateMigrationPlan AutoMigration.defaultOptions [AutoMigration.SchemaTable tableDef]++    secondTimePlan <-+      HH.evalIO $+        Orville.runOrville pool $ do+          AutoMigration.executeMigrationPlan AutoMigration.defaultOptions firstTimePlan+          AutoMigration.generateMigrationPlan AutoMigration.defaultOptions [AutoMigration.SchemaTable tableDef]++    length (AutoMigration.migrationPlanSteps firstTimePlan) === 3+    PgAssert.assertTableColumnsHaveOrDoNotHaveComments pool tableName columnsAndComments+    migrationPlanStepStrings secondTimePlan === []++prop_modifiesColumnComments :: Property.NamedDBProperty+prop_modifiesColumnComments =+  Property.singletonNamedDBProperty "Modifies column comments" $ \pool -> do+    let+      initialColumnsAndComments = [("foo", Just "foo comment"), ("bar", Nothing), ("baz", Nothing)]+      finalColumnsAndComments = [("foo", Nothing), ("bar", Just "bar comment"), ("baz", Just "baz comment")]+      tableName = "migration_test"+      initialTableDef = mkIntListTableWithComments tableName initialColumnsAndComments+      finalTableDef = mkIntListTableWithComments tableName finalColumnsAndComments++    firstTimePlan <-+      HH.evalIO $+        Orville.runOrville pool $ do+          Orville.executeVoid Orville.DDLQuery $ TestTable.dropTableDefSql initialTableDef+          Orville.executeVoid Orville.DDLQuery $ Schema.mkCreateTableExpr initialTableDef+          AutoMigration.generateMigrationPlan AutoMigration.defaultOptions [AutoMigration.SchemaTable initialTableDef]++    MIO.liftIO . Orville.runOrville pool $+      AutoMigration.executeMigrationPlan AutoMigration.defaultOptions firstTimePlan+    PgAssert.assertTableColumnsHaveOrDoNotHaveComments pool tableName initialColumnsAndComments++    secondTimePlan <-+      HH.evalIO . Orville.runOrville pool $+        AutoMigration.generateMigrationPlan AutoMigration.defaultOptions [AutoMigration.SchemaTable finalTableDef]++    MIO.liftIO . Orville.runOrville pool $+      AutoMigration.executeMigrationPlan AutoMigration.defaultOptions secondTimePlan+    PgAssert.assertTableColumnsHaveOrDoNotHaveComments pool tableName finalColumnsAndComments++    length (AutoMigration.migrationPlanSteps firstTimePlan) === 1+    length (AutoMigration.migrationPlanSteps secondTimePlan) === 3+ prop_addsAndRemovesColumns :: Property.NamedDBProperty prop_addsAndRemovesColumns =-  Property.namedDBProperty "Adds and removes columns columns" $ \pool -> do+  Property.namedDBProperty "Adds and removes columns" $ \pool -> do     let       genColumnList =         Gen.subsequence ["foo", "bar", "baz", "bat", "bax"]@@ -233,8 +402,7 @@         HH.annotate "Expected migration to fail, but it did not"         HH.failure -{- |-  Migration Guide: @SomeField@ has been removed. @foldMarshallerFields@ can be+{- | Migration Guide: @SomeField@ has been removed. @foldMarshallerFields@ can be   used to collect data from the fields in a @SqlMarshaller@ while converting   the results to whatever type you desire. @@ -356,6 +524,46 @@         , SomeField <$> genFieldWithMaybeDefault genDefaultDouble Orville.doubleDefault (Orville.doubleField "column")         ] +prop_altersColumnAddIdentity :: Property.NamedDBProperty+prop_altersColumnAddIdentity =+  Property.namedDBProperty "Alters an existing column to add IDENTITY" $ \pool -> do+    colIdentity <- HH.forAll Gen.enumBounded++    let+      originalField = Orville.integerField "column"+      newField = Orville.markAsIdentity colIdentity originalField+      originalTableDef =+        Orville.mkTableDefinitionWithoutKey+          "migration_test"+          (Orville.marshallField id originalField)++      newTableDef =+        Orville.mkTableDefinitionWithoutKey+          "migration_test"+          (Orville.marshallField id newField)++    firstTimePlan <-+      HH.evalIO+        . Orville.runOrville pool+        $ do+          Orville.executeVoid Orville.DDLQuery $ TestTable.dropTableDefSql originalTableDef+          Orville.executeVoid Orville.DDLQuery $ Schema.mkCreateTableExpr originalTableDef+          AutoMigration.generateMigrationPlan AutoMigration.defaultOptions [AutoMigration.SchemaTable newTableDef]++    originalTableDesc <- PgAssert.assertTableExists pool "migration_test"++    secondTimePlan <-+      HH.evalIO+        . Orville.runOrville pool+        $ do+          AutoMigration.executeMigrationPlan AutoMigration.defaultOptions firstTimePlan+          AutoMigration.generateMigrationPlan AutoMigration.defaultOptions [AutoMigration.SchemaTable newTableDef]++    PgAssert.assertFieldIdentityGenerationMatches originalTableDesc "column" Nothing+    newTableDesc <- PgAssert.assertTableExists pool "migration_test"+    PgAssert.assertFieldIdentityGenerationMatches newTableDesc "column" (Just colIdentity)+    migrationPlanStepStrings secondTimePlan === []+ prop_altersColumnDefaultValue_Bool :: Property.NamedDBProperty prop_altersColumnDefaultValue_Bool =   Property.namedDBProperty "Alters default value on existing column (boolean)" $ \pool -> do@@ -654,11 +862,22 @@         Orville.addTableIndexes originalIndexes $           mkIntListTable "migration_test" originalColumns +      originalTableDefWithSchema =+        TableDefinition.setTableSchema "orville_migration_schema" $+          Orville.addTableIndexes originalIndexes $+            mkIntListTable "migration_test" originalColumns+       newTableDef =         Orville.addTableIndexes newIndexes $           Orville.dropColumns columnsToDrop $             mkIntListTable "migration_test" newColumns +      newTableDefWithSchema =+        TableDefinition.setTableSchema "orville_migration_schema" $+          Orville.addTableIndexes newIndexes $+            Orville.dropColumns columnsToDrop $+              mkIntListTable "migration_test" newColumns+     HH.cover 5 (String.fromString "Adding Indexes") (not $ null (newTestIndexes \\ originalTestIndexes))     HH.cover 5 (String.fromString "Dropping Indexes") (not $ null (originalTestIndexes \\ newTestIndexes))     HH.cover@@ -672,9 +891,11 @@     firstTimePlan <-       HH.evalIO $         Orville.runOrville pool $ do+          Orville.executeVoid Orville.DDLQuery $ RawSql.fromString "DROP SCHEMA IF EXISTS orville_migration_schema CASCADE"+          Orville.executeVoid Orville.DDLQuery $ RawSql.fromString "CREATE SCHEMA orville_migration_schema"           Orville.executeVoid Orville.DDLQuery $ TestTable.dropTableDefSql originalTableDef-          AutoMigration.autoMigrateSchema AutoMigration.defaultOptions [AutoMigration.SchemaTable originalTableDef]-          AutoMigration.generateMigrationPlan AutoMigration.defaultOptions [AutoMigration.SchemaTable newTableDef]+          AutoMigration.autoMigrateSchema AutoMigration.defaultOptions [AutoMigration.SchemaTable originalTableDef, AutoMigration.SchemaTable originalTableDefWithSchema]+          AutoMigration.generateMigrationPlan AutoMigration.defaultOptions [AutoMigration.SchemaTable newTableDef, AutoMigration.SchemaTable newTableDefWithSchema]      HH.annotate ("First time migration steps: " <> show (migrationPlanStepStrings firstTimePlan)) @@ -683,20 +904,34 @@       (PgAssert.assertIndexExists originalTableDesc <$> testIndexUniqueness <*> testIndexColumns)       originalTestIndexes +    originalTableWithSchemaDesc <- PgAssert.assertTableExistsInSchema pool "orville_migration_schema" "migration_test"+    Fold.traverse_+      (PgAssert.assertIndexExists originalTableWithSchemaDesc <$> testIndexUniqueness <*> testIndexColumns)+      originalTestIndexes+     secondTimePlan <-       HH.evalIO $         Orville.runOrville pool $ do           AutoMigration.executeMigrationPlan AutoMigration.defaultOptions firstTimePlan-          AutoMigration.generateMigrationPlan AutoMigration.defaultOptions [AutoMigration.SchemaTable newTableDef]+          AutoMigration.generateMigrationPlan+            AutoMigration.defaultOptions+            [AutoMigration.SchemaTable newTableDef, AutoMigration.SchemaTable newTableDefWithSchema]      migrationPlanStepStrings secondTimePlan === []+     newTableDesc <- PgAssert.assertTableExists pool "migration_test"+    newTableWithSchemaDesc <- PgAssert.assertTableExistsInSchema pool "orville_migration_schema" "migration_test"      Fold.traverse_       (PgAssert.assertIndexExists newTableDesc <$> testIndexUniqueness <*> testIndexColumns)       newTestIndexes     length (PgCatalog.relationIndexes newTableDesc) === length (List.nub newTestIndexes) +    Fold.traverse_+      (PgAssert.assertIndexExists newTableWithSchemaDesc <$> testIndexUniqueness <*> testIndexColumns)+      newTestIndexes+    length (PgCatalog.relationIndexes newTableWithSchemaDesc) === length (List.nub newTestIndexes)+ prop_createsMissingSequences :: Property.NamedDBProperty prop_createsMissingSequences =   Property.singletonNamedDBProperty "Creates missing sequences" $ \pool -> do@@ -727,7 +962,7 @@  prop_dropsRequestedSequences :: Property.NamedDBProperty prop_dropsRequestedSequences =-  Property.singletonNamedDBProperty "Drops requested tables" $ \pool -> do+  Property.singletonNamedDBProperty "Drops requested sequences" $ \pool -> do     let       sequenceDef =         Orville.mkSequenceDefinition "migration_test_sequence"@@ -884,6 +1119,258 @@     migrationPlanStepStrings migrationPlanAfterMigration === []     Fold.traverse_ (assertTableStructure pool) testTables +prop_createsMissingFunctions :: Property.NamedDBProperty+prop_createsMissingFunctions =+  Property.singletonNamedDBProperty "Creates missing functions" $ \pool -> do+    let+      functionDef =+        Orville.mkTriggerFunction+          "test_create_function"+          Orville.plpgsql+          "BEGIN return NEW; END"++      functionId =+        Orville.functionIdentifier functionDef++    firstTimePlan <-+      HH.evalIO $+        Orville.runOrville pool $ do+          Orville.executeVoid Orville.DDLQuery $+            Expr.dropFunction+              (Just Expr.ifExists)+              (Orville.functionName functionDef)+          AutoMigration.generateMigrationPlan AutoMigration.defaultOptions [AutoMigration.SchemaFunction functionDef]++    secondTimePlan <-+      HH.evalIO $+        Orville.runOrville pool $ do+          AutoMigration.executeMigrationPlan AutoMigration.defaultOptions firstTimePlan+          AutoMigration.generateMigrationPlan AutoMigration.defaultOptions [AutoMigration.SchemaFunction functionDef]++    length (AutoMigration.migrationPlanSteps firstTimePlan) === 1++    proc <- PgAssert.assertFunctionExists pool (Orville.functionIdUnqualifiedNameString functionId)+    PgCatalog.pgProcSource proc === T.pack "BEGIN return NEW; END"++    migrationPlanStepStrings secondTimePlan === []++prop_recreatesAlteredFunctions :: Property.NamedDBProperty+prop_recreatesAlteredFunctions =+  Property.singletonNamedDBProperty "Recreates functions with altered source code" $ \pool -> do+    let+      oldFunctionDef =+        Orville.mkTriggerFunction+          "test_recreate_function"+          Orville.plpgsql+          "BEGIN return OLD; END"++      newFunctionDef =+        Orville.mkTriggerFunction+          "test_recreate_function"+          Orville.plpgsql+          "BEGIN return NEW; END"++      functionId =+        Orville.functionIdentifier oldFunctionDef++    firstTimePlan <-+      HH.evalIO $+        Orville.runOrville pool $ do+          Orville.executeVoid Orville.DDLQuery $+            Expr.dropFunction (Just Expr.ifExists) (Orville.functionName oldFunctionDef)+          AutoMigration.autoMigrateSchema AutoMigration.defaultOptions [AutoMigration.SchemaFunction oldFunctionDef]+          AutoMigration.generateMigrationPlan AutoMigration.defaultOptions [AutoMigration.SchemaFunction newFunctionDef]++    secondTimePlan <-+      HH.evalIO $+        Orville.runOrville pool $ do+          AutoMigration.executeMigrationPlan AutoMigration.defaultOptions firstTimePlan+          AutoMigration.generateMigrationPlan AutoMigration.defaultOptions [AutoMigration.SchemaFunction newFunctionDef]++    length (AutoMigration.migrationPlanSteps firstTimePlan) === 1++    proc <- PgAssert.assertFunctionExists pool (Orville.functionIdUnqualifiedNameString functionId)+    PgCatalog.pgProcSource proc === T.pack "BEGIN return NEW; END"++    migrationPlanStepStrings secondTimePlan === []++prop_dropsRequestedFunctions :: Property.NamedDBProperty+prop_dropsRequestedFunctions =+  Property.singletonNamedDBProperty "Drops requested functions" $ \pool -> do+    let+      functionDef =+        Orville.mkTriggerFunction+          "test_drop_function"+          Orville.plpgsql+          "BEGIN return NEW; END"++      functionId =+        Orville.functionIdentifier functionDef++    firstTimePlan <-+      HH.evalIO $+        Orville.runOrville pool $ do+          Orville.executeVoid Orville.DDLQuery $ Orville.mkCreateFunctionExpr functionDef (Just Expr.orReplace)+          AutoMigration.generateMigrationPlan AutoMigration.defaultOptions [AutoMigration.SchemaDropFunction functionId]++    secondTimePlan <-+      HH.evalIO $+        Orville.runOrville pool $ do+          AutoMigration.executeMigrationPlan AutoMigration.defaultOptions firstTimePlan+          AutoMigration.generateMigrationPlan AutoMigration.defaultOptions [AutoMigration.SchemaDropFunction functionId]++    length (AutoMigration.migrationPlanSteps firstTimePlan) === 1+    PgAssert.assertFunctionDoesNotExist pool (Orville.functionIdUnqualifiedNameString functionId)+    migrationPlanStepStrings secondTimePlan === []++prop_createsMissingTriggers :: Property.NamedDBProperty+prop_createsMissingTriggers =+  Property.singletonNamedDBProperty "Creates missing triggers" $ \pool -> do+    let+      functionDef =+        Orville.mkTriggerFunction+          "test_create_trigger_function"+          Orville.plpgsql+          "BEGIN return NEW; END"++      testTrigger =+        Orville.beforeInsert+          "before_insert_trigger"+          (Orville.functionName functionDef)++      tableWithTrigger =+        Orville.addTableTriggers [testTrigger] Foo.table++      schemaItems =+        [ AutoMigration.SchemaFunction functionDef+        , AutoMigration.SchemaTable tableWithTrigger+        ]++    firstTimePlan <-+      HH.evalIO $+        Orville.runOrville pool $ do+          Orville.executeVoid Orville.DDLQuery $ TestTable.dropTableDefSql tableWithTrigger+          Orville.executeVoid Orville.DDLQuery $ Expr.dropFunction (Just Expr.ifExists) (Orville.functionName functionDef)+          AutoMigration.generateMigrationPlan AutoMigration.defaultOptions schemaItems++    secondTimePlan <-+      HH.evalIO $+        Orville.runOrville pool $ do+          AutoMigration.executeMigrationPlan AutoMigration.defaultOptions firstTimePlan+          AutoMigration.generateMigrationPlan AutoMigration.defaultOptions schemaItems++    length (AutoMigration.migrationPlanSteps firstTimePlan) === 3++    fooRelation <- PgAssert.assertTableExists pool "foo"+    _ <- PgAssert.assertTriggerExists fooRelation "before_insert_trigger"++    migrationPlanStepStrings secondTimePlan === []++prop_dropsUnrequestedTriggers :: Property.NamedDBProperty+prop_dropsUnrequestedTriggers =+  Property.singletonNamedDBProperty "Drops unrequested triggers" $ \pool -> do+    let+      functionDef =+        Orville.mkTriggerFunction+          "test_drop_trigger_function"+          Orville.plpgsql+          "BEGIN return NEW; END"++      testTrigger =+        Orville.beforeInsert+          "before_insert_trigger"+          (Orville.functionName functionDef)++      tableWithoutTrigger =+        Foo.table++      tableWithTrigger =+        Orville.addTableTriggers [testTrigger] tableWithoutTrigger++      schemaItemsWithTrigger =+        [ AutoMigration.SchemaFunction functionDef+        , AutoMigration.SchemaTable tableWithTrigger+        ]++      schemaItemsWithoutTrigger =+        [ AutoMigration.SchemaFunction functionDef+        , AutoMigration.SchemaTable tableWithoutTrigger+        ]++    firstTimePlan <-+      HH.evalIO $+        Orville.runOrville pool $ do+          Orville.executeVoid Orville.DDLQuery $ TestTable.dropTableDefSql tableWithoutTrigger+          Orville.executeVoid Orville.DDLQuery $ Expr.dropFunction (Just Expr.ifExists) (Orville.functionName functionDef)+          AutoMigration.autoMigrateSchema AutoMigration.defaultOptions schemaItemsWithTrigger+          AutoMigration.generateMigrationPlan AutoMigration.defaultOptions schemaItemsWithoutTrigger++    secondTimePlan <-+      HH.evalIO $+        Orville.runOrville pool $ do+          AutoMigration.executeMigrationPlan AutoMigration.defaultOptions firstTimePlan+          AutoMigration.generateMigrationPlan AutoMigration.defaultOptions schemaItemsWithoutTrigger++    length (AutoMigration.migrationPlanSteps firstTimePlan) === 1+    fooRelation <- PgAssert.assertTableExists pool "foo"+    _ <- PgAssert.assertTriggerDoesNotExist fooRelation "before_insert_trigger"+    migrationPlanStepStrings secondTimePlan === []++prop_loadsMissingExtensions :: Property.NamedDBProperty+prop_loadsMissingExtensions =+  Property.singletonNamedDBProperty "Loads missing extensions" $ \pool -> do+    let+      schemaItems =+        [ AutoMigration.SchemaExtension $ Orville.nameToExtensionId "pg_trgm"+        ]++    firstTimePlan <-+      HH.evalIO $+        Orville.runOrville pool $ do+          Orville.executeVoid Orville.DDLQuery $ Expr.dropExtensionExpr (Expr.extensionName "pg_trgm") (Just Expr.ifExists) Nothing+          AutoMigration.generateMigrationPlan AutoMigration.defaultOptions schemaItems++    secondTimePlan <-+      HH.evalIO $+        Orville.runOrville pool $ do+          AutoMigration.executeMigrationPlan AutoMigration.defaultOptions firstTimePlan+          AutoMigration.generateMigrationPlan AutoMigration.defaultOptions schemaItems++    length (AutoMigration.migrationPlanSteps firstTimePlan) === 1+    _ <- PgAssert.assertExtensionLoaded pool "pg_trgm"+    migrationPlanStepStrings secondTimePlan === []++prop_unloadsPresentExtensions :: Property.NamedDBProperty+prop_unloadsPresentExtensions =+  Property.singletonNamedDBProperty "Unloads present extensions" $ \pool -> do+    let+      pgtrgmExtension = Orville.nameToExtensionId "pg_trgm"++      schemaItemsLoadExtension =+        [ AutoMigration.SchemaExtension pgtrgmExtension+        ]++      schemaItemsUnloadExtension =+        [ AutoMigration.SchemaDropExtension pgtrgmExtension+        ]++    firstTimePlan <-+      HH.evalIO $+        Orville.runOrville pool $ do+          AutoMigration.autoMigrateSchema AutoMigration.defaultOptions schemaItemsLoadExtension+          AutoMigration.generateMigrationPlan AutoMigration.defaultOptions schemaItemsUnloadExtension++    secondTimePlan <-+      HH.evalIO $+        Orville.runOrville pool $ do+          AutoMigration.executeMigrationPlan AutoMigration.defaultOptions firstTimePlan+          AutoMigration.generateMigrationPlan AutoMigration.defaultOptions schemaItemsUnloadExtension++    length (AutoMigration.migrationPlanSteps firstTimePlan) === 1+    PgAssert.assertExtensionNotLoaded pool "pg_trgm"++    migrationPlanStepStrings secondTimePlan === []+ assertTableStructure ::   (HH.MonadTest m, MIO.MonadIO m) =>   Orville.ConnectionPool ->@@ -905,8 +1392,8 @@     (testTableUniqueConstraints testTable)    Fold.traverse_-    (PgAssert.assertForeignKeyConstraintExists tableDesc)-    (map mkForeignKeyInfo . testTableForeignKeys $ testTable)+    (PgAssert.assertForeignKeyConstraintExists tableDesc . mkForeignKeyInfo)+    (testTableForeignKeys testTable)  mkForeignKeyInfo :: TestForeignKey -> PgAssert.ForeignKeyInfo mkForeignKeyInfo testForeignKey =@@ -991,9 +1478,8 @@    tableName <- PgGen.pgIdentifierWithPrefix "t_" 1 -  TestTable tableName-    <$> pure columns-    <*> Gen.subsequence columns+  TestTable tableName columns+    <$> Gen.subsequence columns     <*> generateTestIndexes columns tableName     <*> generateTestUniqueConstraints columns     <*> pure [] -- No foreign keys can be generated until we've generate test tables@@ -1166,14 +1652,23 @@   List.nub <$> Gen.list (Range.constant 0 10) PgGen.pgIdentifier  mkIntListTable :: String -> [String] -> Orville.TableDefinition Orville.NoKey [Int32] [Int32]-mkIntListTable tableName columns =-  Orville.mkTableDefinitionWithoutKey tableName (intColumnsMarshaller columns)+mkIntListTable tableName =+  mkIntListTableWithComments tableName . fmap (\c -> (c, Nothing)) +mkIntListTableWithComments :: String -> [(String, Maybe String)] -> Orville.TableDefinition Orville.NoKey [Int32] [Int32]+mkIntListTableWithComments tableName columnsAndComments =+  Orville.mkTableDefinitionWithoutKey tableName (intColumnsMarshallerWithComments columnsAndComments)+ intColumnsMarshaller :: [String] -> Orville.SqlMarshaller [Int32] [Int32]-intColumnsMarshaller columns =+intColumnsMarshaller = intColumnsMarshallerWithComments . fmap (\c -> (c, Nothing))++intColumnsMarshallerWithComments :: [(String, Maybe String)] -> Orville.SqlMarshaller [Int32] [Int32]+intColumnsMarshallerWithComments columns =   let-    field (idx, column) =-      Orville.marshallField (!! idx) $ Orville.integerField column+    field (idx, (column, mbComment)) =+      Orville.marshallField (!! idx)+        . maybe id Orville.setFieldDescription mbComment+        $ Orville.integerField column   in     traverse field (zip [0 ..] columns) 
test/Test/Connection.hs view
@@ -27,6 +27,7 @@     , prop_errorOnSafeNulByte pool     , prop_truncateValuesAtUnsafeNulByte pool     , prop_errorOnInvalidSql pool+    , prop_returningAConnectionToPoolWithOpenTransactionCausesException pool     ]  prop_safeOrUnsafeNonNullBytes :: Property.NamedDBProperty@@ -137,4 +138,21 @@         Conn.sqlExecutionErrorSqlState err === Just syntaxErrorState       Right _ -> do         HH.footnote "Expected 'executeRow' to return failure, but it did not"+        HH.failure++prop_returningAConnectionToPoolWithOpenTransactionCausesException :: Property.NamedDBProperty+prop_returningAConnectionToPoolWithOpenTransactionCausesException =+  Property.singletonNamedDBProperty "returning a connection to the pool with an open transaction causes an exception" $ \pool -> do+    result <-+      MIO.liftIO . E.try . Conn.withPoolConnection pool $ \connection ->+        Conn.executeRaw+          connection+          (B8.pack "BEGIN TRANSACTION")+          []++    case result of+      Left (_err :: Conn.ConnectionError) ->+        pure ()+      Right _ -> do+        HH.footnote "Expected 'withPoolConnection' to return failure, but it did not"         HH.failure
test/Test/Entities/Bar.hs view
@@ -1,5 +1,6 @@ module Test.Entities.Bar   ( Bar (..)+  , BarId   , table   , generate   , generateList@@ -49,7 +50,8 @@  barNameField :: Orville.FieldDefinition Orville.NotNull BarName barNameField =-  Orville.unboundedTextField "name"+  Orville.setDefaultValue (Orville.textDefault $ T.pack "default") $+    Orville.unboundedTextField "name"  generate :: HH.Gen BarWrite generate =
test/Test/Entities/Foo.hs view
@@ -1,13 +1,18 @@+{-# LANGUAGE CPP #-}+ module Test.Entities.Foo   ( Foo (..)   , FooId   , FooName+  , FooAge   , table   , generate   , generateFooWithName+  , generateFooWithNameAndAge   , generateFooWithId   , generateFooId   , generateFooName+  , generateFooAge   , generateList   , generateListUsing   , generateNonEmpty@@ -16,10 +21,16 @@   , fooNameField   , fooAgeField   , hasName+  , hasAge   , averageFooAge+  , nameAndAgeMarshaller   ) where +#if MIN_VERSION_base(4,18,0)+#else+import Control.Applicative (liftA2)+#endif import Control.Monad.IO.Class (MonadIO (liftIO)) import Data.Function (on) import Data.Int (Int32)@@ -90,6 +101,13 @@     <*> pure name     <*> generateFooAge +generateFooWithNameAndAge :: FooName -> FooAge -> HH.Gen Foo+generateFooWithNameAndAge name age =+  Foo+    <$> generateFooId+    <*> pure name+    <*> pure age+ generateFooId :: HH.Gen FooId generateFooId =   PgGen.pgInt32@@ -116,6 +134,10 @@ hasName name foo =   fooName foo == name +hasAge :: FooAge -> Foo -> Bool+hasAge age foo =+  fooAge foo == age+ generateList :: HH.Range Int -> HH.Gen [Foo] generateList =   flip generateListUsing generate@@ -138,3 +160,10 @@     Conn.withPoolConnection pool $ \connection ->       TestTable.dropAndRecreateTableDef connection table     Orville.runOrville pool operation++nameAndAgeMarshaller :: Orville.SqlMarshaller (FooName, FooAge) (FooName, FooAge)+nameAndAgeMarshaller =+  liftA2+    (,)+    (Orville.marshallField fst fooNameField)+    (Orville.marshallField snd fooAgeField)
+ test/Test/Expr/Aggregate.hs view
@@ -0,0 +1,163 @@+module Test.Expr.Aggregate+  ( aggregateTests+  )+where++import qualified Control.Monad.IO.Class as MIO+import qualified Data.ByteString.Char8 as B8+import qualified Data.Int as Int+import qualified Data.List.NonEmpty as NE+import qualified Data.Text as T+import qualified Hedgehog as HH++import qualified Orville.PostgreSQL as Orville+import qualified Orville.PostgreSQL.Execution as Execution+import qualified Orville.PostgreSQL.Expr as Expr+import qualified Orville.PostgreSQL.Raw.Connection as Conn+import qualified Orville.PostgreSQL.Raw.RawSql as RawSql+import qualified Orville.PostgreSQL.Raw.SqlValue as SqlValue++import qualified Test.Property as Property++data FooBar = FooBar+  { foo :: Int.Int32+  , bar :: String+  }++newtype DoubleRes = DoubleRes+  {doubleRes :: Double}++aggregateTests :: Orville.ConnectionPool -> Property.Group+aggregateTests pool =+  Property.group+    "Expr - Aggregate"+    [ prop_avgAggregate pool+    , prop_maxAggregate pool+    , prop_minAggregate pool+    , prop_sumAggregate pool+    ]++prop_avgAggregate :: Property.NamedDBProperty+prop_avgAggregate =+  aggregateFunctionTest "avgAggregateFunction computes simple average" $+    AggregateTest+      { valuesToInsert = NE.fromList [FooBar 1 "dog", FooBar 2 "dingo", FooBar 3 "dog"]+      , groupByExpectedQueryResults =+          [ DoubleRes 2.00000+          ]+      , aggregateFunctionExpr =+          Expr.avgAggregateFunction+            Nothing+            (Expr.columnReference fooColumn)+            Nothing+            Nothing+      }++prop_maxAggregate :: Property.NamedDBProperty+prop_maxAggregate =+  aggregateFunctionTest "maxAggregateFunction computes maximum" $+    AggregateTest+      { valuesToInsert = NE.fromList [FooBar 1 "dog", FooBar 2 "dingo", FooBar 3 "dog"]+      , groupByExpectedQueryResults =+          [ DoubleRes 3.0+          ]+      , aggregateFunctionExpr =+          Expr.maxAggregateFunction+            Nothing+            (Expr.columnReference fooColumn)+            Nothing+            Nothing+      }++prop_minAggregate :: Property.NamedDBProperty+prop_minAggregate =+  aggregateFunctionTest "minAggregateFunction computes minimum" $+    AggregateTest+      { valuesToInsert = NE.fromList [FooBar 1 "dog", FooBar 2 "dingo", FooBar 3 "dog"]+      , groupByExpectedQueryResults =+          [ DoubleRes 1.0+          ]+      , aggregateFunctionExpr =+          Expr.minAggregateFunction+            Nothing+            (Expr.columnReference fooColumn)+            Nothing+            Nothing+      }++prop_sumAggregate :: Property.NamedDBProperty+prop_sumAggregate =+  aggregateFunctionTest "sumAggregateFunction computes simple sum" $+    AggregateTest+      { valuesToInsert = NE.fromList [FooBar 1 "dog", FooBar 2 "dingo", FooBar 3 "dog"]+      , groupByExpectedQueryResults =+          [ DoubleRes 6.0+          ]+      , aggregateFunctionExpr =+          Expr.sumAggregateFunction+            Nothing+            (Expr.columnReference fooColumn)+            Nothing+            Nothing+      }++data AggregateTest = AggregateTest+  { valuesToInsert :: NE.NonEmpty FooBar+  , aggregateFunctionExpr :: Expr.ValueExpression+  , groupByExpectedQueryResults :: [DoubleRes]+  }++aggregateFunctionTest :: String -> AggregateTest -> Property.NamedDBProperty+aggregateFunctionTest testName test =+  Property.singletonNamedDBProperty testName $ \pool -> do+    rows <- MIO.liftIO . Conn.withPoolConnection pool $ \connection -> do+      dropAndRecreateTestTable connection++      RawSql.executeVoid connection $+        Expr.insertExpr testTable Nothing (mkAggregateTestInsertSource test) Nothing Nothing++      result <-+        RawSql.execute connection $+          Expr.queryExpr+            (Expr.selectClause $ Expr.selectExpr Nothing)+            (Expr.selectDerivedColumns . pure $ Expr.deriveColumnAs (aggregateFunctionExpr test) (RawSql.unsafeFromRawSql $ RawSql.fromString "agg"))+            (Just $ Expr.tableExpr (Expr.singleTableReferenceList testTable) Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing)++      Execution.readRows result++    ((fmap . fmap . fmap) SqlValue.toDouble rows) HH.=== ((fmap . fmap . fmap) SqlValue.toDouble $ mkAggregateTestExpectedRows test)++mkAggregateTestInsertSource :: AggregateTest -> Expr.InsertSource+mkAggregateTestInsertSource test =+  let+    mkRow foobar =+      NE.fromList+        [ Expr.valueExpression $ SqlValue.fromInt32 (foo foobar)+        , Expr.valueExpression $ SqlValue.fromText (T.pack $ bar foobar)+        ]+  in+    Expr.valuesExprInsertSource+      . Expr.valuesExprFromValueExpressions+      $ fmap mkRow (valuesToInsert test)++mkAggregateTestExpectedRows :: AggregateTest -> [[(Maybe B8.ByteString, SqlValue.SqlValue)]]+mkAggregateTestExpectedRows test =+  let+    mkRow res =+      [ (Just (B8.pack "agg"), SqlValue.fromDouble (doubleRes res))+      ]+  in+    fmap mkRow (groupByExpectedQueryResults test)++testTable :: Expr.QualifiedOrUnqualified Expr.TableName+testTable =+  Expr.unqualified (Expr.tableName "expr_test")++fooColumn :: Expr.QualifiedOrUnqualified Expr.ColumnName+fooColumn =+  Expr.unqualified (Expr.columnName "foo")++dropAndRecreateTestTable :: Orville.Connection -> IO ()+dropAndRecreateTestTable connection = do+  RawSql.executeVoid connection (RawSql.fromString "DROP TABLE IF EXISTS " <> RawSql.toRawSql testTable)+  RawSql.executeVoid connection (RawSql.fromString "CREATE TABLE " <> RawSql.toRawSql testTable <> RawSql.fromString "(foo INTEGER, bar TEXT)")
+ test/Test/Expr/ConditionalExpr.hs view
@@ -0,0 +1,75 @@+module Test.Expr.ConditionalExpr+  ( conditionalTests+  )+where++import qualified Data.ByteString.Char8 as B8+import qualified Data.List.NonEmpty as NE+import GHC.Stack (HasCallStack, withFrozenCallStack)+import qualified Hedgehog as HH++import qualified Orville.PostgreSQL.Expr as Expr+import qualified Orville.PostgreSQL.Raw.RawSql as RawSql+import qualified Orville.PostgreSQL.Raw.SqlValue as SqlValue++import qualified Test.Property as Property++conditionalTests :: Property.Group+conditionalTests =+  Property.group+    "Expr - Conditional"+    [ prop_caseWithNoElse+    , prop_caseWithElse+    , prop_caseMultipleWhenWithElse+    ]++prop_caseWithNoElse :: Property.NamedProperty+prop_caseWithNoElse =+  let+    whenTrueThen1 =+      Expr.whenExpr (Expr.literalBooleanExpr True) (Expr.valueExpression $ SqlValue.fromInt32 1)+    caseExpr = Expr.caseExpr (pure whenTrueThen1) Nothing+  in+    Property.singletonNamedProperty "caseExpr with a trivial WHEN and no ELSE generates expected sql." $+      assertConditionalEquals+        "CASE WHEN TRUE THEN $1 END"+        caseExpr++prop_caseWithElse :: Property.NamedProperty+prop_caseWithElse =+  let+    firstEqualsSecond =+      Expr.equals (Expr.valueExpression $ SqlValue.fromInt32 1) (Expr.valueExpression $ SqlValue.fromInt32 1)+    whenExpr =+      Expr.whenExpr firstEqualsSecond (Expr.valueExpression $ SqlValue.fromInt32 1)+    caseExpr = Expr.caseExpr (pure whenExpr) (Just . Expr.valueExpression $ SqlValue.fromInt32 1)+  in+    Property.singletonNamedProperty "caseExpr with a simple WHEN and simple ELSE generates expected sql." $+      assertConditionalEquals+        "CASE WHEN ($1) = ($2) THEN $3 ELSE $4 END"+        caseExpr++prop_caseMultipleWhenWithElse :: Property.NamedProperty+prop_caseMultipleWhenWithElse =+  let+    firstVal = Expr.valueExpression $ SqlValue.fromInt32 1+    secondVal = Expr.valueExpression $ SqlValue.fromInt32 1+    firstGreaterSecond =+      Expr.greaterThan firstVal secondVal+    firstLessSecond =+      Expr.lessThan firstVal secondVal+    whenGreaterExpr =+      Expr.whenExpr firstGreaterSecond (Expr.valueExpression $ SqlValue.fromInt32 1)+    whenLessExpr =+      Expr.whenExpr firstLessSecond (Expr.valueExpression $ SqlValue.fromInt32 1)+    caseExpr = Expr.caseExpr (whenGreaterExpr NE.:| [whenLessExpr]) (Just . Expr.valueExpression $ SqlValue.fromInt32 1)+  in+    Property.singletonNamedProperty "caseExpr with a simple WHEN and simple ELSE generates expected sql." $+      assertConditionalEquals+        "CASE WHEN ($1) > ($2) THEN $3 WHEN ($4) < ($5) THEN $6 ELSE $7 END"+        caseExpr++assertConditionalEquals :: (HH.MonadTest m, HasCallStack) => String -> Expr.ValueExpression -> m ()+assertConditionalEquals valueExpressionStr valueExpr =+  withFrozenCallStack $+    RawSql.toExampleBytes valueExpr HH.=== B8.pack valueExpressionStr
test/Test/Expr/Count.hs view
@@ -32,7 +32,7 @@         Expr.queryExpr           (Expr.selectClause (Expr.selectExpr Nothing))           ( Expr.selectDerivedColumns-              [ Expr.deriveColumnAs Expr.count1 (Expr.columnName "count")+              [ Expr.deriveColumnAs Expr.count1AggregateFunction (Expr.columnName "count")               ]           )           Nothing@@ -57,11 +57,23 @@           (Expr.selectClause (Expr.selectExpr Nothing))           ( Expr.selectDerivedColumns               [ Expr.deriveColumnAs-                  (Expr.countColumn (Orville.fieldColumnName Foo.fooIdField))+                  (Expr.countColumnAggregateFunction . Expr.unqualified $ Orville.fieldColumnName Foo.fooIdField)                   (Expr.columnName "count")               ]           )-          (Just (Expr.tableExpr (Expr.referencesTable $ Orville.tableName Foo.table) Nothing Nothing Nothing Nothing Nothing))+          ( Just+              ( Expr.tableExpr+                  (Expr.singleTableReferenceList (Orville.tableName Foo.table))+                  Nothing+                  Nothing+                  Nothing+                  Nothing+                  Nothing+                  Nothing+                  Nothing+                  Nothing+              )+          )        marshaller =         Orville.annotateSqlMarshallerEmptyAnnotation $
test/Test/Expr/Cursor.hs view
@@ -7,6 +7,7 @@ import qualified Control.Monad.IO.Class as MIO import qualified Data.ByteString.Char8 as B8 import qualified Data.Int as Int+import qualified Data.List.NonEmpty as NE import Hedgehog ((===)) import qualified Hedgehog as HH @@ -47,7 +48,7 @@ prop_cursorInTransaction =   Property.singletonNamedDBProperty "In transaction" $ \pool -> do     result <--      withFooBarData pool [row 1, row 2] $ \connection ->+      withFooBarData pool (NE.fromList [row 1, row 2]) $ \connection ->         withTestTransaction connection $           withTestCursor connection Nothing Nothing findAllFooBars $ \cursorName -> do             result <- RawSql.execute connection $ Expr.fetch Nothing cursorName@@ -59,7 +60,7 @@ prop_cursorOutsideTransactionWithHold =   Property.singletonNamedDBProperty "Outside transaction (with hold)" $ \pool -> do     result <--      withFooBarData pool [row 1, row 2] $ \connection ->+      withFooBarData pool (NE.fromList [row 1, row 2]) $ \connection ->         withTestCursor connection Nothing (Just Expr.withHold) findAllFooBars $ \cursorName -> do           result <- RawSql.execute connection $ Expr.fetch Nothing cursorName           Execution.readRows result@@ -89,7 +90,7 @@ prop_cursorMove =   Property.singletonNamedDBProperty "Move" $ \pool -> do     result <--      withFooBarData pool [row 1, row 2, row 3] $ \connection ->+      withFooBarData pool (NE.fromList [row 1, row 2, row 3]) $ \connection ->         withTestCursor connection Nothing (Just Expr.withHold) findAllFooBars $ \cursorName -> do           RawSql.executeVoid connection $ Expr.move (Just $ Expr.rowCount 2) cursorName           result <- RawSql.execute connection $ Expr.fetch Nothing cursorName@@ -101,7 +102,7 @@ prop_cursorNoScroll =   Property.singletonNamedDBProperty "Move" $ \pool -> do     scrollBackResult <--      withFooBarData pool [row 1, row 2] $ \connection ->+      withFooBarData pool (NE.fromList [row 1, row 2]) $ \connection ->         withTestCursor connection (Just Expr.noScroll) (Just Expr.withHold) findAllFooBars $ \cursorName -> do           RawSql.executeVoid connection $ Expr.move (Just Expr.next) cursorName           ExSafe.try $ RawSql.executeVoid connection $ Expr.move (Just Expr.prior) cursorName@@ -122,7 +123,7 @@       runFetchDirectionsOnData         pool         Nothing-        [row 1, row 2]+        (NE.fromList [row 1, row 2])         [Expr.fetchAll]      assertEqualFooBarRows first [row 1, row 2]@@ -134,7 +135,7 @@       runFetchDirectionsOnData         pool         Nothing-        [row 1, row 2, row 3]+        (NE.fromList [row 1, row 2, row 3])         [Expr.rowCount 2, Expr.rowCount 2]      assertEqualFooBarRows first [row 1, row 2]@@ -147,7 +148,7 @@       runFetchDirectionsOnData         pool         Nothing-        [row 1, row 2]+        (NE.fromList [row 1, row 2])         [Expr.forward, Expr.forward]      assertEqualFooBarRows first [row 1]@@ -160,7 +161,7 @@       runFetchDirectionsOnData         pool         Nothing-        [row 1, row 2, row 3]+        (NE.fromList [row 1, row 2, row 3])         [Expr.forwardCount 2, Expr.forwardCount 2]      assertEqualFooBarRows first [row 1, row 2]@@ -173,7 +174,7 @@       runFetchDirectionsOnData         pool         Nothing-        [row 1, row 2]+        (NE.fromList [row 1, row 2])         [Expr.forwardAll]      assertEqualFooBarRows first [row 1, row 2]@@ -185,7 +186,7 @@       runFetchDirectionsOnData         pool         (Just Expr.scroll)-        [row 1, row 2]+        (NE.fromList [row 1, row 2])         [Expr.forwardCount 2, Expr.backward]      assertEqualFooBarRows first [row 1, row 2]@@ -198,7 +199,7 @@       runFetchDirectionsOnData         pool         (Just Expr.scroll)-        [row 1, row 2, row 3]+        (NE.fromList [row 1, row 2, row 3])         [Expr.forwardCount 3, Expr.backwardCount 2]      assertEqualFooBarRows first [row 1, row 2, row 3]@@ -211,7 +212,7 @@       runFetchDirectionsOnData         pool         (Just Expr.scroll)-        [row 1, row 2, row 3]+        (NE.fromList [row 1, row 2, row 3])         [Expr.forwardCount 4, Expr.backwardAll]      assertEqualFooBarRows first [row 1, row 2, row 3]@@ -224,7 +225,7 @@       runFetchDirectionsOnData         pool         Nothing-        [row 1, row 2, row 3]+        (NE.fromList [row 1, row 2, row 3])         [Expr.first, Expr.last]      assertEqualFooBarRows first [row 1]@@ -237,7 +238,7 @@       runFetchDirectionsOnData         pool         (Just Expr.scroll)-        [row 1, row 2, row 3]+        (NE.fromList [row 1, row 2, row 3])         [Expr.next, Expr.next, Expr.prior]      assertEqualFooBarRows first [row 1]@@ -251,7 +252,7 @@       runFetchDirectionsOnData         pool         (Just Expr.scroll)-        [row 1, row 2, row 3]+        (NE.fromList [row 1, row 2, row 3])         [Expr.absolute 3, Expr.absolute (-1)]      assertEqualFooBarRows first [row 3]@@ -264,7 +265,7 @@       runFetchDirectionsOnData         pool         (Just Expr.scroll)-        [row 1, row 2, row 3]+        (NE.fromList [row 1, row 2, row 3])         [Expr.relative 1, Expr.relative 2, Expr.relative (-1), Expr.relative 0]      assertEqualFooBarRows first [row 1]@@ -278,7 +279,7 @@ runFetchDirectionsOnData ::   Orville.ConnectionPool ->   Maybe Expr.ScrollExpr ->-  [FooBar] ->+  NE.NonEmpty FooBar ->   [Expr.CursorDirection] ->   HH.PropertyT IO [[[(Maybe B8.ByteString, SqlValue.SqlValue)]]] runFetchDirectionsOnData pool scroll fooBars directions =
test/Test/Expr/GroupBy.hs view
@@ -6,7 +6,7 @@ import qualified Control.Monad.IO.Class as MIO import qualified Data.ByteString.Char8 as B8 import qualified Data.Int as Int-import Data.List.NonEmpty (NonEmpty ((:|)))+import qualified Data.List.NonEmpty as NE import qualified Data.Text as T  import qualified Orville.PostgreSQL as Orville@@ -36,19 +36,19 @@ prop_groupByColumnsExpr =   groupByTest "groupByColumnsExpr groups by columns" $     GroupByTest-      { groupByValuesToInsert = [FooBar 1 "dog", FooBar 2 "dingo", FooBar 3 "dog"]+      { groupByValuesToInsert = NE.fromList [FooBar 1 "dog", FooBar 2 "dingo", FooBar 3 "dog"]       , groupByExpectedQueryResults = [FooBar 3 "dog", FooBar 2 "dingo", FooBar 1 "dog"]       , groupByClause =           Just . Expr.groupByClause $             Expr.groupByColumnsExpr $-              barColumn :| [fooColumn]+              barColumn NE.:| [fooColumn]       }  prop_appendGroupByExpr :: Property.NamedDBProperty prop_appendGroupByExpr =   groupByTest "appendGroupByExpr causes grouping on both clauses" $     GroupByTest-      { groupByValuesToInsert = [FooBar 1 "dog", FooBar 2 "dingo", FooBar 1 "dog", FooBar 3 "dingo", FooBar 1 "dog", FooBar 2 "dingo"]+      { groupByValuesToInsert = NE.fromList [FooBar 1 "dog", FooBar 2 "dingo", FooBar 1 "dog", FooBar 3 "dingo", FooBar 1 "dog", FooBar 2 "dingo"]       , groupByExpectedQueryResults = [FooBar 2 "dingo", FooBar 1 "dog", FooBar 3 "dingo"]       , groupByClause =           Just . Expr.groupByClause $@@ -58,7 +58,7 @@       }  data GroupByTest = GroupByTest-  { groupByValuesToInsert :: [FooBar]+  { groupByValuesToInsert :: NE.NonEmpty FooBar   , groupByClause :: Maybe Expr.GroupByClause   , groupByExpectedQueryResults :: [FooBar]   }@@ -67,11 +67,15 @@ mkGroupByTestInsertSource test =   let     mkRow foobar =-      [ SqlValue.fromInt32 (foo foobar)-      , SqlValue.fromText (T.pack $ bar foobar)-      ]+      NE.fromList+        [ Expr.valueExpression $ SqlValue.fromInt32 (foo foobar)+        , Expr.valueExpression $ SqlValue.fromText (T.pack $ bar foobar)+        ]   in-    Expr.insertSqlValues (map mkRow $ groupByValuesToInsert test)+    Expr.valuesExprInsertSource+      . Expr.valuesExprFromValueExpressions+      . fmap mkRow+      $ (groupByValuesToInsert test)  mkGroupByTestExpectedRows :: GroupByTest -> [[(Maybe B8.ByteString, SqlValue.SqlValue)]] mkGroupByTestExpectedRows test =@@ -90,30 +94,30 @@       dropAndRecreateTestTable connection        RawSql.executeVoid connection $-        Expr.insertExpr testTable Nothing (mkGroupByTestInsertSource test) Nothing+        Expr.insertExpr testTable Nothing (mkGroupByTestInsertSource test) Nothing Nothing        result <-         RawSql.execute connection $           Expr.queryExpr             (Expr.selectClause $ Expr.selectExpr Nothing)             (Expr.selectColumns [fooColumn, barColumn])-            (Just $ Expr.tableExpr (Expr.referencesTable testTable) Nothing (groupByClause test) Nothing Nothing Nothing)+            (Just $ Expr.tableExpr (Expr.singleTableReferenceList testTable) Nothing (groupByClause test) Nothing Nothing Nothing Nothing Nothing Nothing)        Execution.readRows result      rows `assertEqualSqlRows` mkGroupByTestExpectedRows test -testTable :: Expr.Qualified Expr.TableName+testTable :: Expr.QualifiedOrUnqualified Expr.TableName testTable =-  Expr.qualifyTable Nothing (Expr.tableName "expr_test")+  Expr.unqualified (Expr.tableName "expr_test") -fooColumn :: Expr.ColumnName+fooColumn :: Expr.QualifiedOrUnqualified Expr.ColumnName fooColumn =-  Expr.columnName "foo"+  Expr.unqualified (Expr.columnName "foo") -barColumn :: Expr.ColumnName+barColumn :: Expr.QualifiedOrUnqualified Expr.ColumnName barColumn =-  Expr.columnName "bar"+  Expr.unqualified (Expr.columnName "bar")  dropAndRecreateTestTable :: Orville.Connection -> IO () dropAndRecreateTestTable connection = do
test/Test/Expr/GroupByOrderBy.hs view
@@ -6,6 +6,7 @@ import qualified Control.Monad.IO.Class as MIO import qualified Data.ByteString.Char8 as B8 import qualified Data.Int as Int+import qualified Data.List.NonEmpty as NE import qualified Data.Text as T  import qualified Orville.PostgreSQL as Orville@@ -34,7 +35,7 @@ prop_groupByOrderByExpr =   groupByOrderByTest "GroupBy and OrderBy clauses can be used together" $     GroupByOrderByTest-      { valuesToInsert = [FooBar 1 "shiba", FooBar 2 "dingo", FooBar 1 "dog", FooBar 2 "dingo", FooBar 1 "shiba"]+      { valuesToInsert = NE.fromList [FooBar 1 "shiba", FooBar 2 "dingo", FooBar 1 "dog", FooBar 2 "dingo", FooBar 1 "shiba"]       , expectedQueryResults = [FooBar 2 "dingo", FooBar 1 "dog", FooBar 1 "shiba"]       , groupByClause =           Just . Expr.groupByClause $@@ -47,7 +48,7 @@       }  data GroupByOrderByTest = GroupByOrderByTest-  { valuesToInsert :: [FooBar]+  { valuesToInsert :: NE.NonEmpty FooBar   , groupByClause :: Maybe Expr.GroupByClause   , orderByClause :: Maybe Expr.OrderByClause   , expectedQueryResults :: [FooBar]@@ -57,11 +58,15 @@ mkGroupByOrderByTestInsertSource test =   let     mkRow foobar =-      [ SqlValue.fromInt32 (foo foobar)-      , SqlValue.fromText (T.pack $ bar foobar)-      ]+      NE.fromList+        [ Expr.valueExpression $ SqlValue.fromInt32 (foo foobar)+        , Expr.valueExpression $ SqlValue.fromText (T.pack $ bar foobar)+        ]   in-    Expr.insertSqlValues (map mkRow $ valuesToInsert test)+    Expr.valuesExprInsertSource+      . Expr.valuesExprFromValueExpressions+      . fmap mkRow+      $ valuesToInsert test  mkGroupByOrderByTestExpectedRows :: GroupByOrderByTest -> [[(Maybe B8.ByteString, SqlValue.SqlValue)]] mkGroupByOrderByTestExpectedRows test =@@ -80,30 +85,30 @@       dropAndRecreateTestTable connection        RawSql.executeVoid connection $-        Expr.insertExpr testTable Nothing (mkGroupByOrderByTestInsertSource test) Nothing+        Expr.insertExpr testTable Nothing (mkGroupByOrderByTestInsertSource test) Nothing Nothing        result <-         RawSql.execute connection $           Expr.queryExpr             (Expr.selectClause $ Expr.selectExpr Nothing)             (Expr.selectColumns [fooColumn, barColumn])-            (Just $ Expr.tableExpr (Expr.referencesTable testTable) Nothing (groupByClause test) (orderByClause test) Nothing Nothing)+            (Just $ Expr.tableExpr (Expr.singleTableReferenceList testTable) Nothing (groupByClause test) (orderByClause test) Nothing Nothing Nothing Nothing Nothing)        Execution.readRows result      rows `assertEqualSqlRows` mkGroupByOrderByTestExpectedRows test -testTable :: Expr.Qualified Expr.TableName+testTable :: Expr.QualifiedOrUnqualified Expr.TableName testTable =-  Expr.qualifyTable Nothing (Expr.tableName "expr_test")+  Expr.unqualified (Expr.tableName "expr_test") -fooColumn :: Expr.ColumnName+fooColumn :: Expr.QualifiedOrUnqualified Expr.ColumnName fooColumn =-  Expr.columnName "foo"+  Expr.unqualified (Expr.columnName "foo") -barColumn :: Expr.ColumnName+barColumn :: Expr.QualifiedOrUnqualified Expr.ColumnName barColumn =-  Expr.columnName "bar"+  Expr.unqualified (Expr.columnName "bar")  dropAndRecreateTestTable :: Orville.Connection -> IO () dropAndRecreateTestTable connection = do
test/Test/Expr/InsertUpdateDelete.hs view
@@ -4,7 +4,7 @@ where  import qualified Control.Monad.IO.Class as MIO-import Data.List.NonEmpty (NonEmpty ((:|)))+import qualified Data.List.NonEmpty as NE import qualified Data.Text as T  import qualified Orville.PostgreSQL as Orville@@ -14,14 +14,15 @@ import qualified Orville.PostgreSQL.Raw.RawSql as RawSql import qualified Orville.PostgreSQL.Raw.SqlValue as SqlValue -import Test.Expr.TestSchema (assertEqualFooBarRows, barColumn, barColumnRef, dropAndRecreateTestTable, findAllFooBars, fooBarTable, fooColumn, insertFooBarSource, mkFooBar)+import Test.Expr.TestSchema (assertEqualFooBarRows, barColumn, barColumnRef, dropAndRecreateTestTable, findAllFooBars, findAllFooBarsInTable, fooBarTable, fooColumn, insertFooBarSource, mkFooBar) import qualified Test.Property as Property  insertUpdateDeleteTests :: Orville.ConnectionPool -> Property.Group insertUpdateDeleteTests pool =   Property.group-    "Expr - Insert/Update/Delete"+    "Expr - Insert/Update/Delete/Truncate"     [ prop_insertExpr pool+    , prop_insertExprWithOnConflictDoNothing pool     , prop_insertExprWithReturning pool     , prop_updateExpr pool     , prop_updateExprWithWhere pool@@ -29,13 +30,16 @@     , prop_deleteExpr pool     , prop_deleteExprWithWhere pool     , prop_deleteExprWithReturning pool+    , prop_truncateTablesExpr pool+    , prop_insertExprWithOnConflictDoUpdate pool+    , prop_updateTableRefList pool     ]  prop_insertExpr :: Property.NamedDBProperty prop_insertExpr =   Property.singletonNamedDBProperty "insertExpr inserts values" $ \pool -> do     let-      fooBars = [mkFooBar 1 "dog", mkFooBar 2 "cat"]+      fooBars = NE.fromList [mkFooBar 1 "dog", mkFooBar 2 "cat"]      rows <-       MIO.liftIO $@@ -43,19 +47,75 @@           dropAndRecreateTestTable connection            RawSql.executeVoid connection $-            Expr.insertExpr fooBarTable Nothing (insertFooBarSource fooBars) Nothing+            Expr.insertExpr fooBarTable Nothing (insertFooBarSource fooBars) Nothing Nothing            result <- RawSql.execute connection findAllFooBars            Execution.readRows result -    assertEqualFooBarRows rows fooBars+    assertEqualFooBarRows rows (NE.toList fooBars) +prop_insertExprWithOnConflictDoNothing :: Property.NamedDBProperty+prop_insertExprWithOnConflictDoNothing =+  Property.singletonNamedDBProperty "insertExpr with on conflict do nothing does not modify table" $ \pool -> do+    let+      fooBars = NE.fromList [mkFooBar 1 "dog", mkFooBar 2 "cat"]+      addIndex = RawSql.fromString "CREATE UNIQUE INDEX ON " <> RawSql.toRawSql fooBarTable <> RawSql.fromString "( foo )"++    rows <-+      MIO.liftIO $+        Conn.withPoolConnection pool $ \connection -> do+          dropAndRecreateTestTable connection+          RawSql.executeVoid connection addIndex++          RawSql.executeVoid connection $+            Expr.insertExpr fooBarTable Nothing (insertFooBarSource fooBars) Nothing Nothing++          RawSql.executeVoid connection $+            Expr.insertExpr fooBarTable Nothing (insertFooBarSource fooBars) (Just Expr.onConflictDoNothing) Nothing++          result <- RawSql.execute connection findAllFooBars++          Execution.readRows result++    assertEqualFooBarRows rows (NE.toList fooBars)++prop_insertExprWithOnConflictDoUpdate :: Property.NamedDBProperty+prop_insertExprWithOnConflictDoUpdate =+  Property.singletonNamedDBProperty "insertExpr with on conflict do update updates conflicting row" $ \pool -> do+    let+      fooBars0 = pure $ mkFooBar 1 "dog"+      fooBars1 = pure $ mkFooBar 1 "eagel"+      addIndex = RawSql.fromString "CREATE UNIQUE INDEX ON " <> RawSql.toRawSql fooBarTable <> RawSql.fromString "( foo )"+      fooColumnName = Expr.unqualified (Expr.columnName "foo")+      barColumnName = Expr.unqualified (Expr.columnName "bar")+      conflictTarget = Expr.conflictTargetForIndexColumn fooColumnName Nothing+      setExcludedColumn = Expr.setColumnNameExcluded barColumnName+      onConflictDoUpdate = Expr.onConflictDoUpdate (Just conflictTarget) (pure setExcludedColumn) Nothing++    rows <-+      MIO.liftIO $+        Conn.withPoolConnection pool $ \connection -> do+          dropAndRecreateTestTable connection+          RawSql.executeVoid connection addIndex++          RawSql.executeVoid connection $+            Expr.insertExpr fooBarTable Nothing (insertFooBarSource fooBars0) (Just onConflictDoUpdate) Nothing++          RawSql.executeVoid connection $+            Expr.insertExpr fooBarTable Nothing (insertFooBarSource fooBars1) (Just onConflictDoUpdate) Nothing++          result <- RawSql.execute connection findAllFooBars++          Execution.readRows result++    assertEqualFooBarRows rows (NE.toList fooBars1)+ prop_insertExprWithReturning :: Property.NamedDBProperty prop_insertExprWithReturning =   Property.singletonNamedDBProperty "insertExpr with returning clause returns the requested columns" $ \pool -> do     let-      fooBars = [mkFooBar 1 "dog", mkFooBar 2 "cat"]+      fooBars = NE.fromList [mkFooBar 1 "dog", mkFooBar 2 "cat"]      rows <-       MIO.liftIO $@@ -68,25 +128,28 @@                 fooBarTable                 Nothing                 (insertFooBarSource fooBars)+                Nothing                 (Just $ Expr.returningExpr $ Expr.selectColumns [fooColumn, barColumn])            Execution.readRows result -    assertEqualFooBarRows rows fooBars+    assertEqualFooBarRows rows (NE.toList fooBars)  prop_updateExpr :: Property.NamedDBProperty prop_updateExpr =   Property.singletonNamedDBProperty "updateExpr updates rows in the db" $ \pool -> do     let-      oldFooBars = [mkFooBar 1 "dog", mkFooBar 2 "cat"]-      newFooBars = [mkFooBar 1 "ferret", mkFooBar 2 "ferret"]+      oldFooBars = NE.fromList [mkFooBar 1 "dog", mkFooBar 2 "cat"]+      newFooBars = NE.fromList [mkFooBar 1 "ferret", mkFooBar 2 "ferret"]        setBarToFerret =         Expr.updateExpr           fooBarTable-          (Expr.setClauseList (Expr.setColumn barColumn (SqlValue.fromText (T.pack "ferret")) :| []))           Nothing+          (Expr.setClauseList (pure $ Expr.setColumn barColumn (SqlValue.fromText (T.pack "ferret"))))           Nothing+          Nothing+          Nothing      rows <-       MIO.liftIO $@@ -94,7 +157,7 @@           dropAndRecreateTestTable connection            RawSql.executeVoid connection $-            Expr.insertExpr fooBarTable Nothing (insertFooBarSource oldFooBars) Nothing+            Expr.insertExpr fooBarTable Nothing (insertFooBarSource oldFooBars) Nothing Nothing            RawSql.executeVoid connection setBarToFerret @@ -102,19 +165,21 @@            Execution.readRows result -    assertEqualFooBarRows rows newFooBars+    assertEqualFooBarRows rows (NE.toList newFooBars)  prop_updateExprWithWhere :: Property.NamedDBProperty prop_updateExprWithWhere =   Property.singletonNamedDBProperty "updateExpr uses a where clause when given" $ \pool -> do     let-      oldFooBars = [mkFooBar 1 "dog", mkFooBar 2 "cat"]-      newFooBars = [mkFooBar 1 "ferret", mkFooBar 2 "cat"]+      oldFooBars = NE.fromList [mkFooBar 1 "dog", mkFooBar 2 "cat"]+      newFooBars = NE.fromList [mkFooBar 1 "ferret", mkFooBar 2 "cat"]        updateDogToForret =         Expr.updateExpr           fooBarTable-          (Expr.setClauseList (Expr.setColumn barColumn (SqlValue.fromText (T.pack "ferret")) :| []))+          Nothing+          (Expr.setClauseList (pure $ Expr.setColumn barColumn (SqlValue.fromText (T.pack "ferret"))))+          Nothing           (Just (Expr.whereClause (Expr.equals barColumnRef (Expr.valueExpression (SqlValue.fromText (T.pack "dog"))))))           Nothing @@ -124,7 +189,7 @@           dropAndRecreateTestTable connection            RawSql.executeVoid connection $-            Expr.insertExpr fooBarTable Nothing (insertFooBarSource oldFooBars) Nothing+            Expr.insertExpr fooBarTable Nothing (insertFooBarSource oldFooBars) Nothing Nothing            RawSql.executeVoid connection updateDogToForret @@ -132,20 +197,22 @@            Execution.readRows result -    assertEqualFooBarRows rows newFooBars+    assertEqualFooBarRows rows (NE.toList newFooBars)  prop_updateExprWithReturning :: Property.NamedDBProperty prop_updateExprWithReturning =   Property.singletonNamedDBProperty "updateExpr with returning clause returns the new records" $ \pool -> do     let-      oldFooBars = [mkFooBar 1 "dog", mkFooBar 2 "cat"]-      newFooBars = [mkFooBar 1 "ferret", mkFooBar 2 "ferret"]+      oldFooBars = NE.fromList [mkFooBar 1 "dog", mkFooBar 2 "cat"]+      newFooBars = NE.fromList [mkFooBar 1 "ferret", mkFooBar 2 "ferret"]        setBarToFerret =         Expr.updateExpr           fooBarTable-          (Expr.setClauseList (Expr.setColumn barColumn (SqlValue.fromText (T.pack "ferret")) :| []))           Nothing+          (Expr.setClauseList (pure $ Expr.setColumn barColumn (SqlValue.fromText (T.pack "ferret"))))+          Nothing+          Nothing           (Just $ Expr.returningExpr $ Expr.selectColumns [fooColumn, barColumn])      rows <-@@ -154,19 +221,19 @@           dropAndRecreateTestTable connection            RawSql.executeVoid connection $-            Expr.insertExpr fooBarTable Nothing (insertFooBarSource oldFooBars) Nothing+            Expr.insertExpr fooBarTable Nothing (insertFooBarSource oldFooBars) Nothing Nothing            result <- RawSql.execute connection setBarToFerret            Execution.readRows result -    assertEqualFooBarRows rows newFooBars+    assertEqualFooBarRows rows (NE.toList newFooBars)  prop_deleteExpr :: Property.NamedDBProperty prop_deleteExpr =   Property.singletonNamedDBProperty "deleteExpr deletes rows in the db" $ \pool -> do     let-      oldFooBars = [mkFooBar 1 "dog", mkFooBar 2 "cat"]+      oldFooBars = NE.fromList [mkFooBar 1 "dog", mkFooBar 2 "cat"]        deleteRows =         Expr.deleteExpr@@ -180,7 +247,7 @@           dropAndRecreateTestTable connection            RawSql.executeVoid connection $-            Expr.insertExpr fooBarTable Nothing (insertFooBarSource oldFooBars) Nothing+            Expr.insertExpr fooBarTable Nothing (insertFooBarSource oldFooBars) Nothing Nothing            RawSql.executeVoid connection deleteRows @@ -194,8 +261,8 @@ prop_deleteExprWithWhere =   Property.singletonNamedDBProperty "deleteExpr uses a where clause when given" $ \pool -> do     let-      oldFooBars = [mkFooBar 1 "dog", mkFooBar 2 "cat"]-      newFooBars = [mkFooBar 2 "cat"]+      oldFooBars = NE.fromList [mkFooBar 1 "dog", mkFooBar 2 "cat"]+      newFooBars = pure $ mkFooBar 2 "cat"        deleteDogs =         Expr.deleteExpr@@ -209,7 +276,7 @@           dropAndRecreateTestTable connection            RawSql.executeVoid connection $-            Expr.insertExpr fooBarTable Nothing (insertFooBarSource oldFooBars) Nothing+            Expr.insertExpr fooBarTable Nothing (insertFooBarSource oldFooBars) Nothing Nothing            RawSql.executeVoid connection deleteDogs @@ -217,13 +284,13 @@            Execution.readRows result -    assertEqualFooBarRows rows newFooBars+    assertEqualFooBarRows rows (NE.toList newFooBars)  prop_deleteExprWithReturning :: Property.NamedDBProperty prop_deleteExprWithReturning =   Property.singletonNamedDBProperty "deleteExpr with returning returns the original rows" $ \pool -> do     let-      oldFooBars = [mkFooBar 1 "dog", mkFooBar 2 "cat"]+      oldFooBars = NE.fromList [mkFooBar 1 "dog", mkFooBar 2 "cat"]        deleteDogs =         Expr.deleteExpr@@ -237,10 +304,115 @@           dropAndRecreateTestTable connection            RawSql.executeVoid connection $-            Expr.insertExpr fooBarTable Nothing (insertFooBarSource oldFooBars) Nothing+            Expr.insertExpr fooBarTable Nothing (insertFooBarSource oldFooBars) Nothing Nothing            result <- RawSql.execute connection deleteDogs            Execution.readRows result -    assertEqualFooBarRows rows oldFooBars+    assertEqualFooBarRows rows (NE.toList oldFooBars)++prop_truncateTablesExpr :: Property.NamedDBProperty+prop_truncateTablesExpr =+  Property.singletonNamedDBProperty "truncateTablesExpr clears out inserted values from all tables" $ \pool -> do+    let+      fooBars = NE.fromList [mkFooBar 1 "dog", mkFooBar 2 "cat"]+      fooBar2Table =+        Expr.unqualified (Expr.tableName "foobar2")+      dropAndRecreateSecondTable connection = do+        RawSql.executeVoid connection (RawSql.fromString "DROP TABLE IF EXISTS " <> RawSql.toRawSql fooBar2Table)+        RawSql.executeVoid connection (RawSql.fromString "CREATE TABLE " <> RawSql.toRawSql fooBar2Table <> RawSql.fromString "(foo INTEGER, bar TEXT)")++      recreateAndInsert =+        MIO.liftIO $+          Conn.withPoolConnection pool $ \connection -> do+            dropAndRecreateTestTable connection+            dropAndRecreateSecondTable connection++            RawSql.executeVoid connection $+              Expr.insertExpr fooBarTable Nothing (insertFooBarSource fooBars) Nothing Nothing+            RawSql.executeVoid connection $+              Expr.insertExpr fooBar2Table Nothing (insertFooBarSource fooBars) Nothing Nothing++      truncateTables =+        MIO.liftIO $+          Conn.withPoolConnection pool $ \connection -> do+            RawSql.executeVoid connection $+              Expr.truncateTablesExpr (fooBarTable NE.:| (pure fooBar2Table))++    _ <- recreateAndInsert+    _ <- truncateTables++    fooBarRows <-+      MIO.liftIO $+        Conn.withPoolConnection pool $ \connection -> do+          result <- RawSql.execute connection findAllFooBars++          Execution.readRows result++    fooBar2Rows <-+      MIO.liftIO $+        Conn.withPoolConnection pool $ \connection -> do+          result <- RawSql.execute connection $ findAllFooBarsInTable fooBar2Table++          Execution.readRows result++    assertEqualFooBarRows fooBarRows []+    assertEqualFooBarRows fooBar2Rows []++prop_updateTableRefList :: Property.NamedDBProperty+prop_updateTableRefList =+  Property.singletonNamedDBProperty "updateExpr includes a tableReferenceList to allow where condition to reference another table" $ \pool -> do+    let+      fooBars = NE.fromList [mkFooBar 1 "dog", mkFooBar 2 "cat", mkFooBar 3 "ferret"]+      fooBars2 = NE.fromList [mkFooBar 1 "bird", mkFooBar 3 "fish", mkFooBar 4 "snake"]+      expectedFooBars = NE.fromList [mkFooBar 1 "bird", mkFooBar 2 "cat", mkFooBar 3 "fish"]+      fooBar2Table =+        Expr.unqualified (Expr.tableName "foobar2")+      dropAndRecreateSecondTable connection = do+        RawSql.executeVoid connection (RawSql.fromString "DROP TABLE IF EXISTS " <> RawSql.toRawSql fooBar2Table)+        RawSql.executeVoid connection (RawSql.fromString "CREATE TABLE " <> RawSql.toRawSql fooBar2Table <> RawSql.fromString "(foo INTEGER, bar TEXT)")++      fooBar2TableRef = Expr.tableNameReference fooBar2Table (Just (Expr.stringToAliasExpr "f2"))++      setClauseList = RawSql.unsafeSqlExpression "bar = f2.bar"++      whereClause = RawSql.unsafeSqlExpression "WHERE foobar.foo = f2.foo"++      updateExpr =+        Expr.updateExpr+          fooBarTable+          Nothing+          setClauseList+          (Just $ Expr.tableReferenceList [fooBar2TableRef])+          (Just whereClause)+          (Just $ Expr.returningExpr $ Expr.selectColumns [RawSql.unsafeSqlExpression "foobar.foo", RawSql.unsafeSqlExpression "foobar.bar"])++      recreateAndInsert =+        MIO.liftIO $+          Conn.withPoolConnection pool $ \connection -> do+            dropAndRecreateTestTable connection+            dropAndRecreateSecondTable connection++            RawSql.executeVoid connection $+              Expr.insertExpr fooBarTable Nothing (insertFooBarSource fooBars) Nothing Nothing+            RawSql.executeVoid connection $+              Expr.insertExpr fooBar2Table Nothing (insertFooBarSource fooBars2) Nothing Nothing++    _ <- recreateAndInsert++    _ <-+      MIO.liftIO $+        Conn.withPoolConnection pool $ \connection -> do+          result <- RawSql.execute connection updateExpr++          Execution.readRows result++    fooBarRows <-+      MIO.liftIO $+        Conn.withPoolConnection pool $ \connection -> do+          result <- RawSql.execute connection findAllFooBars++          Execution.readRows result++    assertEqualFooBarRows fooBarRows (NE.toList expectedFooBars)
+ test/Test/Expr/Join.hs view
@@ -0,0 +1,78 @@+module Test.Expr.Join+  ( joinTests+  )+where++import qualified Data.ByteString.Char8 as B8+import Data.Function ((&))+import GHC.Stack (HasCallStack, withFrozenCallStack)+import Hedgehog ((===))+import qualified Hedgehog as HH++import qualified Orville.PostgreSQL.Expr as Expr+import qualified Orville.PostgreSQL.Raw.RawSql as RawSql++import qualified Test.Property as Property++joinTests :: Property.Group+joinTests =+  Property.group+    "Expr - Join"+    [ prop_joinedTable+    , prop_join+    , prop_joining+    ]++prop_joinedTable :: Property.NamedProperty+prop_joinedTable =+  Property.singletonNamedProperty "joinedTable produces a table ref that joins two tables together"+    $ assertTableRefEquals+      "\"foo\" LEFT JOIN \"bar\" ON TRUE"+    $ Expr.joinedTable fooTableRef Expr.leftJoinType barTableRef joinOnTrue++prop_join :: Property.NamedProperty+prop_join =+  Property.singletonNamedProperty "join allows 'joinedTable' to be used more naturally" $+    let+      joinedTableRef =+        fooTableRef+          & Expr.join Expr.leftJoinType barTableRef joinOnTrue+          & Expr.join Expr.leftJoinType bazTableRef joinOnTrue+    in+      assertTableRefEquals+        "\"foo\" LEFT JOIN \"bar\" ON TRUE LEFT JOIN \"baz\" ON TRUE"+        joinedTableRef++prop_joining :: Property.NamedProperty+prop_joining =+  Property.singletonNamedProperty "joining allows 'join' to be used without the user needing to use (&)" $+    let+      joinedTableRef =+        Expr.joining+          fooTableRef+          [ Expr.join Expr.leftJoinType barTableRef joinOnTrue+          , Expr.join Expr.leftJoinType bazTableRef joinOnTrue+          ]+    in+      assertTableRefEquals+        "\"foo\" LEFT JOIN \"bar\" ON TRUE LEFT JOIN \"baz\" ON TRUE"+        joinedTableRef++assertTableRefEquals ::+  (HH.MonadTest m, HasCallStack) => String -> Expr.TableReference -> m ()+assertTableRefEquals expectedString tableRef =+  withFrozenCallStack $+    RawSql.toExampleBytes tableRef === B8.pack expectedString++joinOnTrue :: Expr.JoinConstraint+joinOnTrue =+  Expr.joinOnConstraint $ Expr.literalBooleanExpr True++fooTableRef :: Expr.TableReference+fooTableRef = Expr.tableNameReference (Expr.unqualified $ Expr.tableName "foo") Nothing++barTableRef :: Expr.TableReference+barTableRef = Expr.tableNameReference (Expr.unqualified $ Expr.tableName "bar") Nothing++bazTableRef :: Expr.TableReference+bazTableRef = Expr.tableNameReference (Expr.unqualified $ Expr.tableName "baz") Nothing
test/Test/Expr/OrderBy.hs view
@@ -25,14 +25,17 @@     , prop_orderByColumnsExpr pool     , prop_ascendingOrderWithExpr pool     , prop_descendingOrderWithExpr pool+    , prop_distinctOnExpr pool     ]  prop_ascendingExpr :: Property.NamedDBProperty prop_ascendingExpr =   orderByTest "ascendingExpr sorts a text column" $     OrderByTest-      { orderByValuesToInsert = [mkFooBar 1 "dog", mkFooBar 2 "dingo", mkFooBar 3 "dog"]+      { orderByValuesToInsert = NE.fromList [mkFooBar 1 "dog", mkFooBar 2 "dingo", mkFooBar 3 "dog"]       , orderByExpectedQueryResults = [mkFooBar 2 "dingo", mkFooBar 1 "dog", mkFooBar 3 "dog"]+      , orderByDistinctOn =+          Nothing       , orderByClause =           Just . Expr.orderByClause $             Expr.orderByColumnName barColumn Expr.ascendingOrder@@ -42,8 +45,10 @@ prop_descendingExpr =   orderByTest "descendingExpr sorts a text column" $     OrderByTest-      { orderByValuesToInsert = [mkFooBar 1 "dog", mkFooBar 2 "dingo", mkFooBar 3 "dog"]+      { orderByValuesToInsert = NE.fromList [mkFooBar 1 "dog", mkFooBar 2 "dingo", mkFooBar 3 "dog"]       , orderByExpectedQueryResults = [mkFooBar 1 "dog", mkFooBar 3 "dog", mkFooBar 2 "dingo"]+      , orderByDistinctOn =+          Nothing       , orderByClause =           Just . Expr.orderByClause $             Expr.orderByColumnName barColumn Expr.descendingOrder@@ -53,8 +58,10 @@ prop_appendOrderByExpr =   orderByTest "appendOrderByExpr causes ordering on both columns" $     OrderByTest-      { orderByValuesToInsert = [mkFooBar 1 "dog", mkFooBar 2 "dingo", mkFooBar 3 "dog"]+      { orderByValuesToInsert = NE.fromList [mkFooBar 1 "dog", mkFooBar 2 "dingo", mkFooBar 3 "dog"]       , orderByExpectedQueryResults = [mkFooBar 2 "dingo", mkFooBar 3 "dog", mkFooBar 1 "dog"]+      , orderByDistinctOn =+          Nothing       , orderByClause =           Just . Expr.orderByClause $             Expr.appendOrderByExpr@@ -66,8 +73,10 @@ prop_orderByColumnsExpr =   orderByTest "orderByColumnsExpr orders by columns" $     OrderByTest-      { orderByValuesToInsert = [mkFooBar 1 "dog", mkFooBar 2 "dingo", mkFooBar 3 "dog"]+      { orderByValuesToInsert = NE.fromList [mkFooBar 1 "dog", mkFooBar 2 "dingo", mkFooBar 3 "dog"]       , orderByExpectedQueryResults = [mkFooBar 2 "dingo", mkFooBar 3 "dog", mkFooBar 1 "dog"]+      , orderByDistinctOn =+          Nothing       , orderByClause =           Just . Expr.orderByClause $             Expr.orderByColumnsExpr $@@ -80,9 +89,11 @@   orderByTest "ascendingOrderWith sorts columns with nulls first/last" $     OrderByTest       { orderByValuesToInsert =-          [FooBar Nothing Nothing, FooBar (Just 1) Nothing, mkFooBar 2 "dog", FooBar Nothing (Just "dog")]+          NE.fromList [FooBar Nothing Nothing, FooBar (Just 1) Nothing, mkFooBar 2 "dog", FooBar Nothing (Just "dog")]       , orderByExpectedQueryResults =           [FooBar Nothing (Just "dog"), FooBar Nothing Nothing, FooBar (Just 1) Nothing, mkFooBar 2 "dog"]+      , orderByDistinctOn =+          Nothing       , orderByClause =           Just . Expr.orderByClause $             Expr.appendOrderByExpr@@ -95,9 +106,11 @@   orderByTest "descendingOrderWith sorts columns with nulls first/last" $     OrderByTest       { orderByValuesToInsert =-          [FooBar Nothing Nothing, FooBar (Just 1) Nothing, mkFooBar 2 "dog", FooBar Nothing (Just "dog")]+          NE.fromList [FooBar Nothing Nothing, FooBar (Just 1) Nothing, mkFooBar 2 "dog", FooBar Nothing (Just "dog")]       , orderByExpectedQueryResults =           [FooBar Nothing (Just "dog"), FooBar Nothing Nothing, mkFooBar 2 "dog", FooBar (Just 1) Nothing]+      , orderByDistinctOn =+          Nothing       , orderByClause =           Just . Expr.orderByClause $             Expr.appendOrderByExpr@@ -105,9 +118,25 @@               (Expr.orderByColumnName barColumn $ Expr.descendingOrderWith Expr.NullsLast)       } +prop_distinctOnExpr :: Property.NamedDBProperty+prop_distinctOnExpr =+  orderByTest "descendingExpr sorts a text column as expected with distinctOn" $+    OrderByTest+      { orderByValuesToInsert = NE.fromList [mkFooBar 1 "dog", mkFooBar 2 "dingo", mkFooBar 3 "dog"]+      , orderByExpectedQueryResults = [mkFooBar 2 "dingo", mkFooBar 1 "dog"]+      , orderByDistinctOn =+          Just . pure . RawSql.unsafeFromRawSql $ RawSql.toRawSql barColumn+      , orderByClause =+          Just . Expr.orderByClause $+            Expr.appendOrderByExpr+              (Expr.orderByColumnName barColumn Expr.ascendingOrder)+              (Expr.orderByColumnName fooColumn Expr.ascendingOrder)+      }+ data OrderByTest = OrderByTest-  { orderByValuesToInsert :: [FooBar]+  { orderByValuesToInsert :: NE.NonEmpty FooBar   , orderByClause :: Maybe Expr.OrderByClause+  , orderByDistinctOn :: Maybe (NE.NonEmpty Expr.DistinctOnExpr)   , orderByExpectedQueryResults :: [FooBar]   } @@ -120,14 +149,25 @@           dropAndRecreateTestTable connection            RawSql.executeVoid connection $-            Expr.insertExpr fooBarTable Nothing (insertFooBarSource $ orderByValuesToInsert test) Nothing+            Expr.insertExpr fooBarTable Nothing (insertFooBarSource $ orderByValuesToInsert test) Nothing Nothing            result <-             RawSql.execute connection $               Expr.queryExpr-                (Expr.selectClause $ Expr.selectExpr Nothing)+                (Expr.selectClause . Expr.selectDistinctOnExpr $ orderByDistinctOn test)                 (Expr.selectColumns [fooColumn, barColumn])-                (Just $ Expr.tableExpr (Expr.referencesTable fooBarTable) Nothing Nothing (orderByClause test) Nothing Nothing)+                ( Just $+                    Expr.tableExpr+                      (Expr.singleTableReferenceList fooBarTable)+                      Nothing+                      Nothing+                      (orderByClause test)+                      Nothing+                      Nothing+                      Nothing+                      Nothing+                      Nothing+                )            Execution.readRows result 
test/Test/Expr/SequenceDefinition.hs view
@@ -57,9 +57,9 @@     PgCatalog.pgSequenceCache pgSequence === 10     PgCatalog.pgSequenceCycle pgSequence === True -exprSequenceName :: Expr.Qualified Expr.SequenceName+exprSequenceName :: Expr.QualifiedOrUnqualified Expr.SequenceName exprSequenceName =-  Expr.qualifySequence Nothing (Expr.sequenceName sequenceNameString)+  Expr.unqualified (Expr.sequenceName sequenceNameString)  sequenceNameString :: String sequenceNameString =
test/Test/Expr/TableDefinition.hs view
@@ -18,6 +18,7 @@     "Expr - TableDefinition"     [ prop_createWithOneColumn pool     , prop_createWithMultipleColumns pool+    , prop_renameTable pool     , prop_addOneColumn pool     , prop_addMultipleColumns pool     ]@@ -39,11 +40,28 @@     MIO.liftIO $       Orville.runOrville pool $ do         Orville.executeVoid Orville.DDLQuery $ Expr.dropTableExpr (Just Expr.ifExists) exprTableName-        Orville.executeVoid Orville.DDLQuery $ Expr.createTableExpr exprTableName [column1Definition, column2Definition] Nothing []+        Orville.executeVoid Orville.DDLQuery $ Expr.createTableExpr exprTableName [column1Definition, column2Definition, column1DefinitionGenAlways, column1DefinitionGenByDefault] Nothing []      tableDesc <- PgAssert.assertTableExists pool tableNameString-    PgAssert.assertColumnNamesEqual tableDesc [column1NameString, column2NameString]+    PgAssert.assertColumnNamesEqual tableDesc [column1NameString, column1AlwaysNameString, column1ByDefaultNameString, column2NameString] +prop_renameTable :: Property.NamedDBProperty+prop_renameTable =+  Property.singletonNamedDBProperty "Rename table results in the new name existing and the old name not" $ \pool -> do+    let+      newTableNameString = "renamed_" <> tableNameString+      newTableName = Expr.unqualified $ Expr.tableName newTableNameString+    MIO.liftIO $+      Orville.runOrville pool $ do+        Orville.executeVoid Orville.DDLQuery $ Expr.dropTableExpr (Just Expr.ifExists) exprTableName+        Orville.executeVoid Orville.DDLQuery $ Expr.dropTableExpr (Just Expr.ifExists) newTableName+        Orville.executeVoid Orville.DDLQuery $ Expr.createTableExpr exprTableName [column1Definition] Nothing []+        Orville.executeVoid Orville.DDLQuery $ Expr.renameTableExpr exprTableName newTableName++    PgAssert.assertTableDoesNotExist pool tableNameString+    tableDesc <- PgAssert.assertTableExists pool newTableNameString+    PgAssert.assertColumnNamesEqual tableDesc [column1NameString]+ prop_addOneColumn :: Property.NamedDBProperty prop_addOneColumn =   Property.singletonNamedDBProperty "Alter table adds one column" $ \pool -> do@@ -63,14 +81,14 @@       Orville.runOrville pool $ do         Orville.executeVoid Orville.DDLQuery $ Expr.dropTableExpr (Just Expr.ifExists) exprTableName         Orville.executeVoid Orville.DDLQuery $ Expr.createTableExpr exprTableName [] Nothing []-        Orville.executeVoid Orville.DDLQuery $ Expr.alterTableExpr exprTableName (Expr.addColumn column1Definition :| [Expr.addColumn column2Definition])+        Orville.executeVoid Orville.DDLQuery $ Expr.alterTableExpr exprTableName (Expr.addColumn column1Definition :| [Expr.addColumn column1DefinitionGenAlways, Expr.addColumn column1DefinitionGenByDefault, Expr.addColumn column2Definition])      tableDesc <- PgAssert.assertTableExists pool tableNameString-    PgAssert.assertColumnNamesEqual tableDesc [column1NameString, column2NameString]+    PgAssert.assertColumnNamesEqual tableDesc [column1NameString, column2NameString, column1AlwaysNameString, column1ByDefaultNameString] -exprTableName :: Expr.Qualified Expr.TableName+exprTableName :: Expr.QualifiedOrUnqualified Expr.TableName exprTableName =-  Expr.qualifyTable Nothing (Expr.tableName tableNameString)+  Expr.unqualified (Expr.tableName tableNameString)  tableNameString :: String tableNameString =@@ -81,19 +99,43 @@   Expr.columnDefinition     (Expr.columnName column1NameString)     Expr.text+    mempty     Nothing++column1DefinitionGenByDefault :: Expr.ColumnDefinition+column1DefinitionGenByDefault =+  Expr.columnDefinition+    (Expr.columnName column1ByDefaultNameString)+    Expr.int+    [Expr.notNullConstraint, Expr.identityColumnConstraint Expr.byDefaultColumnIdentityGeneration]     Nothing +column1DefinitionGenAlways :: Expr.ColumnDefinition+column1DefinitionGenAlways =+  Expr.columnDefinition+    (Expr.columnName column1AlwaysNameString)+    Expr.int+    [Expr.notNullConstraint, Expr.identityColumnConstraint Expr.alwaysColumnIdentityGeneration]+    Nothing+ column1NameString :: String column1NameString =   "column1" +column1AlwaysNameString :: String+column1AlwaysNameString =+  "column1_always"++column1ByDefaultNameString :: String+column1ByDefaultNameString =+  "column1_by_default"+ column2Definition :: Expr.ColumnDefinition column2Definition =   Expr.columnDefinition     (Expr.columnName column2NameString)     Expr.text-    Nothing+    mempty     Nothing  column2NameString :: String
+ test/Test/Expr/TableReferenceList.hs view
@@ -0,0 +1,42 @@+module Test.Expr.TableReferenceList+  ( tableReferenceListTests+  ) where++import qualified Data.ByteString.Char8 as B8+import GHC.Stack (HasCallStack, withFrozenCallStack)+import Hedgehog ((===))+import qualified Hedgehog as HH++import qualified Orville.PostgreSQL.Expr as Expr+import qualified Orville.PostgreSQL.Raw.RawSql as RawSql++import qualified Test.Property as Property++tableReferenceListTests :: Property.Group+tableReferenceListTests =+  Property.group+    "Expr - TableReferenceList"+    [ prop_tableReferenceList+    ]++prop_tableReferenceList :: Property.NamedProperty+prop_tableReferenceList =+  Property.singletonNamedProperty "creates a comma delimited table list"+    $ assertTableReferenteListEquals+      "\"foo\", \"bar\""+    $ Expr.tableReferenceList [fooTable, barTable]++assertTableReferenteListEquals ::+  (HH.MonadTest m, HasCallStack) =>+  String ->+  Expr.TableReferenceList ->+  m ()+assertTableReferenteListEquals expectedString tableReferenceList =+  withFrozenCallStack $+    RawSql.toExampleBytes tableReferenceList === B8.pack expectedString++fooTable :: Expr.TableReference+fooTable = Expr.tableNameReference (Expr.unqualified $ Expr.tableName "foo") Nothing++barTable :: Expr.TableReference+barTable = Expr.tableNameReference (Expr.unqualified $ Expr.tableName "bar") Nothing
test/Test/Expr/TestSchema.hs view
@@ -2,6 +2,7 @@   ( FooBar (..)   , mkFooBar   , findAllFooBars+  , findAllFooBarsInTable   , fooBarTable   , fooColumn   , fooColumnRef@@ -21,6 +22,7 @@ import qualified Control.Monad.IO.Class as MIO import qualified Data.ByteString.Char8 as B8 import qualified Data.Int as Int+import qualified Data.List.NonEmpty as NE import qualified Data.Text as T import GHC.Stack (HasCallStack, withFrozenCallStack) import Hedgehog ((===))@@ -41,22 +43,26 @@ mkFooBar :: Int.Int32 -> String -> FooBar mkFooBar f b = FooBar (Just f) (Just b) -fooBarTable :: Expr.Qualified Expr.TableName+fooBarTable :: Expr.QualifiedOrUnqualified Expr.TableName fooBarTable =-  Expr.qualifyTable Nothing (Expr.tableName "foobar")+  Expr.unqualified (Expr.tableName "foobar") -fooColumn :: Expr.ColumnName+fooColumn :: Expr.QualifiedOrUnqualified Expr.ColumnName fooColumn =-  Expr.columnName "foo"+  Expr.unqualified $ Expr.columnName "foo"  fooColumnRef :: Expr.ValueExpression fooColumnRef =   Expr.columnReference fooColumn -barColumn :: Expr.ColumnName+barColumn :: Expr.QualifiedOrUnqualified Expr.ColumnName barColumn =-  Expr.columnName "bar"+  Expr.unqualified $ Expr.columnName "bar" +barColumnAliased :: Expr.Qualified Expr.ColumnName+barColumnAliased =+  Expr.aliasQualifyColumn (Expr.stringToAliasExpr "b") $ Expr.columnName "bar"+ barColumnRef :: Expr.ValueExpression barColumnRef =   Expr.columnReference barColumn@@ -68,33 +74,46 @@  findAllFooBars :: Expr.QueryExpr findAllFooBars =-  Expr.queryExpr-    (Expr.selectClause $ Expr.selectExpr Nothing)-    (Expr.selectColumns [fooColumn, barColumn])-    (Just $ Expr.tableExpr (Expr.referencesTable fooBarTable) Nothing Nothing (Just orderByFoo) Nothing Nothing)+  findAllFooBarsInTable fooBarTable +findAllFooBarsInTable :: Expr.QualifiedOrUnqualified Expr.TableName -> Expr.QueryExpr+findAllFooBarsInTable tableName =+  let+    tableRefList =+      Expr.tableReferenceList+        [ Expr.tableNameReference tableName (Just (Expr.stringToAliasExpr "b"))+        ]+  in+    Expr.queryExpr+      (Expr.selectClause $ Expr.selectExpr Nothing)+      (Expr.selectColumns [fooColumn, Expr.untrackQualified barColumnAliased])+      (Just $ Expr.tableExpr tableRefList Nothing Nothing (Just orderByFoo) Nothing Nothing Nothing Nothing Nothing)+ encodeFooBar :: FooBar -> [(Maybe B8.ByteString, SqlValue.SqlValue)] encodeFooBar fooBar =   [ (Just (B8.pack "foo"), nullOr SqlValue.fromInt32 (foo fooBar))   , (Just (B8.pack "bar"), nullOr SqlValue.fromText (T.pack <$> bar fooBar))   ] -insertFooBarSource :: [FooBar] -> Expr.InsertSource+insertFooBarSource :: NE.NonEmpty FooBar -> Expr.InsertSource insertFooBarSource fooBars =   let     mkRow fooBar =-      [ nullOr SqlValue.fromInt32 (foo fooBar)-      , nullOr SqlValue.fromText (T.pack <$> bar fooBar)-      ]+      NE.fromList+        [ Expr.valueExpression $ nullOr SqlValue.fromInt32 (foo fooBar)+        , Expr.valueExpression $ nullOr SqlValue.fromText (T.pack <$> bar fooBar)+        ]   in-    Expr.insertSqlValues (map mkRow fooBars)+    Expr.valuesExprInsertSource+      . Expr.valuesExprFromValueExpressions+      $ fmap mkRow fooBars  nullOr :: (a -> SqlValue.SqlValue) -> Maybe a -> SqlValue.SqlValue nullOr = maybe SqlValue.sqlNull  withFooBarData ::   Orville.ConnectionPool ->-  [FooBar] ->+  NE.NonEmpty FooBar ->   (Orville.Connection -> IO a) ->   HH.PropertyT IO a withFooBarData pool fooBars action =@@ -103,7 +122,7 @@       dropAndRecreateTestTable connection        RawSql.executeVoid connection $-        Expr.insertExpr fooBarTable Nothing (insertFooBarSource fooBars) Nothing+        Expr.insertExpr fooBarTable Nothing (insertFooBarSource fooBars) Nothing Nothing        action connection 
+ test/Test/Expr/TextSearch.hs view
@@ -0,0 +1,130 @@+{-# LANGUAGE OverloadedStrings #-}++module Test.Expr.TextSearch+  ( textSearchTests+  )+where++import qualified Control.Monad.IO.Class as MIO+import qualified Data.List.NonEmpty as NE+import Data.Text (Text)++import qualified Orville.PostgreSQL as Orville+import qualified Orville.PostgreSQL.Execution as Execution+import qualified Orville.PostgreSQL.Expr as Expr+import qualified Orville.PostgreSQL.Raw.Connection as Conn+import qualified Orville.PostgreSQL.Raw.RawSql as RawSql+import qualified Orville.PostgreSQL.Raw.SqlValue as SqlValue++import Test.Expr.TestSchema (FooBar (..), assertEqualFooBarRows, barColumn, barColumnRef, dropAndRecreateTestTable, fooBarTable, fooColumn, insertFooBarSource, mkFooBar)+import qualified Test.Property as Property++textSearchTests :: Orville.ConnectionPool -> Property.Group+textSearchTests pool =+  Property.group+    "Expr - TSVector"+    [ prop_matchesOneRow pool+    , prop_toTSRank pool+    , prop_plainToTSQuery pool+    , prop_setTSWeight pool+    ]++prop_matchesOneRow :: Property.NamedDBProperty+prop_matchesOneRow =+  tsVectorTest "TSVector @@ TSQuery finds one result" $+    TSVectorTest+      { tsVectorValuesToInsert = NE.fromList [mkFooBar 1 "ant", mkFooBar 2 "bee", mkFooBar 3 "chihuahua"]+      , tsVectorExpectedQueryResults = [mkFooBar 2 "bee"]+      , whereClause =+          Just . Expr.whereClause $+            Expr.tsMatch+              ( Expr.toTSVector+                  barColumnRef+                  (Just Expr.englishRegConfig)+              )+              ( Expr.toTSQuery+                  (Expr.valueExpression $ SqlValue.fromText ("bee" :: Text))+                  Nothing+              )+      , orderByClause = Nothing+      }++prop_setTSWeight :: Property.NamedDBProperty+prop_setTSWeight =+  tsVectorTest "Sets weight on a TSVector" $+    TSVectorTest+      { tsVectorValuesToInsert = NE.fromList [mkFooBar 1 "weighted"]+      , tsVectorExpectedQueryResults = [mkFooBar 1 "weighted"]+      , whereClause =+          Just . Expr.whereClause $+            Expr.tsMatch+              ( Expr.setTSWeight+                  (Expr.toTSVector barColumnRef Nothing)+                  Expr.tsWeightA+              )+              (Expr.toTSQuery (Expr.valueExpression $ SqlValue.fromText ("weighted" :: Text)) Nothing)+      , orderByClause = Nothing+      }++prop_plainToTSQuery :: Property.NamedDBProperty+prop_plainToTSQuery =+  tsVectorTest "Creates TSQuery from plain text" $+    TSVectorTest+      { tsVectorValuesToInsert = NE.fromList [mkFooBar 1 "plain query"]+      , tsVectorExpectedQueryResults = [mkFooBar 1 "plain query"]+      , whereClause =+          Just . Expr.whereClause $+            Expr.tsMatch+              (Expr.toTSVector barColumnRef Nothing)+              (Expr.plainToTSQuery (Expr.valueExpression $ SqlValue.fromText ("plain query" :: Text)) Nothing)+      , orderByClause = Nothing+      }++prop_toTSRank :: Property.NamedDBProperty+prop_toTSRank =+  tsVectorTest "Calculates rank of TSVector and TSQuery" $+    TSVectorTest+      { tsVectorValuesToInsert = NE.fromList [mkFooBar 1 "foo", mkFooBar 2 "bar"]+      , tsVectorExpectedQueryResults = [mkFooBar 2 "bar", mkFooBar 1 "foo"]+      , whereClause = Nothing+      , orderByClause =+          Just . Expr.orderByClause $+            Expr.orderByValueExpression+              ( Expr.toTSRank+                  ( Expr.setTSWeight+                      (Expr.toTSVector barColumnRef Nothing)+                      Expr.tsWeightA+                  )+                  (Expr.toTSQuery (Expr.valueExpression $ SqlValue.fromText ("bar" :: Text)) Nothing)+              )+              Expr.descendingOrder+      }++data TSVectorTest = TSVectorTest+  { tsVectorValuesToInsert :: NE.NonEmpty FooBar+  , whereClause :: Maybe Expr.WhereClause+  , orderByClause :: Maybe Expr.OrderByClause+  , tsVectorExpectedQueryResults :: [FooBar]+  }++tsVectorTest :: String -> TSVectorTest -> Property.NamedDBProperty+tsVectorTest testName test =+  Property.singletonNamedDBProperty testName $ \pool -> do+    rows <-+      MIO.liftIO $+        Conn.withPoolConnection pool $ \connection -> do+          dropAndRecreateTestTable connection++          RawSql.executeVoid connection $+            Expr.insertExpr fooBarTable Nothing (insertFooBarSource $ tsVectorValuesToInsert test) Nothing Nothing++          result <-+            RawSql.execute connection $+              Expr.queryExpr+                (Expr.selectClause $ Expr.selectExpr Nothing)+                (Expr.selectColumns [fooColumn, barColumn])+                (Just $ Expr.tableExpr (Expr.singleTableReferenceList fooBarTable) (whereClause test) Nothing (orderByClause test) Nothing Nothing Nothing Nothing Nothing)++          Execution.readRows result++    assertEqualFooBarRows rows (tsVectorExpectedQueryResults test)
+ test/Test/Expr/Trigger.hs view
@@ -0,0 +1,73 @@+module Test.Expr.Trigger+  ( triggerTests+  ) where++import qualified Control.Monad.IO.Class as MIO++import qualified Orville.PostgreSQL as Orville+import qualified Orville.PostgreSQL.Execution as Execution+import qualified Orville.PostgreSQL.Expr as Expr+import qualified Orville.PostgreSQL.Raw.Connection as Conn+import qualified Orville.PostgreSQL.Raw.RawSql as RawSql++import Test.Expr.TestSchema (assertEqualFooBarRows, dropAndRecreateTestTable, findAllFooBars, fooBarTable, insertFooBarSource, mkFooBar)+import qualified Test.Property as Property++triggerTests :: Orville.ConnectionPool -> Property.Group+triggerTests pool =+  Property.group+    "Expr - Trigger"+    [ prop_triggers pool+    ]++prop_triggers :: Property.NamedDBProperty+prop_triggers =+  Property.singletonNamedDBProperty "creates a trigger on a table" $ \pool -> do+    let+      fooBars =+        pure $ mkFooBar 1 "dog"++      expectedFooBars =+        [mkFooBar 1 "god"]++      triggerBody =+        "BEGIN \+        \  NEW.bar = reverse (NEW.bar); \+        \  return NEW; \+        \END"++      triggerFunctionName =+        Expr.unqualified $ Expr.functionName "test_trigger_function"++      createTriggerFunction =+        Expr.createFunction+          (Just Expr.orReplace)+          triggerFunctionName+          (Expr.returns Expr.returnTypeTrigger)+          (Expr.language Expr.plpgsql)+          (Expr.asDefinition triggerBody)++      createTrigger =+        Expr.createTrigger+          Nothing+          (Expr.triggerName "test_trigger")+          Expr.triggerBefore+          (pure Expr.triggerOnInsert)+          fooBarTable+          Expr.triggerForEachRow+          triggerFunctionName++    rows <-+      MIO.liftIO $+        Conn.withPoolConnection pool $ \connection -> do+          dropAndRecreateTestTable connection+          RawSql.executeVoid connection createTriggerFunction+          RawSql.executeVoid connection createTrigger+          RawSql.executeVoid connection $+            Expr.insertExpr fooBarTable Nothing (insertFooBarSource fooBars) Nothing Nothing++          result <- RawSql.execute connection findAllFooBars++          Execution.readRows result++    assertEqualFooBarRows rows expectedFooBars
+ test/Test/Expr/Vacuum.hs view
@@ -0,0 +1,90 @@+module Test.Expr.Vacuum+  ( vacuumTests+  )+where++import qualified Data.ByteString.Char8 as B8+import Data.List.NonEmpty (NonEmpty ((:|)))+import GHC.Stack (HasCallStack, withFrozenCallStack)+import qualified Hedgehog as HH++import qualified Orville.PostgreSQL.Expr as Expr+import qualified Orville.PostgreSQL.Raw.RawSql as RawSql++import qualified Test.Property as Property++vacuumTests :: Property.Group+vacuumTests =+  Property.group+    "Expr - Vacuum"+    [ prop_vacuumNoOptionsSingleTable+    , prop_vacuumNoOptionsTwoTables+    , prop_vacuumOneOptionSingleTable+    , prop_vacuumOneOptionTwoTables+    , prop_vacuumTwoOptionsOneTable+    , prop_vacuumTwoOptionsTwoTables+    ]++prop_vacuumNoOptionsSingleTable :: Property.NamedProperty+prop_vacuumNoOptionsSingleTable =+  Property.singletonNamedProperty "vacuumExpr with empty options and a single table generates expected sql" $+    assertVacuumEquals+      "VACUUM \"foo\""+      []+      singleTable++prop_vacuumNoOptionsTwoTables :: Property.NamedProperty+prop_vacuumNoOptionsTwoTables =+  Property.singletonNamedProperty "vacuumExpr with empty options and a pair of tables generates expected sql" $+    assertVacuumEquals+      "VACUUM \"foo\", \"bar\""+      []+      twoTables++prop_vacuumOneOptionSingleTable :: Property.NamedProperty+prop_vacuumOneOptionSingleTable =+  Property.singletonNamedProperty "vacuumExpr with one option and a single table generates expected sql" $+    assertVacuumEquals+      "VACUUM (FULL TRUE) \"foo\""+      [Expr.vacuumFull True]+      singleTable++prop_vacuumOneOptionTwoTables :: Property.NamedProperty+prop_vacuumOneOptionTwoTables =+  Property.singletonNamedProperty "vacuumExpr with one option and a pair of tables generates expected sql" $+    assertVacuumEquals+      "VACUUM (FULL TRUE) \"foo\", \"bar\""+      [Expr.vacuumFull True]+      twoTables++prop_vacuumTwoOptionsOneTable :: Property.NamedProperty+prop_vacuumTwoOptionsOneTable =+  Property.singletonNamedProperty "vacuumExpr with two options and a single table generates expected sql" $+    assertVacuumEquals+      "VACUUM (FULL TRUE, VERBOSE TRUE) \"foo\""+      [ Expr.vacuumFull True+      , Expr.vacuumVerbose True+      ]+      singleTable++prop_vacuumTwoOptionsTwoTables :: Property.NamedProperty+prop_vacuumTwoOptionsTwoTables =+  Property.singletonNamedProperty "vacuumExpr with two options and a pair of tables generates expected sql" $+    assertVacuumEquals+      "VACUUM (FULL TRUE, VERBOSE TRUE) \"foo\", \"bar\""+      [ Expr.vacuumFull True+      , Expr.vacuumVerbose True+      ]+      twoTables++assertVacuumEquals :: (HH.MonadTest m, HasCallStack) => String -> [Expr.VacuumOption] -> NonEmpty (Expr.QualifiedOrUnqualified Expr.TableName) -> m ()+assertVacuumEquals mbVacuum vacuumOptions vacuumTables =+  withFrozenCallStack $+    RawSql.toExampleBytes (Expr.vacuumExpr vacuumOptions vacuumTables) HH.=== B8.pack mbVacuum++singleTable :: NonEmpty (Expr.QualifiedOrUnqualified Expr.TableName)+singleTable = pure . Expr.unqualified $ Expr.tableName "foo"++twoTables :: NonEmpty (Expr.QualifiedOrUnqualified Expr.TableName)+twoTables =+  (Expr.unqualified $ Expr.tableName "foo") :| [Expr.unqualified $ Expr.tableName "bar"]
test/Test/Expr/Where.hs view
@@ -6,6 +6,7 @@ import qualified Control.Monad.IO.Class as MIO import Data.Int (Int32) import Data.List.NonEmpty (NonEmpty ((:|)))+import qualified Data.List.NonEmpty as NE import qualified Data.Text as T  import qualified Orville.PostgreSQL as Orville@@ -23,12 +24,15 @@   Property.group "Expr - WhereClause" $     [ prop_noWhereClauseSpecified pool     , prop_equalsOp pool+    , prop_isDistinctFromOp pool+    , prop_isNotDistinctFromOp pool     , prop_greaterThanOp pool     , prop_greaterThanOrEqualsOp pool     , prop_lessThanOp pool     , prop_lessThanOrEqualsToOp pool     , prop_andExpr pool     , prop_orExpr pool+    , prop_notExpr pool     , prop_valueIn pool     , prop_valueNotIn pool     , prop_tupleIn pool@@ -39,7 +43,7 @@ prop_noWhereClauseSpecified =   whereConditionTest "Returns all rows when where clause is specified" $     WhereConditionTest-      { whereValuesToInsert = [mkFooBar 1 "ant", mkFooBar 2 "bee", mkFooBar 3 "chihuahua"]+      { whereValuesToInsert = NE.fromList [mkFooBar 1 "ant", mkFooBar 2 "bee", mkFooBar 3 "chihuahua"]       , whereExpectedQueryResults = [mkFooBar 1 "ant", mkFooBar 2 "bee", mkFooBar 3 "chihuahua"]       , whereClause = Nothing       }@@ -48,18 +52,43 @@ prop_equalsOp =   whereConditionTest "equalsOp matches exact value" $     WhereConditionTest-      { whereValuesToInsert = [mkFooBar 1 "ant", mkFooBar 2 "bee", mkFooBar 3 "chihuahua"]+      { whereValuesToInsert = NE.fromList [mkFooBar 1 "ant", mkFooBar 2 "bee", mkFooBar 3 "chihuahua"]       , whereExpectedQueryResults = [mkFooBar 2 "bee"]       , whereClause =           Just . Expr.whereClause $             Expr.equals fooColumnRef (int32ValueExpr 2)       } +prop_isDistinctFromOp :: Property.NamedDBProperty+prop_isDistinctFromOp =+  whereConditionTest "isDistinctFromOp matches on null correctly" $+    WhereConditionTest+      { whereValuesToInsert = NE.fromList [FooBar Nothing (Just "ant"), mkFooBar 2 "bee", FooBar Nothing (Just "chihuahua")]+      , whereExpectedQueryResults = [mkFooBar 2 "bee"]+      , whereClause =+          Just . Expr.whereClause $+            Expr.isDistinctFrom fooColumnRef (Expr.valueExpression SqlValue.sqlNull)+      }++prop_isNotDistinctFromOp :: Property.NamedDBProperty+prop_isNotDistinctFromOp =+  whereConditionTest "isNotDistinctFromOp matches on null correctly" $+    let+      expectedResults = [FooBar Nothing (Just "ant"), FooBar Nothing (Just "chihuahua")]+    in+      WhereConditionTest+        { whereValuesToInsert = mkFooBar 2 "bee" :| expectedResults+        , whereExpectedQueryResults = expectedResults+        , whereClause =+            Just . Expr.whereClause $+              Expr.isNotDistinctFrom fooColumnRef (Expr.valueExpression SqlValue.sqlNull)+        }+ prop_greaterThanOp :: Property.NamedDBProperty prop_greaterThanOp =   whereConditionTest "greaterThanOp matches greater values" $     WhereConditionTest-      { whereValuesToInsert = [mkFooBar 1 "ant", mkFooBar 2 "bee", mkFooBar 3 "chihuahua"]+      { whereValuesToInsert = NE.fromList [mkFooBar 1 "ant", mkFooBar 2 "bee", mkFooBar 3 "chihuahua"]       , whereExpectedQueryResults = [mkFooBar 3 "chihuahua"]       , whereClause =           Just . Expr.whereClause $@@ -70,7 +99,7 @@ prop_greaterThanOrEqualsOp =   whereConditionTest "greaterThanOrEqualsOp matches greater or equal values" $     WhereConditionTest-      { whereValuesToInsert = [mkFooBar 1 "ant", mkFooBar 2 "bee", mkFooBar 3 "chihuahua"]+      { whereValuesToInsert = NE.fromList [mkFooBar 1 "ant", mkFooBar 2 "bee", mkFooBar 3 "chihuahua"]       , whereExpectedQueryResults = [mkFooBar 2 "bee", mkFooBar 3 "chihuahua"]       , whereClause =           Just . Expr.whereClause $@@ -81,7 +110,7 @@ prop_lessThanOp =   whereConditionTest "lessThanOp matches lesser values" $     WhereConditionTest-      { whereValuesToInsert = [mkFooBar 1 "ant", mkFooBar 2 "bee", mkFooBar 3 "chihuahua"]+      { whereValuesToInsert = NE.fromList [mkFooBar 1 "ant", mkFooBar 2 "bee", mkFooBar 3 "chihuahua"]       , whereExpectedQueryResults = [mkFooBar 1 "ant"]       , whereClause =           Just . Expr.whereClause $@@ -92,7 +121,7 @@ prop_lessThanOrEqualsToOp =   whereConditionTest "lessThanOrEqualsOp matches lesser or equal values" $     WhereConditionTest-      { whereValuesToInsert = [mkFooBar 1 "ant", mkFooBar 2 "bee", mkFooBar 3 "chihuahua"]+      { whereValuesToInsert = NE.fromList [mkFooBar 1 "ant", mkFooBar 2 "bee", mkFooBar 3 "chihuahua"]       , whereExpectedQueryResults = [mkFooBar 1 "ant", mkFooBar 2 "bee"]       , whereClause =           Just . Expr.whereClause $@@ -103,7 +132,7 @@ prop_andExpr =   whereConditionTest "andExpr requires both conditions to be true" $     WhereConditionTest-      { whereValuesToInsert = [mkFooBar 1 "dog", mkFooBar 2 "dingo", mkFooBar 3 "dog"]+      { whereValuesToInsert = NE.fromList [mkFooBar 1 "dog", mkFooBar 2 "dingo", mkFooBar 3 "dog"]       , whereExpectedQueryResults = [mkFooBar 3 "dog"]       , whereClause =           Just . Expr.whereClause $@@ -116,7 +145,7 @@ prop_orExpr =   whereConditionTest "orExpr requires either conditions to be true" $     WhereConditionTest-      { whereValuesToInsert = [mkFooBar 1 "dog", mkFooBar 2 "dingo", mkFooBar 3 "dog"]+      { whereValuesToInsert = NE.fromList [mkFooBar 1 "dog", mkFooBar 2 "dingo", mkFooBar 3 "dog"]       , whereExpectedQueryResults = [mkFooBar 2 "dingo", mkFooBar 3 "dog"]       , whereClause =           Just . Expr.whereClause $@@ -125,11 +154,24 @@               (Expr.equals barColumnRef (textValueExpr "dingo"))       } +prop_notExpr :: Property.NamedDBProperty+prop_notExpr =+  whereConditionTest "notExpr inverts condition" $+    WhereConditionTest+      { whereValuesToInsert = NE.fromList [mkFooBar 1 "dog", mkFooBar 2 "dingo", mkFooBar 3 "dog"]+      , whereExpectedQueryResults = [mkFooBar 2 "dingo"]+      , whereClause =+          Just . Expr.whereClause . Expr.notExpr $+            Expr.orExpr+              (Expr.equals fooColumnRef (int32ValueExpr 3))+              (Expr.equals barColumnRef (textValueExpr "dog"))+      }+ prop_valueIn :: Property.NamedDBProperty prop_valueIn =   whereConditionTest "valueIn requires the column's value to be in the list" $     WhereConditionTest-      { whereValuesToInsert = [mkFooBar 1 "dog", mkFooBar 2 "dingo", mkFooBar 3 "dog"]+      { whereValuesToInsert = NE.fromList [mkFooBar 1 "dog", mkFooBar 2 "dingo", mkFooBar 3 "dog"]       , whereExpectedQueryResults = [mkFooBar 1 "dog", mkFooBar 3 "dog"]       , whereClause =           Just . Expr.whereClause $@@ -142,7 +184,7 @@ prop_valueNotIn =   whereConditionTest "valueNotIn requires the column's value to not be in the list" $     WhereConditionTest-      { whereValuesToInsert = [mkFooBar 1 "dog", mkFooBar 2 "dingo", mkFooBar 3 "dog"]+      { whereValuesToInsert = NE.fromList [mkFooBar 1 "dog", mkFooBar 2 "dingo", mkFooBar 3 "dog"]       , whereExpectedQueryResults = [mkFooBar 2 "dingo"]       , whereClause =           Just . Expr.whereClause $@@ -155,7 +197,7 @@ prop_tupleIn =   whereConditionTest "tupleIn requires the column value combination to be in the list" $     WhereConditionTest-      { whereValuesToInsert = [mkFooBar 1 "dog", mkFooBar 2 "dingo", mkFooBar 3 "dog"]+      { whereValuesToInsert = NE.fromList [mkFooBar 1 "dog", mkFooBar 2 "dingo", mkFooBar 3 "dog"]       , whereExpectedQueryResults = [mkFooBar 1 "dog", mkFooBar 2 "dingo"]       , whereClause =           Just . Expr.whereClause $@@ -170,7 +212,7 @@ prop_tupleNotIn =   whereConditionTest "tupleNotIn requires the column value combination to not be in the list" $     WhereConditionTest-      { whereValuesToInsert = [mkFooBar 1 "dog", mkFooBar 2 "dingo", mkFooBar 3 "dog"]+      { whereValuesToInsert = NE.fromList [mkFooBar 1 "dog", mkFooBar 2 "dingo", mkFooBar 3 "dog"]       , whereExpectedQueryResults = [mkFooBar 3 "dog"]       , whereClause =           Just . Expr.whereClause $@@ -190,7 +232,7 @@   Expr.valueExpression . SqlValue.fromText . T.pack  data WhereConditionTest = WhereConditionTest-  { whereValuesToInsert :: [FooBar]+  { whereValuesToInsert :: NE.NonEmpty FooBar   , whereClause :: Maybe Expr.WhereClause   , whereExpectedQueryResults :: [FooBar]   }@@ -204,14 +246,14 @@           dropAndRecreateTestTable connection            RawSql.executeVoid connection $-            Expr.insertExpr fooBarTable Nothing (insertFooBarSource $ whereValuesToInsert test) Nothing+            Expr.insertExpr fooBarTable Nothing (insertFooBarSource $ whereValuesToInsert test) Nothing Nothing            result <-             RawSql.execute connection $               Expr.queryExpr                 (Expr.selectClause $ Expr.selectExpr Nothing)                 (Expr.selectColumns [fooColumn, barColumn])-                (Just $ Expr.tableExpr (Expr.referencesTable fooBarTable) (whereClause test) Nothing Nothing Nothing Nothing)+                (Just $ Expr.tableExpr (Expr.singleTableReferenceList fooBarTable) (whereClause test) Nothing Nothing Nothing Nothing Nothing Nothing Nothing)            Execution.readRows result 
+ test/Test/Expr/Window.hs view
@@ -0,0 +1,60 @@+module Test.Expr.Window+  ( windowTests+  )+where++import qualified Data.ByteString.Char8 as B8+import GHC.Stack (HasCallStack, withFrozenCallStack)+import qualified Hedgehog as HH++import qualified Orville.PostgreSQL.Expr as Expr+import qualified Orville.PostgreSQL.Raw.RawSql as RawSql+import qualified Orville.PostgreSQL.Raw.SqlValue as SqlValue++import Test.Expr.TestSchema (barColumn)+import qualified Test.Property as Property++windowTests :: Property.Group+windowTests =+  Property.group+    "Expr - Window"+    [ prop_windowClauseSingleDef+    , prop_windowClauseMultiDef+    ]++prop_windowClauseSingleDef :: Property.NamedProperty+prop_windowClauseSingleDef =+  let+    windowDef = Expr.windowDefinition Nothing (Just partitionBy) (Just orderBy) (Just frameClause)+    windowDefName = Expr.fromIdentifier $ Expr.identifier "a"+    partitionBy = Expr.partitionBy (pure . Expr.valueExpression $ SqlValue.fromInt32 1)+    orderBy = Expr.orderByClause $ Expr.orderByColumnName barColumn Expr.ascendingOrder+    frameClause = Expr.frameClause (Just Expr.rowsFrameMode) Expr.unboundedPrecedingFrameStart (Just Expr.unboundedFollowingFrameEnd) (Just Expr.tiesFrameExclusion)+  in+    Property.singletonNamedProperty "window clause with single window definition containing a partition by, an order by, and a frame clause generates expected sql."+      . assertWindowEquals+        "WINDOW \"a\" AS (PARTITION BY $1 ORDER BY \"bar\" ASC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING EXCLUDE TIES)"+      . Expr.windowClause+      $ Expr.namedWindowDefinition windowDefName windowDef++prop_windowClauseMultiDef :: Property.NamedProperty+prop_windowClauseMultiDef =+  let+    windowDef1 = Expr.windowDefinition Nothing (Just partitionBy) Nothing Nothing+    windowDef1Name = Expr.fromIdentifier $ Expr.identifier "a"+    windowDef2 = Expr.windowDefinition (Just windowDef1Name) Nothing (Just orderBy) (Just frameClause)+    windowDef2Name = Expr.fromIdentifier $ Expr.identifier "b"+    partitionBy = Expr.partitionBy (pure . Expr.valueExpression $ SqlValue.fromInt32 1)+    orderBy = Expr.orderByClause $ Expr.orderByColumnName barColumn Expr.ascendingOrder+    frameClause = Expr.frameClause (Just Expr.rowsFrameMode) Expr.unboundedPrecedingFrameStart (Just Expr.unboundedFollowingFrameEnd) (Just Expr.tiesFrameExclusion)+  in+    Property.singletonNamedProperty "window clause with two window definitions, where the second \"copies\" the first,  generates expected sql."+      . assertWindowEquals+        "WINDOW \"a\" AS (PARTITION BY $1), \"b\" AS (\"a\" ORDER BY \"bar\" ASC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING EXCLUDE TIES)"+      . Expr.windowClause+      $ Expr.namedWindowDefinition windowDef1Name windowDef1 <> Expr.namedWindowDefinition windowDef2Name windowDef2++assertWindowEquals :: (HH.MonadTest m, HasCallStack) => String -> Expr.WindowClause -> m ()+assertWindowEquals windowClauseStr windowClause =+  withFrozenCallStack $+    RawSql.toExampleBytes windowClause HH.=== B8.pack windowClauseStr
test/Test/FieldDefinition.hs view
@@ -248,15 +248,22 @@       Expr.insertExpr         testTable         Nothing-        (Expr.insertSqlValues [[Marshall.fieldValueToSqlValue fieldDef value]])+        ( Expr.valuesExprInsertSource+            . Expr.valuesExprFromValueExpressions+            . pure+            . pure+            . Expr.valueExpression+            $ Marshall.toComparableSqlValue fieldDef value+        )         Nothing+        Nothing      result <-       RawSql.execute connection $         Expr.queryExpr           (Expr.selectClause $ Expr.selectExpr Nothing)-          (Expr.selectColumns [Marshall.fieldColumnName fieldDef])-          (Just $ Expr.tableExpr (Expr.referencesTable testTable) Nothing Nothing Nothing Nothing Nothing)+          (Expr.selectColumns [Expr.unqualified (Marshall.fieldColumnName fieldDef)])+          (Just $ Expr.tableExpr (Expr.singleTableReferenceList testTable) Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing)      Execution.readRows result @@ -292,15 +299,22 @@       Expr.insertExpr         testTable         Nothing-        (Expr.insertSqlValues [[Marshall.fieldValueToSqlValue fieldDef value]])+        ( Expr.valuesExprInsertSource+            . Expr.valuesExprFromValueExpressions+            . pure+            . pure+            . Expr.valueExpression+            $ Marshall.toComparableSqlValue fieldDef value+        )         Nothing+        Nothing      result <-       RawSql.execute connection $         Expr.queryExpr           (Expr.selectClause $ Expr.selectExpr Nothing)-          (Expr.selectColumns [Marshall.fieldColumnName fieldDef])-          (Just $ Expr.tableExpr (Expr.referencesTable testTable) Nothing Nothing Nothing Nothing Nothing)+          (Expr.selectColumns [Expr.unqualified (Marshall.fieldColumnName fieldDef)])+          (Just $ Expr.tableExpr (Expr.singleTableReferenceList testTable) Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing)      Execution.readRows result @@ -327,8 +341,14 @@         Expr.insertExpr           testTable           Nothing-          (Expr.insertSqlValues [[SqlValue.sqlNull]])+          ( Expr.valuesExprInsertSource+              . Expr.valuesExprFromValueExpressions+              . pure+              . pure+              $ Expr.valueExpression SqlValue.sqlNull+          )           Nothing+          Nothing    case result of     Left err ->@@ -362,13 +382,14 @@         Nothing         (RawSql.unsafeSqlExpression "VALUES(DEFAULT)")         Nothing+        Nothing      result <-       RawSql.execute connection $         Expr.queryExpr           (Expr.selectClause $ Expr.selectExpr Nothing)-          (Expr.selectColumns [Marshall.fieldColumnName fieldDef])-          (Just $ Expr.tableExpr (Expr.referencesTable testTable) Nothing Nothing Nothing Nothing Nothing)+          (Expr.selectColumns [Expr.unqualified (Marshall.fieldColumnName fieldDef)])+          (Just $ Expr.tableExpr (Expr.singleTableReferenceList testTable) Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing)      Execution.readRows result @@ -401,10 +422,11 @@         Nothing         (RawSql.unsafeSqlExpression "VALUES(DEFAULT)")         Nothing+        Nothing -testTable :: Expr.Qualified Expr.TableName+testTable :: Expr.QualifiedOrUnqualified Expr.TableName testTable =-  Expr.qualifyTable Nothing (Expr.tableName "field_definition_test")+  Expr.unqualified (Expr.tableName "field_definition_test")  dropAndRecreateTestTable :: Marshall.FieldDefinition nullability a -> Orville.Connection -> IO () dropAndRecreateTestTable fieldDef connection = do
test/Test/MarshallError.hs view
@@ -33,9 +33,7 @@     , prop_showMarshallErrorRaisedFromOrvilleContext pool     ] -{- |-  Shared example used to test error detail level rendering of decoding errors-  below.+{- | Shared example used to test error detail level rendering of decoding errors below.  @since 1.0.0.0 -}@@ -134,9 +132,7 @@             \Unable to decode columns from result set: [REDACTED]. \             \Value(s) that failed to decode: [bar = baz]" -{- |-  Shared example used to test error detail level rendering of missing column-  errors below.+{- | Shared example used to test error detail level rendering of missing column errors below.  @since 1.0.0.0 -}
+ test/Test/Orphans.hs view
@@ -0,0 +1,23 @@+{-# LANGUAGE StandaloneDeriving #-}+{-# OPTIONS_GHC -Wno-orphans #-}++module Test.Orphans () where++import qualified Data.ByteString.Char8 as B8+import qualified Data.List as List+import qualified Data.List.NonEmpty as NE++import qualified Orville.PostgreSQL.Marshall as Marshall+import qualified Orville.PostgreSQL.Raw.PgTextFormatValue as PgTextFormatValue+import qualified Orville.PostgreSQL.Raw.SqlValue as SqlValue++instance Show SqlValue.SqlValue where+  show =+    SqlValue.foldSqlValue+      (B8.unpack . PgTextFormatValue.toByteString)+      (\vals -> "(" <> List.intercalate ", " (NE.toList vals) <> ")")+      "NULL"++deriving instance Show Marshall.FieldIdentityGeneration+deriving instance Enum Marshall.FieldIdentityGeneration+deriving instance Bounded Marshall.FieldIdentityGeneration
test/Test/PgAssert.hs view
@@ -12,25 +12,41 @@   , assertColumnExists   , assertColumnDefaultExists   , assertColumnDefaultMatches+  , assertFieldIdentityGenerationMatches   , assertUniqueConstraintExists   , assertForeignKeyConstraintExists   , assertIndexExists+  , assertFunctionExists+  , assertFunctionExistsInSchema+  , assertFunctionDoesNotExist+  , assertFunctionDoesNotExistInSchema+  , assertTriggerExists+  , assertTriggerDoesNotExist+  , assertExtensionLoaded+  , assertExtensionNotLoaded   , ForeignKeyInfo (..)+  , assertTableHasComment+  , assertTableDoesNotHaveComment+  , assertTableColumnsHaveOrDoNotHaveComments   ) where +import qualified Control.Exception.Safe as SafeEx import qualified Control.Monad as Monad import qualified Control.Monad.IO.Class as MIO+import qualified Data.Foldable as Fold import qualified Data.List as List import qualified Data.List.NonEmpty as NEL import qualified Data.Map.Strict as Map import qualified Data.String as String+import qualified Data.Text as T import qualified Data.Text.Encoding as Enc import GHC.Stack (HasCallStack, withFrozenCallStack) import Hedgehog ((===)) import qualified Hedgehog as HH  import qualified Orville.PostgreSQL as Orville+import qualified Orville.PostgreSQL.Marshall as Marshall import qualified Orville.PostgreSQL.PgCatalog as PgCatalog import qualified Orville.PostgreSQL.Raw.RawSql as RawSql @@ -126,8 +142,10 @@   dbDesc <-     MIO.liftIO $       Orville.runOrville pool $ do-        PgCatalog.describeDatabaseRelations+        PgCatalog.describeDatabase           [(String.fromString schemaName, String.fromString relationName)]+          []+          []    case PgCatalog.lookupRelation (String.fromString schemaName, String.fromString relationName) dbDesc of     Nothing -> do@@ -148,9 +166,11 @@ assertRelationDoesNotExistInSchema pool schemaName tableName relationKind = do   dbDesc <-     MIO.liftIO $-      Orville.runOrville pool $ do-        PgCatalog.describeDatabaseRelations+      Orville.runOrville pool $+        PgCatalog.describeDatabase           [(String.fromString schemaName, String.fromString tableName)]+          []+          []    case PgCatalog.lookupRelation (String.fromString schemaName, String.fromString tableName) dbDesc of     Nothing ->@@ -198,6 +218,29 @@     Just attr ->       pure attr +assertFieldIdentityGenerationMatches ::+  (HH.MonadTest m, HasCallStack) =>+  PgCatalog.RelationDescription ->+  String ->+  Maybe Marshall.FieldIdentityGeneration ->+  m ()+assertFieldIdentityGenerationMatches relationDesc columnName mbExpectedIdentityGeneration = do+  attr <- assertColumnExists relationDesc columnName+  case (PgCatalog.pgAttributeIdentity attr, mbExpectedIdentityGeneration) of+    (Nothing, Nothing) -> pure ()+    (Nothing, Just _) -> do+      HH.annotate $ columnName <> " expected to be an identity but was not"+      HH.failure+    (Just _, Nothing) -> do+      HH.annotate $ columnName <> " expected to not be an identity but was"+      HH.failure+    (Just colId, Just expectedIdentityGeneration) ->+      if colId == expectedIdentityGeneration+        then pure ()+        else do+          HH.annotate $ columnName <> " did not match expected identity generation"+          HH.failure+ assertColumnDefaultExists ::   (HH.MonadTest m, HasCallStack) =>   PgCatalog.RelationDescription ->@@ -371,3 +414,228 @@   List.any     predicate     (PgCatalog.relationIndexes relationDesc)++assertFunctionExists ::+  (HH.MonadTest m, MIO.MonadIO m, SafeEx.MonadCatch m, HasCallStack) =>+  Orville.ConnectionPool ->+  String ->+  m PgCatalog.PgProc+assertFunctionExists pool =+  assertFunctionExistsInSchema pool "public"++assertFunctionExistsInSchema ::+  (HH.MonadTest m, MIO.MonadIO m, SafeEx.MonadCatch m, HasCallStack) =>+  Orville.ConnectionPool ->+  String ->+  String ->+  m PgCatalog.PgProc+assertFunctionExistsInSchema pool schemaName functionName = do+  namespaceOid <-+    HH.evalMaybeM+      . MIO.liftIO+      . Orville.runOrville pool+      . fmap (fmap PgCatalog.pgNamespaceOid)+      $ Orville.findFirstEntityBy+        PgCatalog.pgNamespaceTable+        ( Orville.where_+            (Orville.fieldEquals PgCatalog.namespaceNameField (String.fromString schemaName))+        )++  mbProc <-+    MIO.liftIO $+      Orville.runOrville pool $ do+        Orville.findFirstEntityBy+          PgCatalog.pgProcTable+          ( Orville.where_+              ( Orville.andExpr+                  (Orville.fieldEquals PgCatalog.procNamespaceOidField namespaceOid)+                  (Orville.fieldEquals PgCatalog.procNameField (String.fromString functionName))+              )+          )++  case mbProc of+    Nothing -> do+      withFrozenCallStack $ do+        HH.annotate $ functionName <> " function not found"+        HH.failure+    Just proc -> do+      pure proc++assertFunctionDoesNotExist ::+  (HH.MonadTest m, MIO.MonadIO m, SafeEx.MonadCatch m, HasCallStack) =>+  Orville.ConnectionPool ->+  String ->+  m ()+assertFunctionDoesNotExist pool =+  assertFunctionDoesNotExistInSchema pool "public"++assertFunctionDoesNotExistInSchema ::+  (HH.MonadTest m, MIO.MonadIO m, SafeEx.MonadCatch m, HasCallStack) =>+  Orville.ConnectionPool ->+  String ->+  String ->+  m ()+assertFunctionDoesNotExistInSchema pool schemaName functionName = do+  namespaceOid <-+    HH.evalMaybeM+      . MIO.liftIO+      . Orville.runOrville pool+      . fmap (fmap PgCatalog.pgNamespaceOid)+      $ Orville.findFirstEntityBy+        PgCatalog.pgNamespaceTable+        ( Orville.where_+            (Orville.fieldEquals PgCatalog.namespaceNameField (String.fromString schemaName))+        )++  mbProc <-+    MIO.liftIO $+      Orville.runOrville pool $ do+        Orville.findFirstEntityBy+          PgCatalog.pgProcTable+          ( Orville.where_+              ( Orville.andExpr+                  (Orville.fieldEquals PgCatalog.procNamespaceOidField namespaceOid)+                  (Orville.fieldEquals PgCatalog.procNameField (String.fromString functionName))+              )+          )++  case mbProc of+    Nothing -> do+      pure ()+    Just _proc -> do+      withFrozenCallStack $ do+        HH.annotate $ functionName <> " function was found, but was not expected"+        HH.failure++assertTriggerExists ::+  (HH.MonadTest m, HasCallStack) =>+  PgCatalog.RelationDescription ->+  String ->+  m ()+assertTriggerExists relationDesc triggerName =+  let+    nameMatches trigger =+      PgCatalog.pgTriggerName trigger == String.fromString triggerName+  in+    case List.find nameMatches (PgCatalog.relationTriggers relationDesc) of+      Nothing ->+        withFrozenCallStack $ do+          HH.annotate $ triggerName <> " trigger not found"+          HH.failure+      Just _trigger ->+        pure ()++assertTriggerDoesNotExist ::+  (HH.MonadTest m, HasCallStack) =>+  PgCatalog.RelationDescription ->+  String ->+  m ()+assertTriggerDoesNotExist relationDesc triggerName =+  let+    nameMatches trigger =+      PgCatalog.pgTriggerName trigger == String.fromString triggerName+  in+    case List.find nameMatches (PgCatalog.relationTriggers relationDesc) of+      Nothing ->+        pure ()+      Just _trigger ->+        withFrozenCallStack $ do+          HH.annotate $ triggerName <> " trigger was found, but was not expected"+          HH.failure++assertExtensionLoaded ::+  (HH.MonadTest m, MIO.MonadIO m, HasCallStack) =>+  Orville.ConnectionPool ->+  String ->+  m PgCatalog.PgExtension+assertExtensionLoaded pool extensionName = do+  dbDesc <-+    MIO.liftIO $+      Orville.runOrville pool $ do+        PgCatalog.describeDatabase+          []+          []+          [String.fromString extensionName]++  case PgCatalog.lookupExtension (String.fromString extensionName) dbDesc of+    Nothing -> do+      withFrozenCallStack $ do+        HH.annotate $ extensionName <> " extension not found"+        HH.failure+    Just ext ->+      pure ext++assertExtensionNotLoaded ::+  (HH.MonadTest m, MIO.MonadIO m, HasCallStack) =>+  Orville.ConnectionPool ->+  String ->+  m ()+assertExtensionNotLoaded pool extensionName = do+  dbDesc <-+    MIO.liftIO $+      Orville.runOrville pool $ do+        PgCatalog.describeDatabase+          []+          []+          [String.fromString extensionName]++  case PgCatalog.lookupExtension (String.fromString extensionName) dbDesc of+    Nothing ->+      pure ()+    Just _ ->+      withFrozenCallStack $ do+        HH.annotate $ extensionName <> " was found but not expected"+        HH.failure++assertTableHasComment ::+  (HH.MonadTest m, MIO.MonadIO m, HasCallStack) =>+  Orville.ConnectionPool ->+  String ->+  T.Text ->+  m ()+assertTableHasComment pool tableName expectedComment = do+  relationDesc <- assertTableExists pool tableName+  PgCatalog.relationComment relationDesc HH.=== Just expectedComment++assertTableDoesNotHaveComment ::+  (HH.MonadTest m, MIO.MonadIO m, HasCallStack) =>+  Orville.ConnectionPool ->+  String ->+  m ()+assertTableDoesNotHaveComment pool tableName = do+  relationDesc <- assertTableExists pool tableName+  PgCatalog.relationComment relationDesc HH.=== Nothing++assertTableColumnHasComment ::+  (HH.MonadTest m, MIO.MonadIO m, HasCallStack) =>+  Orville.ConnectionPool ->+  String ->+  String ->+  T.Text ->+  m ()+assertTableColumnHasComment pool tableName fieldName expectedComment = do+  relationDesc <- assertTableExists pool tableName+  PgCatalog.lookupAttributeComment (String.fromString fieldName) relationDesc HH.=== Just expectedComment++assertTableColumnDoesNotHaveComment ::+  (HH.MonadTest m, MIO.MonadIO m, HasCallStack) =>+  Orville.ConnectionPool ->+  String ->+  String ->+  m ()+assertTableColumnDoesNotHaveComment pool tableName fieldName = do+  relationDesc <- assertTableExists pool tableName+  PgCatalog.lookupAttributeComment (String.fromString fieldName) relationDesc HH.=== Nothing++assertTableColumnsHaveOrDoNotHaveComments ::+  (HH.MonadTest m, MIO.MonadIO m, HasCallStack) =>+  Orville.ConnectionPool ->+  String ->+  [(String, Maybe String)] ->+  m ()+assertTableColumnsHaveOrDoNotHaveComments pool tableName =+  Fold.traverse_+    ( \(colName, mbComment) -> case mbComment of+        Just comment -> assertTableColumnHasComment pool tableName colName (T.pack comment)+        Nothing -> assertTableColumnDoesNotHaveComment pool tableName colName+    )
test/Test/PgCatalog.hs view
@@ -4,6 +4,7 @@ where  import qualified Control.Monad.IO.Class as MIO+import Data.List.NonEmpty (NonEmpty ((:|))) import qualified Data.Map.Strict as Map import qualified Data.Set as Set import qualified Data.String as String@@ -12,6 +13,7 @@ import qualified Hedgehog as HH  import qualified Orville.PostgreSQL as Orville+import qualified Orville.PostgreSQL.Expr as Expr import qualified Orville.PostgreSQL.PgCatalog as PgCatalog  import qualified Test.Entities.Foo as Foo@@ -26,6 +28,8 @@     , prop_queryPgAttribute pool     , prop_queryPgAttributeDefault pool     , prop_queryPgConstraint pool+    , prop_queryPgTrigger pool+    , prop_queryPgProc pool     , prop_describeDatabaseRelations pool     ] @@ -128,6 +132,80 @@     map PgCatalog.pgConstraintType constraints === [PgCatalog.PrimaryKeyConstraint]     map PgCatalog.pgConstraintKey constraints === [Just [1]] +prop_queryPgTrigger :: Property.NamedDBProperty+prop_queryPgTrigger =+  Property.singletonNamedDBProperty "Can query the pg_trigger table to find out about a trigger" $ \pool -> do+    let+      triggerFunctionName =+        Expr.unqualified $ Expr.functionName "test_trigger_function"++      createTriggerFunction =+        Expr.createFunction+          (Just Expr.orReplace)+          triggerFunctionName+          (Expr.returns Expr.returnTypeTrigger)+          (Expr.language Expr.plpgsql)+          (Expr.asDefinition "BEGIN return NEW; END")++      createTrigger =+        Expr.createTrigger+          Nothing+          (Expr.triggerName "test_trigger")+          Expr.triggerBefore+          (Expr.triggerOnInsert :| [])+          (Orville.tableIdQualifiedName (Orville.tableIdentifier Foo.table))+          Expr.triggerForEachRow+          triggerFunctionName++    maybePgClassRecord <-+      HH.evalIO . Foo.withTable pool $ do+        Orville.executeVoid Orville.DDLQuery createTriggerFunction+        Orville.executeVoid Orville.DDLQuery createTrigger+        Orville.findFirstEntityBy+          PgCatalog.pgClassTable+          (Orville.where_ $ Orville.fieldEquals PgCatalog.relationNameField fooRelation)++    pgClassOid <-+      case maybePgClassRecord of+        Nothing -> do+          HH.annotate "Expected to find pg_class table, but did not"+          HH.failure+        Just pgClassRecord ->+          pure $ PgCatalog.pgClassOid pgClassRecord++    triggers <- HH.evalIO . Orville.runOrville pool $ do+      Orville.findEntitiesBy+        PgCatalog.pgTriggerTable+        (Orville.where_ $ Orville.fieldEquals PgCatalog.triggerRelationOidField pgClassOid)++    fmap PgCatalog.pgTriggerName triggers === [String.fromString "test_trigger"]++prop_queryPgProc :: Property.NamedDBProperty+prop_queryPgProc =+  Property.singletonNamedDBProperty "Can query the pg_proc table to find out about a proc" $ \pool -> do+    let+      procFunctionName =+        Expr.unqualified $ Expr.functionName "test_proc"++      procName =+        String.fromString "test_proc"++      createProcFunction =+        Expr.createFunction+          (Just Expr.orReplace)+          procFunctionName+          (Expr.returns Expr.returnTypeTrigger)+          (Expr.language Expr.plpgsql)+          (Expr.asDefinition "BEGIN return NEW; END")++    procs <- HH.evalIO . Orville.runOrville pool $ do+      Orville.executeVoid Orville.DDLQuery createProcFunction+      Orville.findEntitiesBy+        PgCatalog.pgProcTable+        (Orville.where_ $ Orville.fieldEquals PgCatalog.procNameField procName)++    fmap PgCatalog.pgProcName procs === [procName]+ prop_describeDatabaseRelations :: Property.NamedDBProperty prop_describeDatabaseRelations =   Property.singletonNamedDBProperty "Can describe relations from different schemas at once" $ \pool -> do@@ -139,7 +217,7 @@         ]      desc <- HH.evalIO . Orville.runOrville pool $ do-      PgCatalog.describeDatabaseRelations relationsToDescribe+      PgCatalog.describeDatabase relationsToDescribe [] []      Map.keysSet (PgCatalog.databaseRelations desc) === Set.fromList relationsToDescribe 
test/Test/PgGen.hs view
@@ -29,14 +29,12 @@ pgInt32 =   Gen.integral (Range.linearFrom 0 minBound maxBound) -{- |-  Produces a double value that can be reliably round tripped through-  PostgreSQL, which only allows 15 digits of decimal precision.+{- | Produces a double value that can be reliably round tripped through PostgreSQL, which only allows+  15 digits of decimal precision. -  Generating doubles naively using 'Gen.double' produces large numbers with-  more than 15 digits of precisio, so we use 'encodeFloat' to directly ensure-  the precision of large numbers is within PostgreSQL's limit. Precision of-  small numbers is enforced by rounding excess digits off.+  Generating doubles naively using 'Gen.double' produces large numbers with more than 15 digits of+  precisio, so we use 'encodeFloat' to directly ensure the precision of large numbers is within+  PostgreSQL's limit. Precision of small numbers is enforced by rounding excess digits off.  @since 1.0.0.0 -}@@ -85,12 +83,10 @@ pgIdentifier =   Gen.string (Range.linear 1 63) $ Gen.element pgIdentifierChars -{- |-  Relation names must be unique in PostgreSQL, so we sometimes generate-  names with prefixes to avoid conflicts between different types of-  relations such as tables and indexes. The min length value allows the-  caller to length of the random strings that will be appended to prefix,-  which case be useful to avoid conflicts.+{- | Relation names must be unique in PostgreSQL, so we sometimes generate names with prefixes to+  avoid conflicts between different types of relations such as tables and indexes. The min length+  value allows the caller to length of the random strings that will be appended to prefix, which+  case be useful to avoid conflicts.  @since 1.0.0.0 -}@@ -101,10 +97,8 @@     . Gen.element     $ pgIdentifierChars -{- |-  A list of characters to include in identifiers when testing. Not all of these-  are valid in unquoted identifiers -- this helps ensure that Orville is-  properly quoting ids.+{- | A list of characters to include in identifiers when testing. Not all of these are valid in+  unquoted identifiers -- this helps ensure that Orville is properly quoting ids.  @since 1.0.0.0 -}@@ -125,11 +119,20 @@  pgDay :: HH.Gen Time.Day pgDay = do-  year <- Gen.integral (Range.linearFrom 2000 0 3000)+  year <- Gen.integral (Range.constantFrom 2000 (-4713) 294276)   month <- Gen.integral (Range.constant 1 12)   day <- Gen.integral (Range.constant 1 (Time.gregorianMonthLength year month)) -  pure (Time.fromGregorian year month day)+  Gen.frequency+    [ (1, feb29thBCE)+    , (49, pure (Time.fromGregorian year month day))+    ]++-- Edge case to help catch mistakes in date parsing/printing+feb29thBCE :: HH.Gen Time.Day+feb29thBCE = do+  year <- Gen.filter Time.isLeapYear (Gen.integral (Range.linear (-4713) 0))+  pure (Time.fromGregorian year 2 29)  pgTimeOfDay :: HH.Gen Time.TimeOfDay pgTimeOfDay = fmap Time.timeToTimeOfDay pgDiffTime
test/Test/Plan.hs view
@@ -11,11 +11,13 @@ where  import qualified Control.Exception as Exception+import qualified Control.Monad as Monad import qualified Control.Monad.IO.Class as MIO import qualified Data.Either as Either import Data.Foldable (traverse_) import qualified Data.List as List import qualified Data.List.NonEmpty as NEL+import qualified Data.Map.Strict as Map import qualified Data.String as String import Hedgehog ((===)) import qualified Hedgehog as HH@@ -43,13 +45,22 @@   Property.group "Plan" $     [ prop_askParam pool     , prop_findMaybeOne pool+    , prop_findMaybeOneByMarshaller pool     , prop_findMaybeOneWhere pool+    , prop_findMaybeOneWhereByMarshaller pool     , prop_findAll pool+    , prop_findAllByMarshaller pool     , prop_findAllWhere pool+    , prop_findAllWhereByMarshaller pool     , prop_planMany_findMaybeOne pool+    , prop_planMany_findMaybeOneByMarshaller pool     , prop_planMany_findMaybeOneWhere pool+    , prop_planMany_findMaybeOneWhereByMarshaller pool     , prop_planMany_findAll pool+    , prop_planTraversable_Map_findAll pool+    , prop_planMany_findAllByMarshaller pool     , prop_planMany_findAllWhere pool+    , prop_planMany_findAllWhereByMarshaller pool     , prop_planEither pool     , prop_planMany_planEither pool     , prop_bindAndUse pool@@ -84,7 +95,7 @@       plan :: Plan.Plan scope Foo.FooName (Maybe Foo.Foo)       plan = Plan.findMaybeOne Foo.table Foo.fooNameField -    (targetName, foos) <- HH.forAll generateSearchTargetAndSubjects+    (targetName, _, foos) <- HH.forAll generateSearchTargetAndSubjects     maybeResult <-       Foo.withTable pool $ do         traverse_ (Orville.insertEntities Foo.table) (NEL.nonEmpty foos)@@ -96,6 +107,25 @@     coverSearchResultCases isMatch foos     assertMatchIsFromPredicate maybeResult isMatch foos +prop_findMaybeOneByMarshaller :: Property.NamedDBProperty+prop_findMaybeOneByMarshaller =+  Property.namedDBProperty "findMaybeOneByMarshaller finds a row where the marshaller matches (if any)" $ \pool -> do+    let+      plan :: Plan.Plan scope (Foo.FooName, Foo.FooAge) (Maybe Foo.Foo)+      plan = Plan.findMaybeOneByMarshaller Foo.table Foo.nameAndAgeMarshaller++    (targetName, targetAge, foos) <- HH.forAll generateSearchTargetAndSubjects+    maybeResult <-+      Foo.withTable pool $ do+        traverse_ (Orville.insertEntities Foo.table) (NEL.nonEmpty foos)+        Plan.execute plan (targetName, targetAge)++    let+      isMatch foo = Foo.hasName targetName foo && Foo.hasAge targetAge foo++    coverSearchResultCases isMatch foos+    assertMatchIsFromPredicate maybeResult isMatch foos+ prop_planMany_findMaybeOne :: Property.NamedDBProperty prop_planMany_findMaybeOne =   Property.namedDBProperty "(planMany findMaybeOne) finds a row where the field matches for each input" $ \pool -> do@@ -103,7 +133,7 @@       plan :: Plan.Plan scope [Foo.FooName] (Many.Many Foo.FooName (Maybe Foo.Foo))       plan = Plan.planMany $ Plan.findMaybeOne Foo.table Foo.fooNameField -    (targetNames, foos) <- HH.forAll generateSearchTargetListAndSubjects+    (targetNames, _, foos) <- HH.forAll generateSearchTargetListAndSubjects     results <-       Foo.withTable pool $ do         traverse_ (Orville.insertEntities Foo.table) (NEL.nonEmpty foos)@@ -120,6 +150,32 @@         (\foo -> Foo.hasName targetName foo && isMatch foo)         foos +prop_planMany_findMaybeOneByMarshaller :: Property.NamedDBProperty+prop_planMany_findMaybeOneByMarshaller =+  Property.namedDBProperty "(planMany findMaybeOneByMarshaller) finds a row where the marshaller matches for each input" $ \pool -> do+    let+      plan :: Plan.Plan scope [(Foo.FooName, Foo.FooAge)] (Many.Many (Foo.FooName, Foo.FooAge) (Maybe Foo.Foo))+      plan = Plan.planMany $ Plan.findMaybeOneByMarshaller Foo.table Foo.nameAndAgeMarshaller++    (targetNames, targetAges, foos) <- HH.forAll generateSearchTargetListAndSubjects+    let+      target = zip targetNames targetAges+    results <-+      Foo.withTable pool $ do+        traverse_ (Orville.insertEntities Foo.table) (NEL.nonEmpty foos)+        Plan.execute plan target++    let+      isMatch foo = elem (Foo.fooName foo, Foo.fooAge foo) target++    coverSearchResultCases isMatch foos++    assertEachManyResult target results $ \(name, age) maybeFoo ->+      assertMatchIsFromPredicate+        maybeFoo+        (\foo -> Foo.hasName name foo && Foo.hasAge age foo && isMatch foo)+        foos+ prop_findMaybeOneWhere :: Property.NamedDBProperty prop_findMaybeOneWhere =   Property.namedDBProperty "findMaybeOneWhere finds a row where the field matches (if any), with the given condition" $ \pool -> do@@ -131,7 +187,7 @@           Foo.fooNameField           (Orville.fieldGreaterThan Foo.fooAgeField Foo.averageFooAge) -    (targetName, foos) <- HH.forAll generateSearchTargetAndSubjects+    (targetName, _, foos) <- HH.forAll generateSearchTargetAndSubjects     maybeResult <-       Foo.withTable pool $ do         traverse_ (Orville.insertEntities Foo.table) (NEL.nonEmpty foos)@@ -143,6 +199,29 @@     coverSearchResultCases isMatch foos     assertMatchIsFromPredicate maybeResult isMatch foos +prop_findMaybeOneWhereByMarshaller :: Property.NamedDBProperty+prop_findMaybeOneWhereByMarshaller =+  Property.namedDBProperty "findMaybeOneWhereByMarshaller finds a row where the marshaller matches (if any), with the given condition" $ \pool -> do+    let+      plan :: Plan.Plan scope (Foo.FooName, Foo.FooAge) (Maybe Foo.Foo)+      plan =+        Plan.findMaybeOneWhereByMarshaller+          Foo.table+          Foo.nameAndAgeMarshaller+          (Orville.fieldGreaterThan Foo.fooAgeField Foo.averageFooAge)++    (targetName, targetAge, foos) <- HH.forAll generateSearchTargetAndSubjects+    maybeResult <-+      Foo.withTable pool $ do+        traverse_ (Orville.insertEntities Foo.table) (NEL.nonEmpty foos)+        Plan.execute plan (targetName, targetAge)++    let+      isMatch foo = Foo.hasName targetName foo && Foo.hasAge targetAge foo && Foo.fooAge foo > Foo.averageFooAge++    coverSearchResultCases isMatch foos+    assertMatchIsFromPredicate maybeResult isMatch foos+ prop_planMany_findMaybeOneWhere :: Property.NamedDBProperty prop_planMany_findMaybeOneWhere =   Property.namedDBProperty "(planMany findMaybeOneWhere) finds a row where the field matches for each input, with the given condition" $ \pool -> do@@ -155,7 +234,7 @@             Foo.fooNameField             (Orville.fieldGreaterThan Foo.fooAgeField Foo.averageFooAge) -    (targetNames, foos) <- HH.forAll generateSearchTargetListAndSubjects+    (targetNames, _, foos) <- HH.forAll generateSearchTargetListAndSubjects     results <-       Foo.withTable pool $ do         traverse_ (Orville.insertEntities Foo.table) (NEL.nonEmpty foos)@@ -174,6 +253,41 @@         (\foo -> Foo.hasName targetName foo && isMatch foo)         foos +prop_planMany_findMaybeOneWhereByMarshaller :: Property.NamedDBProperty+prop_planMany_findMaybeOneWhereByMarshaller =+  Property.namedDBProperty "(planMany findMaybeOneWhereByMarshaller) finds a row where the marshaller matches for each input, with the given condition" $ \pool -> do+    let+      plan :: Plan.Plan scope [(Foo.FooName, Foo.FooAge)] (Many.Many (Foo.FooName, Foo.FooAge) (Maybe Foo.Foo))+      plan =+        Plan.planMany $+          Plan.findMaybeOneWhereByMarshaller+            Foo.table+            Foo.nameAndAgeMarshaller+            (Orville.fieldGreaterThan Foo.fooAgeField Foo.averageFooAge)++    (targetNames, targetAges, foos) <- HH.forAll generateSearchTargetListAndSubjects++    let+      target = zip targetNames targetAges++    results <-+      Foo.withTable pool $ do+        traverse_ (Orville.insertEntities Foo.table) (NEL.nonEmpty foos)+        Plan.execute plan target++    let+      isMatch foo =+        elem (Foo.fooName foo, Foo.fooAge foo) target+          && Foo.fooAge foo > Foo.averageFooAge++    coverSearchResultCases isMatch foos++    assertEachManyResult target results $ \(name, age) maybeFoo ->+      assertMatchIsFromPredicate+        maybeFoo+        (\foo -> Foo.hasName name foo && Foo.hasAge age foo && isMatch foo)+        foos+ prop_findAll :: Property.NamedDBProperty prop_findAll =   Property.namedDBProperty "findAll finds all rows where the field matches" $ \pool -> do@@ -181,7 +295,7 @@       plan :: Plan.Plan scope Foo.FooName [Foo.Foo]       plan = Plan.findAll Foo.table Foo.fooNameField -    (targetName, foos) <- HH.forAll generateSearchTargetAndSubjects+    (targetName, _, foos) <- HH.forAll generateSearchTargetAndSubjects     results <-       Foo.withTable pool $ do         traverse_ (Orville.insertEntities Foo.table) (NEL.nonEmpty foos)@@ -193,14 +307,33 @@     coverSearchResultCases isMatch foos     assertAllMatchesFound Foo.fooId results isMatch foos +prop_findAllByMarshaller :: Property.NamedDBProperty+prop_findAllByMarshaller =+  Property.namedDBProperty "findAllByMarshaller finds all rows where the marshaller matches" $ \pool -> do+    let+      plan :: Plan.Plan scope (Foo.FooName, Foo.FooAge) [Foo.Foo]+      plan = Plan.findAllByMarshaller Foo.table Foo.nameAndAgeMarshaller++    (targetName, targetAge, foos) <- HH.forAll generateSearchTargetAndSubjects+    results <-+      Foo.withTable pool $ do+        traverse_ (Orville.insertEntities Foo.table) (NEL.nonEmpty foos)+        Plan.execute plan (targetName, targetAge)++    let+      isMatch foo = Foo.hasName targetName foo && Foo.hasAge targetAge foo++    coverSearchResultCases isMatch foos+    assertAllMatchesFound Foo.fooId results isMatch foos+ prop_planMany_findAll :: Property.NamedDBProperty prop_planMany_findAll =-  Property.namedDBProperty "(planMany findAll) finds all rows where the field matches for each list of inputs" $ \pool -> do+  Property.namedDBProperty "(planMany findAll) finds all rows where the field matches for each element in a list of inputs" $ \pool -> do     let       plan :: Plan.Plan scope [Foo.FooName] (Many.Many Foo.FooName [Foo.Foo])       plan = Plan.planMany (Plan.findAll Foo.table Foo.fooNameField) -    (targetNames, foos) <- HH.forAll generateSearchTargetListAndSubjects+    (targetNames, _, foos) <- HH.forAll generateSearchTargetListAndSubjects     results <-       Foo.withTable pool $ do         traverse_ (Orville.insertEntities Foo.table) (NEL.nonEmpty foos)@@ -213,6 +346,55 @@     assertEachManyResult targetNames results $ \targetName foundFoos ->       assertAllMatchesFound Foo.fooId foundFoos (\foo -> Foo.hasName targetName foo && isMatch foo) foos +prop_planTraversable_Map_findAll :: Property.NamedDBProperty+prop_planTraversable_Map_findAll =+  Property.namedDBProperty "(planTraversable findAll) finds all rows where the field matches for each element in a map of inputs" $ \pool -> do+    let+      plan :: Plan.Plan scope (Map.Map Foo.FooName Foo.FooName) (Map.Map Foo.FooName [Foo.Foo])+      plan = Plan.planTraversable (Plan.findAll Foo.table Foo.fooNameField)++    (targetNames, _, foos) <- HH.forAll generateSearchTargetListAndSubjects+    let+      targetNamesMap = Map.fromList $ Monad.join zip targetNames+    results <-+      Foo.withTable pool $ do+        traverse_ (Orville.insertEntities Foo.table) (NEL.nonEmpty foos)+        Plan.execute plan targetNamesMap++    let+      isMatch foo = elem (Foo.fooName foo) targetNames++    coverSearchResultCases isMatch foos+    length targetNamesMap === length results+    traverse_+      ( \targetName -> do+          Just foundFoos <- pure $ Map.lookup targetName results+          assertAllMatchesFound Foo.fooId foundFoos (\foo -> Foo.hasName targetName foo && isMatch foo) foos+      )+      targetNames++prop_planMany_findAllByMarshaller :: Property.NamedDBProperty+prop_planMany_findAllByMarshaller =+  Property.namedDBProperty "(planMany findAllByMarshaller) finds all rows where the marshaller matches for each list of inputs" $ \pool -> do+    let+      plan :: Plan.Plan scope [(Foo.FooName, Foo.FooAge)] (Many.Many (Foo.FooName, Foo.FooAge) [Foo.Foo])+      plan = Plan.planMany (Plan.findAllByMarshaller Foo.table Foo.nameAndAgeMarshaller)++    (targetNames, targetAges, foos) <- HH.forAll generateSearchTargetListAndSubjects+    let+      target = zip targetNames targetAges+    results <-+      Foo.withTable pool $ do+        traverse_ (Orville.insertEntities Foo.table) (NEL.nonEmpty foos)+        Plan.execute plan target++    let+      isMatch foo = elem (Foo.fooName foo, Foo.fooAge foo) target++    coverSearchResultCases isMatch foos+    assertEachManyResult target results $ \(name, age) foundFoos ->+      assertAllMatchesFound Foo.fooId foundFoos (\foo -> Foo.hasName name foo && Foo.hasAge age foo) foos+ prop_findAllWhere :: Property.NamedDBProperty prop_findAllWhere =   Property.namedDBProperty "findAllWhere finds all rows where the field matches, with the given condition" $ \pool -> do@@ -224,7 +406,7 @@           Foo.fooNameField           (Orville.fieldGreaterThan Foo.fooAgeField Foo.averageFooAge) -    (targetName, foos) <- HH.forAll generateSearchTargetAndSubjects+    (targetName, _, foos) <- HH.forAll generateSearchTargetAndSubjects     results <-       Foo.withTable pool $ do         traverse_ (Orville.insertEntities Foo.table) (NEL.nonEmpty foos)@@ -236,9 +418,32 @@     coverSearchResultCases isMatch foos     assertAllMatchesFound Foo.fooId results isMatch foos +prop_findAllWhereByMarshaller :: Property.NamedDBProperty+prop_findAllWhereByMarshaller =+  Property.namedDBProperty "findAllWhereByMarshaller finds all rows where the marshaller matches, with the given condition" $ \pool -> do+    let+      plan :: Plan.Plan scope (Foo.FooName, Foo.FooAge) [Foo.Foo]+      plan =+        Plan.findAllWhereByMarshaller+          Foo.table+          Foo.nameAndAgeMarshaller+          (Orville.fieldGreaterThan Foo.fooAgeField Foo.averageFooAge)++    (targetName, targetAge, foos) <- HH.forAll generateSearchTargetAndSubjects+    results <-+      Foo.withTable pool $ do+        traverse_ (Orville.insertEntities Foo.table) (NEL.nonEmpty foos)+        Plan.execute plan (targetName, targetAge)++    let+      isMatch foo = Foo.hasName targetName foo && Foo.hasAge targetAge foo && Foo.fooAge foo > Foo.averageFooAge++    coverSearchResultCases isMatch foos+    assertAllMatchesFound Foo.fooId results isMatch foos+ prop_planMany_findAllWhere :: Property.NamedDBProperty prop_planMany_findAllWhere =-  Property.namedDBProperty "(planMany findAllWhere) finds all rows where the field matches for each list of inputs, with the given condition" $ \pool -> do+  Property.namedDBProperty "(planMany findAllWhere) finds all rows where the field matches for each element in a list of inputs, with the given condition" $ \pool -> do     let       plan :: Plan.Plan scope [Foo.FooName] (Many.Many Foo.FooName [Foo.Foo])       plan =@@ -248,7 +453,7 @@             Foo.fooNameField             (Orville.fieldGreaterThan Foo.fooAgeField Foo.averageFooAge) -    (targetNames, foos) <- HH.forAll generateSearchTargetListAndSubjects+    (targetNames, _, foos) <- HH.forAll generateSearchTargetListAndSubjects     results <-       Foo.withTable pool $ do         traverse_ (Orville.insertEntities Foo.table) (NEL.nonEmpty foos)@@ -261,6 +466,33 @@     assertEachManyResult targetNames results $ \targetName foundFoos ->       assertAllMatchesFound Foo.fooId foundFoos (\foo -> Foo.hasName targetName foo && isMatch foo) foos +prop_planMany_findAllWhereByMarshaller :: Property.NamedDBProperty+prop_planMany_findAllWhereByMarshaller =+  Property.namedDBProperty "(planMany findAllWhereByMarshaller) finds all rows where the marshaller matches for each list of inputs, with the given condition" $ \pool -> do+    let+      plan :: Plan.Plan scope [(Foo.FooName, Foo.FooAge)] (Many.Many (Foo.FooName, Foo.FooAge) [Foo.Foo])+      plan =+        Plan.planMany $+          Plan.findAllWhereByMarshaller+            Foo.table+            Foo.nameAndAgeMarshaller+            (Orville.fieldGreaterThan Foo.fooAgeField Foo.averageFooAge)++    (targetNames, targetAges, foos) <- HH.forAll generateSearchTargetListAndSubjects+    let+      target = zip targetNames targetAges+    results <-+      Foo.withTable pool $ do+        traverse_ (Orville.insertEntities Foo.table) (NEL.nonEmpty foos)+        Plan.execute plan target++    let+      isMatch foo = elem (Foo.fooName foo, Foo.fooAge foo) target && Foo.fooAge foo > Foo.averageFooAge++    coverSearchResultCases isMatch foos+    assertEachManyResult target results $ \(name, age) foundFoos ->+      assertAllMatchesFound Foo.fooId foundFoos (\foo -> Foo.hasName name foo && Foo.hasAge age foo && isMatch foo) foos+ prop_planEither :: Property.NamedDBProperty prop_planEither =   Property.namedDBProperty "planEither executes either the right or left plan based on input" $ \pool -> do@@ -494,48 +726,46 @@           , "SELECT \"foo_id\",\"id\",\"foo_id\" FROM \"foo_child\" WHERE (\"foo_id\") = ($1)"           ] -{- |-  Generates a list of Foos that along with FooName that could plausibly be-  found in the list zero, one or more times.+{- | Generates a list of Foos that along with FooName and FooAge that could plausibly be found in the+  list zero, one or more times.  @since 1.0.0.0 -}-generateSearchTargetAndSubjects :: HH.Gen (Foo.FooName, [Foo.Foo])+generateSearchTargetAndSubjects :: HH.Gen (Foo.FooName, Foo.FooAge, [Foo.Foo]) generateSearchTargetAndSubjects = do   targetName <- Foo.generateFooName+  targetAge <- Foo.generateFooAge    let     generatePossibleTargetFoo =       Gen.choice-        [ Foo.generateFooWithName targetName+        [ Foo.generateFooWithNameAndAge targetName targetAge         , Foo.generate         ]    foos <- Foo.generateListUsing (Range.linear 0 5) generatePossibleTargetFoo-  pure (targetName, foos)+  pure (targetName, targetAge, foos) -{- |-  Generates a list of Foos that along with FooName that could plausibly be-  found in the list zero, one or more times.+{- | Generates a list of Foos that along with FooName that could plausibly be found in the list zero,+  one or more times.  @since 1.0.0.0 -}-generateSearchTargetListAndSubjects :: HH.Gen ([Foo.FooName], [Foo.Foo])+generateSearchTargetListAndSubjects :: HH.Gen ([Foo.FooName], [Foo.FooAge], [Foo.Foo]) generateSearchTargetListAndSubjects = do   targetNames <- Gen.list (Range.linear 0 5) Foo.generateFooName+  targetAges <- Gen.list (Range.linear 0 5) Foo.generateFooAge    let     generatePossibleTargetFoo =-      Gen.choice (Foo.generate : fmap Foo.generateFooWithName targetNames)+      Gen.choice (Foo.generate : fmap (uncurry Foo.generateFooWithNameAndAge) (zip targetNames targetAges))    foos <- Foo.generateListUsing (Range.linear 0 5) generatePossibleTargetFoo-  pure (targetNames, foos)+  pure (targetNames, targetAges, foos) -{- |-  Uses Hedgehog's cover function to make sure common edge cases are covered-  for tests that are conducting a search. The predicate given should indicate-  whether the item would be expected to match the search being tested. The-  list of items should be the list that will be searced against.+{- | Uses Hedgehog's cover function to make sure common edge cases are covered for tests that are+  conducting a search. The predicate given should indicate whether the item would be expected to+  match the search being tested. The list of items should be the list that will be searced against.  @since 1.0.0.0 -}@@ -545,12 +775,10 @@   HH.cover 1 (String.fromString "subjects present with no matches") (not (null subjects) && not (any predicate subjects))   HH.cover 1 (String.fromString "at least 1 match") (length (filter predicate subjects) >= 1) -{- |-  Asserts that the given result is one that could have been produced by apply-  the predicate to the given list to find a single result. This assertion-  doesn't care which particular item from the list was found, as long as it the-  result matches the predicate and it is one from the list, or that nothing in-  the list matches if the 'Nothing' was found.+{- | Asserts that the given result is one that could have been produced by apply the predicate to the+  given list to find a single result. This assertion doesn't care which particular item from the+  list was found, as long as it the result matches the predicate and it is one from the list, or+  that nothing in the list matches if the 'Nothing' was found.  @since 1.0.0.0 -}@@ -567,9 +795,8 @@     Just entity ->       HH.assert (predicate entity && elem entity subjects) -{- |-  Asserts that the found items are all of those from the list that match the-  predicate, but without caring about order.+{- | Asserts that the found items are all of those from the list that match the predicate, but without+  caring about order.  @since 1.0.0.0 -}@@ -587,9 +814,8 @@ assertAllMatchesFound keyAttr foundEntities predicate allEntities =   List.sortOn keyAttr foundEntities === List.sortOn keyAttr (filter predicate allEntities) -{- |-  Applies the given assertion for every key in the list. If any of the key-  is not found in the provided 'Many.Many' value, this assertion will fail.+{- | Applies the given assertion for every key in the list. If any of the key is not found in the+  provided 'Many.Many' value, this assertion will fail.  @since 1.0.0.0 -}
test/Test/RawSql.hs view
@@ -5,6 +5,7 @@  import qualified Data.ByteString.Char8 as B8 import Data.Functor.Identity (runIdentity)+import qualified Data.List.NonEmpty as NE import qualified Data.Text as T import Hedgehog ((===)) import qualified Hedgehog as HH@@ -58,24 +59,32 @@           <> RawSql.fromString "WHERE id = "           <> RawSql.parameter (SqlValue.fromInt32 1)           <> RawSql.fromString " AND "-          <> RawSql.fromString "bar IN ("+          <> RawSql.fromString "(bar, baz, id) IN ("           <> RawSql.intercalate RawSql.comma bars           <> RawSql.fromString ")"        bars =         map-          RawSql.parameter-          [ SqlValue.fromText (T.pack "pants")-          , SqlValue.fromText (T.pack "cheese")+          (RawSql.parameter . SqlValue.fromRow . NE.fromList)+          [ [SqlValue.fromText (T.pack "pants"), SqlValue.fromText (T.pack "ants"), SqlValue.fromInt32 2]+          , [SqlValue.fromText (T.pack "cheese"), SqlValue.fromText (T.pack "louise"), SqlValue.fromInt32 3]+          , [SqlValue.fromText (T.pack "lasagna"), SqlValue.fromText (T.pack "banana"), SqlValue.fromInt32 4]           ]        expectedBytes =-        B8.pack "SELECT * FROM foo WHERE id = $1 AND bar IN ($2,$3)"+        B8.pack "SELECT * FROM foo WHERE id = $1 AND (bar, baz, id) IN (($2,$3,$4),($5,$6,$7),($8,$9,$10))"        expectedParams =-        [ Just . PgTextFormatValue.fromByteString . B8.pack $ "1"-        , Just . PgTextFormatValue.fromByteString . B8.pack $ "pants"-        , Just . PgTextFormatValue.fromByteString . B8.pack $ "cheese"+        [ Just . PgTextFormatValue.fromByteString $ B8.pack "1"+        , Just . PgTextFormatValue.fromByteString $ B8.pack "pants"+        , Just . PgTextFormatValue.fromByteString $ B8.pack "ants"+        , Just . PgTextFormatValue.fromByteString $ B8.pack "2"+        , Just . PgTextFormatValue.fromByteString $ B8.pack "cheese"+        , Just . PgTextFormatValue.fromByteString $ B8.pack "louise"+        , Just . PgTextFormatValue.fromByteString $ B8.pack "3"+        , Just . PgTextFormatValue.fromByteString $ B8.pack "lasagna"+        , Just . PgTextFormatValue.fromByteString $ B8.pack "banana"+        , Just . PgTextFormatValue.fromByteString $ B8.pack "4"         ]        (actualBytes, actualParams) =
test/Test/SelectOptions.hs view
@@ -13,6 +13,7 @@ import Orville.PostgreSQL ((.&&), (./=), (.<), (.<-), (.</-), (.<=), (.==), (.>), (.>=), (.||)) import qualified Orville.PostgreSQL as O import qualified Orville.PostgreSQL.Expr as Expr+import qualified Orville.PostgreSQL.Marshall as Marshall import qualified Orville.PostgreSQL.Marshall.FieldDefinition as FieldDef import qualified Orville.PostgreSQL.Raw.RawSql as RawSql import qualified Test.Property as Property@@ -26,6 +27,8 @@     , prop_fieldEqualsOperator     , prop_fieldNotEquals     , prop_fieldNotEqualsOperator+    , prop_fieldIsDistinctFrom+    , prop_fieldIsNotDistinctFrom     , prop_fieldLessThan     , prop_fieldLessThanOperator     , prop_fieldGreaterThan@@ -51,9 +54,11 @@     , prop_fieldNotInOperator     , prop_distinct     , prop_orderBy+    , prop_orderByQualified     , prop_orderByCombined     , prop_groupBy     , prop_groupByCombined+    , prop_forRowLock     ]  prop_emptyWhereClause :: Property.NamedProperty@@ -84,6 +89,20 @@       (Just "WHERE (\"foo\") <> ($1)")       (O.where_ $ O.fieldNotEquals fooField 0) +prop_fieldIsDistinctFrom :: Property.NamedProperty+prop_fieldIsDistinctFrom =+  Property.singletonNamedProperty "fieldIsDistinctFrom generates expected sql" $+    assertWhereClauseEquals+      (Just "WHERE (\"foo\") IS DISTINCT FROM ($1)")+      (O.where_ $ O.fieldIsDistinctFrom fooField 0)++prop_fieldIsNotDistinctFrom :: Property.NamedProperty+prop_fieldIsNotDistinctFrom =+  Property.singletonNamedProperty "fieldIsNotDistinctFrom generates expected sql" $+    assertWhereClauseEquals+      (Just "WHERE (\"foo\") IS NOT DISTINCT FROM ($1)")+      (O.where_ $ O.fieldIsNotDistinctFrom fooField 0)+ prop_fieldNotEqualsOperator :: Property.NamedProperty prop_fieldNotEqualsOperator =   Property.singletonNamedProperty "./= generates expected sql" $@@ -294,12 +313,23 @@           )       ) +prop_orderByQualified :: Property.NamedProperty+prop_orderByQualified =+  Property.singletonNamedProperty "orderBy generates expected sql" $+    assertOrderByClauseEquals+      (Just "ORDER BY \"f\".\"foo\" ASC, \"bar\" DESC")+      ( O.orderBy+          ( O.orderBySqlComparable (O.qualifyField (Marshall.stringToAliasName "f") fooField) Expr.ascendingOrder+              <> O.orderByField barField Expr.descendingOrder+          )+      )+ prop_orderByCombined :: Property.NamedProperty prop_orderByCombined =   Property.singletonNamedProperty "orderBy generates expected sql with multiple selectOptions" $     assertOrderByClauseEquals       (Just "ORDER BY \"foo\" ASC, \"bar\" DESC")-      ( (O.orderBy $ O.orderByColumnName (Expr.columnName "foo") O.ascendingOrder)+      ( (O.orderBy $ O.orderByColumnName (Expr.unqualified (Expr.columnName "foo")) O.ascendingOrder)           <> (O.orderBy $ O.orderByField barField O.descendingOrder)       ) @@ -309,7 +339,8 @@     assertGroupByClauseEquals       (Just "GROUP BY \"foo\", \"bar\"")       ( O.groupBy . Expr.groupByColumnsExpr $-          FieldDef.fieldColumnName fooField :| [FieldDef.fieldColumnName barField]+          Expr.unqualified (FieldDef.fieldColumnName fooField)+            :| [Expr.unqualified (FieldDef.fieldColumnName barField)]       )  prop_groupByCombined :: Property.NamedProperty@@ -318,9 +349,16 @@     assertGroupByClauseEquals       (Just "GROUP BY foo, \"bar\"")       ( (O.groupBy . RawSql.unsafeSqlExpression $ "foo")-          <> (O.groupBy . Expr.groupByColumnsExpr $ (FieldDef.fieldColumnName barField :| []))+          <> (O.groupBy . Expr.groupByColumnsExpr $ ((Expr.unqualified (FieldDef.fieldColumnName barField)) :| []))       ) +prop_forRowLock :: Property.NamedProperty+prop_forRowLock =+  Property.singletonNamedProperty "forRowLock generates expected sql" $+    assertRowLockingClauseEquals+      (Just "FOR UPDATE SKIP LOCKED")+      (O.forRowLock $ Expr.rowLockingClause Expr.updateStrength Nothing (Just Expr.skipLockedRowLockingOption))+ assertDistinctEquals :: (HH.MonadTest m, HasCallStack) => String -> O.SelectOptions -> m () assertDistinctEquals mbDistinct selectOptions =   withFrozenCallStack $@@ -340,6 +378,11 @@ assertGroupByClauseEquals mbGroupByClause selectOptions =   withFrozenCallStack $     fmap RawSql.toExampleBytes (O.selectGroupByClause selectOptions) HH.=== fmap B8.pack mbGroupByClause++assertRowLockingClauseEquals :: (HH.MonadTest m, HasCallStack) => Maybe String -> O.SelectOptions -> m ()+assertRowLockingClauseEquals mbRowLockingClause selectOptions =+  withFrozenCallStack $+    fmap RawSql.toExampleBytes (O.selectRowLockingClause selectOptions) HH.=== fmap B8.pack mbRowLockingClause  fooField :: FieldDef.FieldDefinition FieldDef.NotNull Int.Int32 fooField =
test/Test/SqlCommenter.hs view
@@ -6,6 +6,7 @@ import qualified Control.Monad.IO.Class as MIO import qualified Data.ByteString.Char8 as B8 import Data.Functor.Identity (runIdentity)+import qualified Data.List.NonEmpty as NE import qualified Data.Map.Strict as Map import qualified Data.Text as T import Hedgehog ((===))@@ -62,7 +63,7 @@ prop_sqlCommenterInsertExpr =   Property.singletonNamedDBProperty "sqlcommenter support does not impact ability of insertExpr inserting values" $ \pool -> do     let-      fooBars = [mkFooBar 1 "dog", mkFooBar 2 "cat"]+      fooBars = NE.fromList [mkFooBar 1 "dog", mkFooBar 2 "cat"]      rows <-       MIO.liftIO $@@ -70,7 +71,7 @@           dropAndRecreateTestTable connection            let-            insertExpr = Expr.insertExpr fooBarTable Nothing (insertFooBarSource fooBars) Nothing+            insertExpr = Expr.insertExpr fooBarTable Nothing (insertFooBarSource fooBars) Nothing Nothing           RawSql.executeVoid connection $             SqlCommenter.addSqlCommenterAttributes staticSqlCommenterAttributes insertExpr @@ -78,7 +79,7 @@            Execution.readRows result -    assertEqualFooBarRows rows fooBars+    assertEqualFooBarRows rows (NE.toList fooBars)  prop_sqlCommenterOrvilleState :: Property.NamedDBProperty prop_sqlCommenterOrvilleState =
test/Test/SqlMarshaller.hs view
@@ -8,6 +8,7 @@ import qualified Data.ByteString.Char8 as B8 import qualified Data.Either as Either import qualified Data.Int as Int+import qualified Data.List.NonEmpty as NE import qualified Data.Set as Set import qualified Data.String as String import qualified Data.Text as T@@ -18,10 +19,13 @@  import qualified Orville.PostgreSQL.ErrorDetailLevel as ErrorDetailLevel import qualified Orville.PostgreSQL.Execution.ExecutionResult as Result+import qualified Orville.PostgreSQL.Expr as Expr import qualified Orville.PostgreSQL.Marshall as Marshall+import qualified Orville.PostgreSQL.Raw.RawSql as RawSql import qualified Orville.PostgreSQL.Raw.SqlValue as SqlValue  import Test.Expr.TestSchema (assertEqualSqlRows)+import Test.Orphans () import qualified Test.PgGen as PgGen import qualified Test.Property as Property @@ -35,10 +39,13 @@     , prop_marshallField_missingColumn     , prop_marshallField_decodeValueFailure     , prop_marshallResultFromSql_Foo+    , prop_marshallResultFromSql_FooWithQualifier     , prop_marshallResultFromSql_Bar     , prop_foldMarshallerFields     , prop_passMaybeThrough     , prop_partialMap+    , prop_toComparableSqlValue+    , prop_referenceValueExpression     ]  property_returnPureValue :: Property.NamedProperty@@ -161,6 +168,26 @@     result <- marshallTestRowFromSql fooMarshaller input     Bifunctor.first show result === Right foos +prop_marshallResultFromSql_FooWithQualifier :: Property.NamedProperty+prop_marshallResultFromSql_FooWithQualifier =+  Property.namedProperty "marshallResultFromSql decodes all rows in Foo result set, when given an alias for each field" $ do+    foos <- HH.forAll $ Gen.list (Range.linear 0 10) generateFoo++    let+      mkRowValues foo =+        [ SqlValue.fromText (fooName foo)+        , SqlValue.fromInt32 (fooSize foo)+        , maybe SqlValue.sqlNull SqlValue.fromBool (fooOption foo)+        ]++      input =+        Result.mkFakeLibPQResult+          [B8.pack "name", B8.pack "size", B8.pack "option"]+          (map mkRowValues foos)++    result <- marshallTestRowFromSql fooMarshallerWithQualifierOnEachField input+    Bifunctor.first show result === Right foos+ prop_marshallResultFromSql_Bar :: Property.NamedProperty prop_marshallResultFromSql_Bar =   Property.namedProperty "marshallResultFromSql decodes all rows in Bar result set" $ do@@ -189,9 +216,9 @@     let       addField entry fields =         case entry of-          Marshall.Natural fieldDef (Just getValue) ->+          Marshall.Natural _ fieldDef (Just getValue) ->             (Marshall.fieldName fieldDef, Marshall.fieldValueToSqlValue fieldDef (getValue foo)) : fields-          Marshall.Natural _ Nothing ->+          Marshall.Natural _ _ Nothing ->             fields           Marshall.Synthetic _ ->             fields@@ -271,6 +298,36 @@     result <- marshallTestRowFromSql marshaller input     Bifunctor.first show result === expected +prop_toComparableSqlValue :: Property.NamedProperty+prop_toComparableSqlValue =+  Property.namedProperty "toComparableSqlValue encodes the marshaller's write entity as a row value" $ do+    foo <- HH.forAll generateFoo++    let+      nameVal = SqlValue.fromText $ fooName foo+      sizeVal = SqlValue.fromInt32 $ fooSize foo+      optionVal = maybe SqlValue.sqlNull SqlValue.fromBool $ fooOption foo+      expected = SqlValue.fromRow $ NE.fromList [nameVal, sizeVal, optionVal]+      actual = Marshall.toComparableSqlValue fooMarshaller foo++    actual === expected++prop_referenceValueExpression :: Property.NamedProperty+prop_referenceValueExpression =+  Property.singletonNamedProperty "referenceValueExpression returns a row value containing references to the marshaller's write fields" $ do+    let+      alias = Marshall.stringToAliasName "f"+      expected =+        Expr.rowValueConstructor $+          NE.fromList+            [ Expr.columnReference . Expr.untrackQualified . Marshall.qualifiedFieldColumnName $ Marshall.qualifyField alias nameField+            , Expr.columnReference . Expr.untrackQualified . Marshall.qualifiedFieldColumnName $ Marshall.qualifyField alias sizeField+            , Expr.columnReference . Expr.untrackQualified . Marshall.qualifiedFieldColumnName $ Marshall.qualifyField alias optionField+            ]+      actual = Marshall.referenceValueExpression (Marshall.marshallQualifyFields alias fooMarshaller)++    RawSql.toExampleBytes actual === RawSql.toExampleBytes expected+ data Foo = Foo   { fooName :: T.Text   , fooSize :: Int.Int32@@ -288,9 +345,29 @@ fooMarshaller :: Marshall.SqlMarshaller Foo Foo fooMarshaller =   Foo-    <$> Marshall.marshallField fooName (Marshall.unboundedTextField "name")-    <*> Marshall.marshallField fooSize (Marshall.integerField "size")-    <*> Marshall.marshallField fooOption (Marshall.nullableField $ Marshall.booleanField "option")+    <$> Marshall.marshallField fooName nameField+    <*> Marshall.marshallField fooSize sizeField+    <*> Marshall.marshallField fooOption optionField++fooMarshallerWithQualifierOnEachField :: Marshall.SqlMarshaller Foo Foo+fooMarshallerWithQualifierOnEachField =+  Marshall.marshallQualifyFields (Marshall.stringToAliasName "f") $+    Foo+      <$> Marshall.marshallQualifyFields (Marshall.stringToAliasName "f") (Marshall.marshallField fooName nameField)+      <*> Marshall.marshallQualifyFields (Marshall.stringToAliasName "f") (Marshall.marshallField fooSize sizeField)+      <*> Marshall.marshallQualifyFields (Marshall.stringToAliasName "f") (Marshall.marshallField fooOption optionField)++nameField :: Marshall.FieldDefinition Marshall.NotNull T.Text+nameField =+  Marshall.unboundedTextField "name"++sizeField :: Marshall.FieldDefinition Marshall.NotNull Int.Int32+sizeField =+  Marshall.integerField "size"++optionField :: Marshall.FieldDefinition Marshall.Nullable (Maybe Bool)+optionField =+  Marshall.nullableField $ Marshall.booleanField "option"  generateFoo :: HH.Gen Foo generateFoo =
test/Test/SqlType.hs view
@@ -370,6 +370,26 @@           , expectedValue = Time.fromGregorian 10000 12 21           }     )+  ,+    ( String.fromString "Testing the decode of DATE with value 0001-12-21 BC"+    , runDecodingTest pool $+        DecodingTest+          { sqlTypeDDL = "DATE"+          , rawSqlValue = Just $ B8.pack "'0001-12-21 BC'"+          , sqlType = SqlType.date+          , expectedValue = Time.fromGregorian 0 12 21+          }+    )+  ,+    ( String.fromString "Testing the decode of DATE with value 0041-02-29 BC"+    , runDecodingTest pool $+        DecodingTest+          { sqlTypeDDL = "DATE"+          , rawSqlValue = Just $ B8.pack "'0041-02-29 BC'"+          , sqlType = SqlType.date+          , expectedValue = Time.fromGregorian (-40) 2 29+          }+    )   ]  timestampTests :: Orville.ConnectionPool -> [(HH.PropertyName, HH.Property)]@@ -445,6 +465,26 @@           }     )   ,+    ( String.fromString "Testing the decode of TIMESTAMP WITH TIME ZONE with value '0001-12-21 00:00:32.000+00 BC'"+    , runDecodingTest pool $+        DecodingTest+          { sqlTypeDDL = "TIMESTAMP WITH TIME ZONE"+          , rawSqlValue = Just $ B8.pack "'0001-12-21 00:00:32.000+00 BC'"+          , sqlType = SqlType.timestamp+          , expectedValue = Time.UTCTime (Time.fromGregorian 0 12 21) (Time.secondsToDiffTime 32)+          }+    )+  ,+    ( String.fromString "Testing the decode of TIMESTAMP WITH TIME ZONE with value '0041-02-29 00:00:32.000+00 BC'"+    , runDecodingTest pool $+        DecodingTest+          { sqlTypeDDL = "TIMESTAMP WITH TIME ZONE"+          , rawSqlValue = Just $ B8.pack "'0041-02-29 00:00:32.000+00 BC'"+          , sqlType = SqlType.timestamp+          , expectedValue = Time.UTCTime (Time.fromGregorian (-40) 2 29) (Time.secondsToDiffTime 32)+          }+    )+  ,     ( String.fromString "Testing the decode of TIMESTAMP WITHOUT TIME ZONE with value '2020-12-21 00:00:32'"     , runDecodingTest pool $         DecodingTest@@ -514,6 +554,26 @@           , expectedValue = Time.LocalTime (Time.fromGregorian 1 12 21) (Time.timeToTimeOfDay $ Time.secondsToDiffTime 32)           }     )+  ,+    ( String.fromString "Testing the decode of TIMESTAMP WITHOUT TIME ZONE with value '0001-12-21 00:00:32 BC'"+    , runDecodingTest pool $+        DecodingTest+          { sqlTypeDDL = "TIMESTAMP WITHOUT TIME ZONE"+          , rawSqlValue = Just $ B8.pack "'0001-12-21 00:00:32 BC'"+          , sqlType = SqlType.timestampWithoutZone+          , expectedValue = Time.LocalTime (Time.fromGregorian 0 12 21) (Time.timeToTimeOfDay $ Time.secondsToDiffTime 32)+          }+    )+  ,+    ( String.fromString "Testing the decode of TIMESTAMP WITHOUT TIME ZONE with value '0041-02-29 00:00:32 BC'"+    , runDecodingTest pool $+        DecodingTest+          { sqlTypeDDL = "TIMESTAMP WITHOUT TIME ZONE"+          , rawSqlValue = Just $ B8.pack "'0041-02-29 00:00:32 BC'"+          , sqlType = SqlType.timestampWithoutZone+          , expectedValue = Time.LocalTime (Time.fromGregorian (-40) 2 29) (Time.timeToTimeOfDay $ Time.secondsToDiffTime 32)+          }+    )   ]  data DecodingTest a = DecodingTest@@ -530,21 +590,30 @@       dropAndRecreateTable connection "decoding_test" (sqlTypeDDL test)        let-        tableName = Expr.qualifyTable Nothing (Expr.tableName "decoding_test")+        tableName = Expr.unqualified $ Expr.tableName "decoding_test"+        insertSource =+          Expr.valuesExprInsertSource+            . Expr.valuesExprFromValueExpressions+            . pure+            . pure+            . Expr.valueExpression+            . SqlValue.fromRawBytesNullable+            $ rawSqlValue test        RawSql.executeVoid connection $         Expr.insertExpr           tableName           Nothing-          (Expr.insertSqlValues [[SqlValue.fromRawBytesNullable (rawSqlValue test)]])+          insertSource           Nothing+          Nothing        result <-         RawSql.execute connection $           Expr.queryExpr             (Expr.selectClause $ Expr.selectExpr Nothing)             Expr.selectStar-            (Just $ Expr.tableExpr (Expr.referencesTable tableName) Nothing Nothing Nothing Nothing Nothing)+            (Just $ Expr.tableExpr (Expr.singleTableReferenceList tableName) Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing)        Execution.readRows result 
test/Test/TableDefinition.hs view
@@ -7,6 +7,7 @@ import qualified Control.Monad.IO.Class as MIO import qualified Data.ByteString.Char8 as B8 import Data.List.NonEmpty (NonEmpty ((:|)))+import qualified Data.List.NonEmpty as NE import qualified Data.Set as Set import qualified Data.Text as T import Hedgehog ((===))@@ -34,6 +35,7 @@     , prop_primaryKey pool     , prop_uniqueConstraint pool     , prop_fieldConstraints+    , prop_insertDefaultValuesWithEmptyMarshaller pool     ]  prop_roundTrip :: Property.NamedDBProperty@@ -46,7 +48,8 @@         TableDefinition.mkInsertExpr           ReturningOption.WithoutReturning           Foo.table-          (originalFoo :| [])+          Nothing+          (originalFoo NE.:| [])        selectFoos =         Select.selectTable Foo.table mempty@@ -67,7 +70,7 @@      let       insertBar =-        TableDefinition.mkInsertExpr ReturningOption.WithoutReturning Bar.table (originalBar :| [])+        TableDefinition.mkInsertExpr ReturningOption.WithoutReturning Bar.table Nothing (originalBar :| [])        selectBars =         Select.selectTable Bar.table mempty@@ -94,6 +97,7 @@         TableDefinition.mkInsertExpr           ReturningOption.WithoutReturning           Foo.table+          Nothing           (originalFoo :| [conflictingFoo])      result <- MIO.liftIO . E.try . Conn.withPoolConnection pool $ \connection -> do@@ -125,6 +129,7 @@         TableDefinition.mkInsertExpr           ReturningOption.WithoutReturning           Foo.table+          Nothing           (originalFoo :| [conflictingFoo])      result <- MIO.liftIO . E.try . Conn.withPoolConnection pool $ \connection -> do@@ -182,3 +187,33 @@         ConstraintDefinition.tableConstraintKeys . Orville.tableConstraints      tableConstraintKeys tableWithFieldConstraints === tableConstraintKeys tableWithTableConstraints++prop_insertDefaultValuesWithEmptyMarshaller :: Property.NamedDBProperty+prop_insertDefaultValuesWithEmptyMarshaller =+  Property.singletonNamedDBProperty "Inserts default values when table marshaller is empty" $ \pool -> do+    let+      table :: TableDefinition.TableDefinition (Orville.HasKey Bar.BarId) () ()+      table =+        TableDefinition.mapTableMarshaller (const $ pure ()) Bar.table+      entities =+        NE.fromList [(), (), ()]+      expectedResult =+        [ Bar.Bar 1 $ T.pack "default"+        , Bar.Bar 2 $ T.pack "default"+        , Bar.Bar 3 $ T.pack "default"+        ]++      insertExpr =+        TableDefinition.mkInsertExpr+          ReturningOption.WithoutReturning+          table+          Nothing+          entities++    result <- MIO.liftIO . Orville.runOrville pool $ do+      MIO.liftIO . Conn.withPoolConnection pool $ \connection -> do+        TestTable.dropAndRecreateTableDef connection Bar.table+        RawSql.executeVoid connection insertExpr+      Select.executeSelect $ Select.selectTable Bar.table mempty++    result === expectedResult
test/Test/TestTable.hs view
@@ -36,10 +36,10 @@   String ->   RawSql.RawSql dropTableNameSql =-  dropTableNameExprSql . Expr.qualifyTable Nothing . Expr.tableName+  dropTableNameExprSql . Expr.unqualified . Expr.tableName  dropTableNameExprSql ::-  Expr.Qualified Expr.TableName ->+  Expr.QualifiedOrUnqualified Expr.TableName ->   RawSql.RawSql dropTableNameExprSql name =   RawSql.fromString "DROP TABLE IF EXISTS " <> RawSql.toRawSql name
test/Test/Transaction.hs view
@@ -1,17 +1,25 @@+{-# LANGUAGE OverloadedStrings #-}+ module Test.Transaction   ( transactionTests   ) where +import Control.Exception (SomeException (..), catch) import qualified Control.Monad as Monad+import qualified Control.Monad.Trans.Reader as Reader import qualified Data.ByteString as BS import qualified Data.IORef as IORef import qualified Data.Text as T+import qualified Database.PostgreSQL.LibPQ as LibPQ import Hedgehog ((===)) import qualified Hedgehog as HH import qualified Hedgehog.Gen as Gen+import qualified UnliftIO+import qualified UnliftIO.Concurrent as Concurrent  import qualified Orville.PostgreSQL as Orville+import qualified Orville.PostgreSQL.Execution as Execution import qualified Orville.PostgreSQL.Expr as Expr import qualified Orville.PostgreSQL.OrvilleState as OrvilleState import qualified Orville.PostgreSQL.Raw.Connection as Conn@@ -25,11 +33,18 @@ transactionTests pool =   Property.group "Transaction" $     [ prop_transactionsWithoutExceptionsCommit pool+    , prop_transactionWithInstructionsToCommitCommit pool     , prop_exceptionsLeadToTransactionRollback pool-    , prop_savepointsRollbackInnerTransactions pool+    , prop_savepointsRollbackInnerTransactionsOnException pool+    , prop_savepointsAllowInnerTransactionsToRollback pool     , prop_callbacksMadeForTransactionCommit pool-    , prop_callbacksMadeForTransactionRollback pool+    , prop_callbacksMadeForTransactionRollbackByException pool+    , prop_callbacksMadeForTransactionRollbackByInstruction pool     , prop_usesCustomBeginTransactionSql pool+    , prop_inWithTransaction pool+    , prop_rollbackCallbackInInvalidTransaction pool+    , prop_transactionCleanupRunsAfterBeginTransactionFails pool+    , prop_transactionCleanupCannotBeInterrupted pool     ]  prop_transactionsWithoutExceptionsCommit :: Property.NamedDBProperty@@ -49,6 +64,25 @@      length tracers === nestingLevel +prop_transactionWithInstructionsToCommitCommit :: Property.NamedDBProperty+prop_transactionWithInstructionsToCommitCommit =+  Property.namedDBProperty "Transactions with instructions to commit, commit" $ \pool -> do+    nestingLevel <- HH.forAll TransactionUtil.genNestingLevel++    tracers <-+      HH.evalIO $ do+        Conn.withPoolConnection pool $ \connection ->+          TestTable.dropAndRecreateTableDef connection tracerTable++        Orville.runOrville pool $ do+          TransactionUtil.runNestedTransactionWithInstructions nestingLevel $ \_ -> do+            Monad.void $ Orville.insertEntity tracerTable Tracer+            pure Orville.Commit++          Orville.findEntitiesBy tracerTable mempty++    length tracers === nestingLevel+ prop_exceptionsLeadToTransactionRollback :: Property.NamedDBProperty prop_exceptionsLeadToTransactionRollback =   Property.namedDBProperty "Exceptions within transaction blocks execute rollbock" $ \pool -> do@@ -69,9 +103,9 @@      length tracers === 0 -prop_savepointsRollbackInnerTransactions :: Property.NamedDBProperty-prop_savepointsRollbackInnerTransactions =-  Property.namedDBProperty "Savepoints allow inner transactions to rollback while outer transactions commit" $ \pool -> do+prop_savepointsRollbackInnerTransactionsOnException :: Property.NamedDBProperty+prop_savepointsRollbackInnerTransactionsOnException =+  Property.namedDBProperty "Savepoints allow inner transactions to throw and rollback while outer transactions commit" $ \pool -> do     outerNestingLevel <- HH.forAll TransactionUtil.genNestingLevel     innerNestingLevel <- HH.forAll TransactionUtil.genNestingLevel @@ -98,6 +132,39 @@      length tracers === outerNestingLevel +prop_savepointsAllowInnerTransactionsToRollback :: Property.NamedDBProperty+prop_savepointsAllowInnerTransactionsToRollback =+  Property.namedDBProperty "Savepoints allow inner transactions to rollback while outer transactions commit" $ \pool -> do+    outerNestingLevel <- HH.forAll TransactionUtil.genNestingLevel+    innerNestingLevel <- HH.forAll TransactionUtil.genNestingLevel++    let+      innerActions =+        TransactionUtil.runNestedTransactionWithInstructions innerNestingLevel $ \level -> do+          _ <- Orville.insertEntity tracerTable Tracer+          -- Commit all the inner savepoints, but then rollback the first one so that+          -- nothing from this transaction set gets committed+          pure $+            if level > 1+              then Orville.Commit+              else Orville.Rollback++      outerActions =+        TransactionUtil.runNestedTransactions outerNestingLevel $ \level -> do+          _ <- Orville.insertEntity tracerTable Tracer+          Monad.when (level >= outerNestingLevel) innerActions++    tracers <-+      HH.evalIO $ do+        Conn.withPoolConnection pool $ \connection ->+          TestTable.dropAndRecreateTableDef connection tracerTable++        Orville.runOrville pool $ do+          outerActions+          Orville.findEntitiesBy tracerTable mempty++    length tracers === outerNestingLevel+ prop_callbacksMadeForTransactionCommit :: Property.NamedDBProperty prop_callbacksMadeForTransactionCommit =   Property.namedDBProperty "Callbacks are delivered for a transaction that is commited" $ \pool -> do@@ -116,9 +183,9 @@      allEvents === expectedEvents -prop_callbacksMadeForTransactionRollback :: Property.NamedDBProperty-prop_callbacksMadeForTransactionRollback =-  Property.namedDBProperty "Callbacks are delivered for a transaction this is rolled back" $ \pool -> do+prop_callbacksMadeForTransactionRollbackByException :: Property.NamedDBProperty+prop_callbacksMadeForTransactionRollbackByException =+  Property.namedDBProperty "Callbacks are delivered for a transaction this is rolled back by an exception" $ \pool -> do     nestingLevel <- HH.forAll TransactionUtil.genNestingLevel      allEvents <- captureTransactionCallbackEvents pool $@@ -134,6 +201,24 @@      allEvents === expectedEvents +prop_callbacksMadeForTransactionRollbackByInstruction :: Property.NamedDBProperty+prop_callbacksMadeForTransactionRollbackByInstruction =+  Property.namedDBProperty "Callbacks are delivered for a transaction this is rolled back by an instruction" $ \pool -> do+    nestingLevel <- HH.forAll TransactionUtil.genNestingLevel++    allEvents <- captureTransactionCallbackEvents pool $+      TransactionUtil.runNestedTransactionWithInstructions nestingLevel $ \_ ->+        pure Orville.Rollback++    let+      expectedEvents =+        mkExpectedEventsForNestedActions nestingLevel $ \maybeSavepoint ->+          case maybeSavepoint of+            Nothing -> (Orville.BeginTransaction, Orville.RollbackTransaction)+            Just savepoint -> (Orville.NewSavepoint savepoint, Orville.RollbackToSavepoint savepoint)++    allEvents === expectedEvents+ prop_usesCustomBeginTransactionSql :: Property.NamedDBProperty prop_usesCustomBeginTransactionSql =   Property.namedDBProperty "Uses custom begin transaction sql" $ \pool -> do@@ -161,6 +246,92 @@       === [ (Orville.OtherQuery, RawSql.toExampleBytes Expr.commit)           , (Orville.OtherQuery, RawSql.toExampleBytes customExpr)           ]++prop_inWithTransaction :: Property.NamedDBProperty+prop_inWithTransaction =+  Property.singletonNamedDBProperty "inWithTransaction returns InWithTransaction inside of withTransaction" $ \pool -> do+    (inside, insideSavepoint, outsideBefore, outsideAfter) <- HH.evalIO . Orville.runOrville pool $ do+      outsideBefore <- Orville.inWithTransaction+      inside <- Orville.withTransaction Orville.inWithTransaction+      insideSavepoint <- Orville.withTransaction $ Orville.withTransaction Orville.inWithTransaction+      outsideAfter <- Orville.inWithTransaction+      pure (inside, insideSavepoint, outsideBefore, outsideAfter)+    inside === Just Orville.InOutermostTransaction+    insideSavepoint === Just (Orville.InSavepointTransaction 1)+    outsideBefore === Nothing+    outsideAfter === Nothing++prop_rollbackCallbackInInvalidTransaction :: Property.NamedDBProperty+prop_rollbackCallbackInInvalidTransaction =+  Property.singletonNamedDBProperty "withTransaction triggers the rollback callback if the LibPQ transaction status is TransInError" $ \pool -> do+    let+      badQuery = RawSql.fromString "bad"++    allEvents <- captureTransactionCallbackEvents pool $+      Orville.withTransaction $ do+        Orville.liftCatch+          catch+          (Execution.executeVoid Execution.OtherQuery badQuery)+          (\(SomeException _) -> pure ())++    allEvents === [Orville.BeginTransaction, Orville.RollbackTransaction]++prop_transactionCleanupRunsAfterBeginTransactionFails :: Property.NamedDBProperty+prop_transactionCleanupRunsAfterBeginTransactionFails =+  Property.singletonNamedDBProperty "withTransaction runs cleanup after failed begin callback" $ \pool -> do+    block <- UnliftIO.newEmptyMVar+    cancel <- UnliftIO.newEmptyMVar+    let+      run :: Reader.ReaderT Orville.OrvilleState IO a -> IO a+      run =+        flip Reader.runReaderT (Orville.newOrvilleState Orville.defaultErrorDetailLevel pool)++      callback =+        Orville.addTransactionCallback $ \ev -> case ev of+          Orville.BeginTransaction -> do+            UnliftIO.putMVar cancel ()+            UnliftIO.takeMVar block+          _ -> pure ()++      action :: Reader.ReaderT Orville.OrvilleState IO (Maybe LibPQ.TransactionStatus)+      action = Orville.withConnection $ \conn -> do+        t <- UnliftIO.async . Orville.withTransaction $ pure ()+        UnliftIO.takeMVar cancel+        UnliftIO.cancel t+        UnliftIO.liftIO $ Conn.transactionStatus conn++    status <- HH.evalIO . run $ Orville.localOrvilleState callback action++    status === Just LibPQ.TransIdle++prop_transactionCleanupCannotBeInterrupted :: Property.NamedDBProperty+prop_transactionCleanupCannotBeInterrupted =+  Property.singletonNamedDBProperty "withTransaction - finishTransaction cannot be interrupted by an async exception" $ \pool -> do+    cancel <- UnliftIO.newEmptyMVar+    let+      run :: Reader.ReaderT Orville.OrvilleState IO a -> IO a+      run =+        flip Reader.runReaderT (Orville.newOrvilleState Orville.defaultErrorDetailLevel pool)++      callback =+        Orville.addSqlExecutionCallback $ \_ sql act ->+          case RawSql.toExampleBytes sql of+            "COMMIT" -> do+              UnliftIO.putMVar cancel ()+              Concurrent.threadDelay 500000+              act+            _ -> act++      action :: Reader.ReaderT Orville.OrvilleState IO (Maybe LibPQ.TransactionStatus)+      action = Orville.withConnection $ \conn -> do+        t <- UnliftIO.async . Orville.withTransaction $ pure ()+        UnliftIO.takeMVar cancel+        UnliftIO.cancel t+        UnliftIO.liftIO $ Conn.transactionStatus conn++    status <- HH.evalIO . run $ Orville.localOrvilleState callback action++    status === Just LibPQ.TransIdle  captureTransactionCallbackEvents ::   Orville.ConnectionPool ->
test/Test/Transaction/Util.hs view
@@ -4,7 +4,7 @@   , silentlyHandleTestError   , genNestingLevel   , runNestedTransactions-  , runNestedTransactionItems+  , runNestedTransactionWithInstructions   , transationNestingLevelRange   ) where@@ -27,26 +27,37 @@   Int ->   (Int -> m ()) ->   m ()-runNestedTransactions maxLevel =-  runNestedTransactionItems [1 .. maxLevel]+runNestedTransactions maxLevel doLevel =+  let+    go is =+      case is of+        [] ->+          pure ()+        (item : rest) ->+          Orville.withTransaction $ do+            doLevel item+            go rest+  in+    go [1 .. maxLevel] -runNestedTransactionItems ::+runNestedTransactionWithInstructions ::   Orville.MonadOrville m =>-  [a] ->-  (a -> m ()) ->+  Int ->+  (Int -> m Orville.TransactionInstruction) ->   m ()-runNestedTransactionItems items doLevel =+runNestedTransactionWithInstructions maxLevel doLevel =   let     go is =       case is of         [] ->           pure ()         (item : rest) ->-          Orville.withTransaction $ do-            doLevel item+          Orville.withTransactionInstruction $ do+            instruction <- doLevel item             go rest+            pure ((), instruction)   in-    go items+    go [1 .. maxLevel]  throwTestError :: ExSafe.MonadThrow m => m a throwTestError =