{-|
Module: IHP.IDE.SchemaDesigner.SchemaOperations
Description: Apply high level operations to the Schema.sql
Copyright: (c) digitally induced GmbH, 2021
-}
module IHP.IDE.SchemaDesigner.SchemaOperations where

import IHP.Prelude
import IHP.IDE.SchemaDesigner.Types
import Data.Maybe (fromJust)
import qualified Data.List as List
import qualified Data.Text as Text

-- | A Schema.sql basically is just a list of sql DDL statements
type Schema = [Statement]

-- | Creates a new tables with a 'id' columns as the primary key
addTable :: Text -> Schema -> Schema
addTable :: Text -> [Statement] -> [Statement]
addTable Text
tableName [Statement]
list = [Statement]
list [Statement] -> [Statement] -> [Statement]
forall a. Semigroup a => a -> a -> a
<> [CreateTable -> Statement
StatementCreateTable CreateTable
    { $sel:name:CreateTable :: Text
name = Text
tableName
    , $sel:columns:CreateTable :: [Column]
columns =
        [Column
            { $sel:name:Column :: Text
name = Text
"id"
            , $sel:columnType:Column :: PostgresType
columnType = PostgresType
PUUID
            , $sel:defaultValue:Column :: Maybe Expression
defaultValue = Expression -> Maybe Expression
forall a. a -> Maybe a
Just (Text -> [Expression] -> Expression
CallExpression Text
"uuid_generate_v4" [])
            , $sel:notNull:Column :: Bool
notNull = Bool
True
            , $sel:isUnique:Column :: Bool
isUnique = Bool
False
            , $sel:generator:Column :: Maybe ColumnGenerator
generator = Maybe ColumnGenerator
forall a. Maybe a
Nothing
            }]
    , $sel:primaryKeyConstraint:CreateTable :: PrimaryKeyConstraint
primaryKeyConstraint = [Text] -> PrimaryKeyConstraint
PrimaryKeyConstraint [Text
"id"]
    , $sel:constraints:CreateTable :: [Constraint]
constraints = []
    , $sel:unlogged:CreateTable :: Bool
unlogged = Bool
False
    }]


data AddColumnOptions = AddColumnOptions
    { AddColumnOptions -> Text
tableName :: !Text
    , AddColumnOptions -> Text
columnName :: !Text
    , AddColumnOptions -> PostgresType
columnType :: !PostgresType
    , AddColumnOptions -> Maybe Expression
defaultValue :: !(Maybe Expression)
    , AddColumnOptions -> Bool
isArray :: !Bool
    , AddColumnOptions -> Bool
allowNull :: !Bool
    , AddColumnOptions -> Bool
isUnique :: !Bool
    , AddColumnOptions -> Bool
isReference :: !Bool
    , AddColumnOptions -> Maybe Text
referenceTable :: !(Maybe Text)
    , AddColumnOptions -> Bool
primaryKey :: !Bool
    , AddColumnOptions -> Bool
withIndex :: !Bool
    , AddColumnOptions -> Bool
autoPolicy :: !Bool
    }
addColumn :: AddColumnOptions -> Schema -> Schema
addColumn :: AddColumnOptions -> [Statement] -> [Statement]
addColumn options :: AddColumnOptions
options@(AddColumnOptions { Bool
Maybe Text
Maybe Expression
Text
PostgresType
$sel:tableName:AddColumnOptions :: AddColumnOptions -> Text
$sel:columnName:AddColumnOptions :: AddColumnOptions -> Text
$sel:columnType:AddColumnOptions :: AddColumnOptions -> PostgresType
$sel:defaultValue:AddColumnOptions :: AddColumnOptions -> Maybe Expression
$sel:isArray:AddColumnOptions :: AddColumnOptions -> Bool
$sel:allowNull:AddColumnOptions :: AddColumnOptions -> Bool
$sel:isUnique:AddColumnOptions :: AddColumnOptions -> Bool
$sel:isReference:AddColumnOptions :: AddColumnOptions -> Bool
$sel:referenceTable:AddColumnOptions :: AddColumnOptions -> Maybe Text
$sel:primaryKey:AddColumnOptions :: AddColumnOptions -> Bool
$sel:withIndex:AddColumnOptions :: AddColumnOptions -> Bool
$sel:autoPolicy:AddColumnOptions :: AddColumnOptions -> Bool
tableName :: Text
columnName :: Text
columnType :: PostgresType
defaultValue :: Maybe Expression
isArray :: Bool
allowNull :: Bool
isUnique :: Bool
isReference :: Bool
referenceTable :: Maybe Text
primaryKey :: Bool
withIndex :: Bool
autoPolicy :: Bool
.. }) =
    let
        column :: Column
column = AddColumnOptions -> Column
newColumn AddColumnOptions
options
        addColumnToTable :: Text -> Column -> Bool -> Statement -> Statement
        addColumnToTable :: Text -> Column -> Bool -> Statement -> Statement
addColumnToTable Text
tableName (column :: Column
column@Column { $sel:name:Column :: Column -> Text
name = Text
columnName }) Bool
isPrimaryKey (StatementCreateTable table :: CreateTable
table@CreateTable { Text
$sel:name:CreateTable :: CreateTable -> Text
name :: Text
name, [Column]
$sel:columns:CreateTable :: CreateTable -> [Column]
columns :: [Column]
columns, $sel:primaryKeyConstraint:CreateTable :: CreateTable -> PrimaryKeyConstraint
primaryKeyConstraint = PrimaryKeyConstraint [Text]
pks})
            | Text
name Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
tableName =
                let primaryKeyConstraint :: PrimaryKeyConstraint
primaryKeyConstraint =
                      if Bool
isPrimaryKey
                      then [Text] -> PrimaryKeyConstraint
PrimaryKeyConstraint ([Text]
pks [Text] -> [Text] -> [Text]
forall a. Semigroup a => a -> a -> a
<> [Text
columnName])
                      else [Text] -> PrimaryKeyConstraint
PrimaryKeyConstraint [Text]
pks
                in CreateTable -> Statement
StatementCreateTable (CreateTable
table { $sel:columns:CreateTable :: [Column]
columns = [Column]
columns [Column] -> [Column] -> [Column]
forall a. Semigroup a => a -> a -> a
<> [Column
column] , PrimaryKeyConstraint
$sel:primaryKeyConstraint:CreateTable :: PrimaryKeyConstraint
primaryKeyConstraint :: PrimaryKeyConstraint
primaryKeyConstraint })
        addColumnToTable Text
tableName Column
column Bool
isPrimaryKey Statement
statement = Statement
statement

        [Statement] -> [Statement]
addTableOp :: Schema -> Schema = (Statement -> Statement) -> [Statement] -> [Statement]
forall a b. (a -> b) -> [a] -> [b]
map (Text -> Column -> Bool -> Statement -> Statement
addColumnToTable Text
tableName Column
column Bool
primaryKey)

        foreignKeyConstraint :: Statement
foreignKeyConstraint = Text -> Text -> Text -> Statement
newForeignKeyConstraint Text
tableName Text
columnName (Maybe Text -> Text
forall a. HasCallStack => Maybe a -> a
fromJust Maybe Text
referenceTable)
        index :: Statement
index = Text -> Text -> Statement
newColumnIndex Text
tableName Text
columnName

        handleAutoPolicy :: [Statement] -> [Statement]
handleAutoPolicy [Statement]
statements =
            if Bool
autoPolicy
                then
                    let
                        isTable :: Statement -> Bool
isTable (StatementCreateTable CreateTable { Text
$sel:name:CreateTable :: CreateTable -> Text
name :: Text
name }) = Text
name Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
tableName
                        isTable Statement
otherwise = Bool
False
                        (Just Statement
table) = (Statement -> Bool) -> [Statement] -> Maybe Statement
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find Statement -> Bool
isTable [Statement]
statements
                        suggestedPolicy :: Statement
suggestedPolicy = [Statement] -> Statement -> Statement
suggestPolicy [Statement]
statements Statement
table
                    in if (Statement
suggestedPolicy.name Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
/= Text
"" Bool -> Bool -> Bool
&& Bool -> Bool
not ([Statement] -> Text -> Bool
doesHaveExistingPolicies [Statement]
statements Text
tableName))
                            then
                                [Statement]
statements
                                [Statement] -> ([Statement] -> [Statement]) -> [Statement]
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> Text -> [Statement] -> [Statement]
enableRowLevelSecurity Text
tableName
                                [Statement] -> ([Statement] -> [Statement]) -> [Statement]
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> AddPolicyOptions -> [Statement] -> [Statement]
addPolicy AddPolicyOptions
                                        { $sel:tableName:AddPolicyOptions :: Text
tableName = Text
tableName
                                        , $sel:name:AddPolicyOptions :: Text
name = Statement
suggestedPolicy.name
                                        , $sel:using:AddPolicyOptions :: Maybe Expression
using = Statement
suggestedPolicy.using
                                        , $sel:check:AddPolicyOptions :: Maybe Expression
check = Statement
suggestedPolicy.check
                                        }
                            else [Statement]
statements
                else [Statement]
statements
    in
        if Bool
isReference then
            \[Statement]
statements -> [Statement]
statements
            [Statement] -> ([Statement] -> [Statement]) -> [Statement]
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> [Statement] -> [Statement]
addTableOp
            [Statement] -> ([Statement] -> [Statement]) -> [Statement]
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> Statement -> [Statement] -> [Statement]
appendStatement Statement
index
            [Statement] -> ([Statement] -> [Statement]) -> [Statement]
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> Statement -> [Statement] -> [Statement]
appendStatement Statement
foreignKeyConstraint
            [Statement] -> ([Statement] -> [Statement]) -> [Statement]
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> [Statement] -> [Statement]
handleAutoPolicy
        else
            [Statement] -> [Statement]
addTableOp
            ([Statement] -> [Statement])
-> ([Statement] -> [Statement]) -> [Statement] -> [Statement]
forall b c a. (b -> c) -> (a -> b) -> a -> c
forall {k} (cat :: k -> k -> *) (b :: k) (c :: k) (a :: k).
Category cat =>
cat b c -> cat a b -> cat a c
. (if Bool
withIndex
                    then Statement -> [Statement] -> [Statement]
appendStatement Statement
index
                    else \[Statement]
schema -> [Statement]
schema)
            ([Statement] -> [Statement])
-> ([Statement] -> [Statement]) -> [Statement] -> [Statement]
forall b c a. (b -> c) -> (a -> b) -> a -> c
forall {k} (cat :: k -> k -> *) (b :: k) (c :: k) (a :: k).
Category cat =>
cat b c -> cat a b -> cat a c
. (if Text
columnName Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
"updated_at"
                then Text -> [Statement] -> [Statement]
addUpdatedAtTrigger Text
tableName
                else \[Statement]
schema -> [Statement]
schema)

data UpdateColumnOptions = UpdateColumnOptions
    { UpdateColumnOptions -> Text
tableName :: !Text
    , UpdateColumnOptions -> Text
columnName :: !Text
    , UpdateColumnOptions -> PostgresType
columnType :: !PostgresType
    , UpdateColumnOptions -> Maybe Expression
defaultValue :: !(Maybe Expression)
    , UpdateColumnOptions -> Bool
isArray :: !Bool
    , UpdateColumnOptions -> Bool
allowNull :: !Bool
    , UpdateColumnOptions -> Bool
isUnique :: !Bool
    , UpdateColumnOptions -> Bool
primaryKey :: !Bool
    , UpdateColumnOptions -> Int
columnId :: !Int
    }
updateColumn :: UpdateColumnOptions -> Schema -> Schema
updateColumn :: UpdateColumnOptions -> [Statement] -> [Statement]
updateColumn options :: UpdateColumnOptions
options@(UpdateColumnOptions { Bool
Int
Maybe Expression
Text
PostgresType
$sel:tableName:UpdateColumnOptions :: UpdateColumnOptions -> Text
$sel:columnName:UpdateColumnOptions :: UpdateColumnOptions -> Text
$sel:columnType:UpdateColumnOptions :: UpdateColumnOptions -> PostgresType
$sel:defaultValue:UpdateColumnOptions :: UpdateColumnOptions -> Maybe Expression
$sel:isArray:UpdateColumnOptions :: UpdateColumnOptions -> Bool
$sel:allowNull:UpdateColumnOptions :: UpdateColumnOptions -> Bool
$sel:isUnique:UpdateColumnOptions :: UpdateColumnOptions -> Bool
$sel:primaryKey:UpdateColumnOptions :: UpdateColumnOptions -> Bool
$sel:columnId:UpdateColumnOptions :: UpdateColumnOptions -> Int
tableName :: Text
columnName :: Text
columnType :: PostgresType
defaultValue :: Maybe Expression
isArray :: Bool
allowNull :: Bool
isUnique :: Bool
primaryKey :: Bool
columnId :: Int
.. }) [Statement]
schema =
    let
        updateColumnAtIndex :: [Column] -> [Column]
        updateColumnAtIndex :: [Column] -> [Column]
updateColumnAtIndex [Column]
columns = (Column -> Int -> Column) -> [Column] -> [Column]
forall a b. (a -> Int -> b) -> [a] -> [b]
mapWithIndex Column -> Int -> Column
updateColumnAtIndex' [Column]
columns

        mapWithIndex :: (a -> Int -> b) -> [a] -> [b]
        mapWithIndex :: forall a b. (a -> Int -> b) -> [a] -> [b]
mapWithIndex a -> Int -> b
mapFn [a]
items = (a -> Int -> b) -> [a] -> Int -> [b]
forall a b. (a -> Int -> b) -> [a] -> Int -> [b]
mapWithIndex' a -> Int -> b
mapFn [a]
items Int
0
            where
                mapWithIndex' :: (a -> Int -> b) -> [a] -> Int -> [b]
                mapWithIndex' :: forall a b. (a -> Int -> b) -> [a] -> Int -> [b]
mapWithIndex' a -> Int -> b
mapFn [] Int
_ = []
                mapWithIndex' a -> Int -> b
mapFn (a
item:[a]
rest) Int
i = (a -> Int -> b
mapFn a
item Int
i)b -> [b] -> [b]
forall a. a -> [a] -> [a]
:((a -> Int -> b) -> [a] -> Int -> [b]
forall a b. (a -> Int -> b) -> [a] -> Int -> [b]
mapWithIndex' a -> Int -> b
mapFn [a]
rest (Int
i Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1))

        updateColumnAtIndex' :: Column -> Int -> Column
        updateColumnAtIndex' :: Column -> Int -> Column
updateColumnAtIndex' Column
column Int
index | Int
index Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
columnId = Column
column
                { $sel:name:Column :: Text
name = Text
columnName
                , $sel:columnType:Column :: PostgresType
columnType = Bool -> PostgresType -> PostgresType
arrayifytype Bool
isArray PostgresType
columnType
                , $sel:defaultValue:Column :: Maybe Expression
defaultValue = Maybe Expression
defaultValue
                , $sel:notNull:Column :: Bool
notNull = Bool -> Bool
not Bool
allowNull
                , Bool
$sel:isUnique:Column :: Bool
isUnique :: Bool
isUnique
                }
        updateColumnAtIndex' Column
column Int
index = Column
column

        updateTableOp :: [Statement] -> [Statement]
        updateTableOp :: [Statement] -> [Statement]
updateTableOp = (Statement -> Statement) -> [Statement] -> [Statement]
forall a b. (a -> b) -> [a] -> [b]
map \case
                (StatementCreateTable table :: CreateTable
table@(CreateTable { Text
$sel:name:CreateTable :: CreateTable -> Text
name :: Text
name, [Column]
$sel:columns:CreateTable :: CreateTable -> [Column]
columns :: [Column]
columns, PrimaryKeyConstraint
$sel:primaryKeyConstraint:CreateTable :: CreateTable -> PrimaryKeyConstraint
primaryKeyConstraint :: PrimaryKeyConstraint
primaryKeyConstraint })) | Text
name Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
tableName ->
                    let
                        oldColumn :: Column
                        oldColumn :: Column
oldColumn = [Column]
columns
                                [Column] -> ([Column] -> [(Column, Int)]) -> [(Column, Int)]
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> (\[Column]
c -> [Column] -> [Int] -> [(Column, Int)]
forall a b. [a] -> [b] -> [(a, b)]
zip [Column]
c [Int
0..])
                                [(Column, Int)]
-> ([(Column, Int)] -> Maybe (Column, Int)) -> Maybe (Column, Int)
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> ((Column, Int) -> Bool) -> [(Column, Int)] -> Maybe (Column, Int)
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find ((\(Column
c, Int
index) -> Int
index Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
columnId))
                                Maybe (Column, Int)
-> (Maybe (Column, Int) -> (Column, Int)) -> (Column, Int)
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> (Column, Int) -> Maybe (Column, Int) -> (Column, Int)
forall a. a -> Maybe a -> a
fromMaybe (Text -> (Column, Int)
forall a. Text -> a
error Text
"could not find column with id")
                                (Column, Int) -> ((Column, Int) -> Column) -> Column
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> (Column, Int) -> Column
forall a b. (a, b) -> a
fst
                    in CreateTable -> Statement
StatementCreateTable (CreateTable -> Statement) -> CreateTable -> Statement
forall a b. (a -> b) -> a -> b
$ (CreateTable
table :: CreateTable)
                            { $sel:columns:CreateTable :: [Column]
columns = [Column] -> [Column]
updateColumnAtIndex [Column]
columns
                            , $sel:primaryKeyConstraint:CreateTable :: PrimaryKeyConstraint
primaryKeyConstraint = Column -> Bool -> PrimaryKeyConstraint -> PrimaryKeyConstraint
updatePrimaryKeyConstraint Column
oldColumn Bool
primaryKey PrimaryKeyConstraint
primaryKeyConstraint
                            }
                Statement
otherwise -> Statement
otherwise

        -- | Add or remove a column from the primary key constraint
        updatePrimaryKeyConstraint :: Column -> Bool -> PrimaryKeyConstraint -> PrimaryKeyConstraint
        updatePrimaryKeyConstraint :: Column -> Bool -> PrimaryKeyConstraint -> PrimaryKeyConstraint
updatePrimaryKeyConstraint Column { Text
$sel:name:Column :: Column -> Text
name :: Text
name } Bool
isPrimaryKey primaryKeyConstraint :: PrimaryKeyConstraint
primaryKeyConstraint@PrimaryKeyConstraint { [Text]
primaryKeyColumnNames :: [Text]
$sel:primaryKeyColumnNames:PrimaryKeyConstraint :: PrimaryKeyConstraint -> [Text]
primaryKeyColumnNames } =
          case (Bool
isPrimaryKey, Text
name Text -> [Text] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Text]
primaryKeyColumnNames) of
              (Bool
False, Bool
False) -> PrimaryKeyConstraint
primaryKeyConstraint
              (Bool
False, Bool
True) -> [Text] -> PrimaryKeyConstraint
PrimaryKeyConstraint ((Text -> Bool) -> [Text] -> [Text]
forall a. (a -> Bool) -> [a] -> [a]
filter (Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
/= Text
name) [Text]
primaryKeyColumnNames)
              (Bool
True, Bool
False) -> [Text] -> PrimaryKeyConstraint
PrimaryKeyConstraint ([Text]
primaryKeyColumnNames [Text] -> [Text] -> [Text]
forall a. Semigroup a => a -> a -> a
<> [Text
name])
              (Bool
True, Bool
True) -> PrimaryKeyConstraint
primaryKeyConstraint

        updateForeignKeyConstraints :: [Statement] -> [Statement]
updateForeignKeyConstraints = (Statement -> Statement) -> [Statement] -> [Statement]
forall a b. (a -> b) -> [a] -> [b]
map \case
                statement :: Statement
statement@(AddConstraint { $sel:tableName:StatementCreateTable :: Statement -> Text
tableName = Text
constraintTable, $sel:constraint:StatementCreateTable :: Statement -> Constraint
constraint = constraint :: Constraint
constraint@(ForeignKeyConstraint { $sel:name:ForeignKeyConstraint :: Constraint -> Maybe Text
name = Maybe Text
fkName, $sel:columnName:ForeignKeyConstraint :: Constraint -> Text
columnName = Text
fkColumnName  })  }) | Text
constraintTable Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
tableName Bool -> Bool -> Bool
&& Text
fkColumnName Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== (Column
oldColumn.name) ->
                    let newName :: Maybe Text
newName = HasCallStack => Text -> Text -> Text -> Text
Text -> Text -> Text -> Text
Text.replace (Column
oldColumn.name) Text
columnName (Text -> Text) -> Maybe Text -> Maybe Text
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Maybe Text
fkName
                    in Statement
statement { $sel:constraint:StatementCreateTable :: Constraint
constraint = Constraint
constraint { Text
columnName :: Text
$sel:columnName:ForeignKeyConstraint :: Text
columnName, $sel:name:ForeignKeyConstraint :: Maybe Text
name = Maybe Text
newName } }
                index :: Statement
index@(CreateIndex { Text
indexName :: Text
$sel:indexName:StatementCreateTable :: Statement -> Text
indexName, $sel:tableName:StatementCreateTable :: Statement -> Text
tableName = Text
indexTable, $sel:columns:StatementCreateTable :: Statement -> [IndexColumn]
columns = [IndexColumn]
indexColumns }) | Text
indexTable Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
tableName ->
                    let
                        updateIndexColumn :: IndexColumn -> IndexColumn
                        updateIndexColumn :: IndexColumn -> IndexColumn
updateIndexColumn indexColumn :: IndexColumn
indexColumn@(IndexColumn { $sel:column:IndexColumn :: IndexColumn -> Expression
column = VarExpression Text
varName }) | Text
varName Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== (Column
oldColumn.name) = IndexColumn
indexColumn { $sel:column:IndexColumn :: Expression
column = Text -> Expression
VarExpression Text
columnName }
                        updateIndexColumn IndexColumn
otherwise = IndexColumn
otherwise
                    in
                        (Statement
index :: Statement) { $sel:columns:StatementCreateTable :: [IndexColumn]
columns = (IndexColumn -> IndexColumn) -> [IndexColumn] -> [IndexColumn]
forall a b. (a -> b) -> [a] -> [b]
map IndexColumn -> IndexColumn
updateIndexColumn [IndexColumn]
indexColumns, $sel:indexName:StatementCreateTable :: Text
indexName = HasCallStack => Text -> Text -> Text -> Text
Text -> Text -> Text -> Text
Text.replace (Column
oldColumn.name) Text
columnName Text
indexName }
                Statement
otherwise -> Statement
otherwise
        findOldColumn :: [Statement] -> Column
findOldColumn [Statement]
statements = (Statement -> Maybe Column) -> [Statement] -> [Column]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe Statement -> Maybe Column
findOldColumn' [Statement]
statements
                [Column] -> ([Column] -> Maybe Column) -> Maybe Column
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> [Column] -> Maybe Column
forall a. [a] -> Maybe a
head
                Maybe Column -> (Maybe Column -> Column) -> Column
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> Column -> Maybe Column -> Column
forall a. a -> Maybe a -> a
fromMaybe (Text -> Column
forall a. Text -> a
error Text
"Could not find old column")
        findOldColumn' :: Statement -> Maybe Column
findOldColumn' (StatementCreateTable table :: CreateTable
table@(CreateTable { Text
$sel:name:CreateTable :: CreateTable -> Text
name :: Text
name, [Column]
$sel:columns:CreateTable :: CreateTable -> [Column]
columns :: [Column]
columns, PrimaryKeyConstraint
$sel:primaryKeyConstraint:CreateTable :: CreateTable -> PrimaryKeyConstraint
primaryKeyConstraint :: PrimaryKeyConstraint
primaryKeyConstraint })) | Text
name Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
tableName =
                    let
                        oldColumn :: Column
                        oldColumn :: Column
oldColumn = [Column]
columns
                                [Column] -> ([Column] -> [(Column, Int)]) -> [(Column, Int)]
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> (\[Column]
c -> [Column] -> [Int] -> [(Column, Int)]
forall a b. [a] -> [b] -> [(a, b)]
zip [Column]
c [Int
0..])
                                [(Column, Int)]
-> ([(Column, Int)] -> Maybe (Column, Int)) -> Maybe (Column, Int)
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> ((Column, Int) -> Bool) -> [(Column, Int)] -> Maybe (Column, Int)
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find ((\(Column
c, Int
index) -> Int
index Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
columnId))
                                Maybe (Column, Int)
-> (Maybe (Column, Int) -> (Column, Int)) -> (Column, Int)
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> (Column, Int) -> Maybe (Column, Int) -> (Column, Int)
forall a. a -> Maybe a -> a
fromMaybe (Text -> (Column, Int)
forall a. Text -> a
error Text
"could not find column with id")
                                (Column, Int) -> ((Column, Int) -> Column) -> Column
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> (Column, Int) -> Column
forall a b. (a, b) -> a
fst
                    in
                        Column -> Maybe Column
forall a. a -> Maybe a
Just Column
oldColumn
        findOldColumn' Statement
_ = Maybe Column
forall a. Maybe a
Nothing

        oldColumn :: Column
        oldColumn :: Column
oldColumn = [Statement] -> Column
findOldColumn [Statement]
schema
    in
        [Statement]
schema
        [Statement] -> ([Statement] -> [Statement]) -> [Statement]
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> [Statement] -> [Statement]
updateTableOp
        [Statement] -> ([Statement] -> [Statement]) -> [Statement]
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> [Statement] -> [Statement]
updateForeignKeyConstraints

newColumn :: AddColumnOptions -> Column
newColumn :: AddColumnOptions -> Column
newColumn AddColumnOptions { Bool
Maybe Text
Maybe Expression
Text
PostgresType
$sel:tableName:AddColumnOptions :: AddColumnOptions -> Text
$sel:columnName:AddColumnOptions :: AddColumnOptions -> Text
$sel:columnType:AddColumnOptions :: AddColumnOptions -> PostgresType
$sel:defaultValue:AddColumnOptions :: AddColumnOptions -> Maybe Expression
$sel:isArray:AddColumnOptions :: AddColumnOptions -> Bool
$sel:allowNull:AddColumnOptions :: AddColumnOptions -> Bool
$sel:isUnique:AddColumnOptions :: AddColumnOptions -> Bool
$sel:isReference:AddColumnOptions :: AddColumnOptions -> Bool
$sel:referenceTable:AddColumnOptions :: AddColumnOptions -> Maybe Text
$sel:primaryKey:AddColumnOptions :: AddColumnOptions -> Bool
$sel:withIndex:AddColumnOptions :: AddColumnOptions -> Bool
$sel:autoPolicy:AddColumnOptions :: AddColumnOptions -> Bool
tableName :: Text
columnName :: Text
columnType :: PostgresType
defaultValue :: Maybe Expression
isArray :: Bool
allowNull :: Bool
isUnique :: Bool
isReference :: Bool
referenceTable :: Maybe Text
primaryKey :: Bool
withIndex :: Bool
autoPolicy :: Bool
.. } = Column
    { $sel:name:Column :: Text
name = Text
columnName
    , $sel:columnType:Column :: PostgresType
columnType = Bool -> PostgresType -> PostgresType
arrayifytype Bool
isArray PostgresType
columnType
    , $sel:defaultValue:Column :: Maybe Expression
defaultValue = Maybe Expression
defaultValue
    , $sel:notNull:Column :: Bool
notNull = (Bool -> Bool
not Bool
allowNull)
    , $sel:isUnique:Column :: Bool
isUnique = Bool
isUnique
    , $sel:generator:Column :: Maybe ColumnGenerator
generator = Maybe ColumnGenerator
forall a. Maybe a
Nothing
    }

newForeignKeyConstraint :: Text -> Text -> Text -> Statement
newForeignKeyConstraint :: Text -> Text -> Text -> Statement
newForeignKeyConstraint Text
tableName Text
columnName Text
referenceTable =
    AddConstraint
    { Text
$sel:tableName:StatementCreateTable :: Text
tableName :: Text
tableName
    , $sel:constraint:StatementCreateTable :: Constraint
constraint = ForeignKeyConstraint
        { $sel:name:ForeignKeyConstraint :: Maybe Text
name = Text -> Maybe Text
forall a. a -> Maybe a
Just (Text -> Maybe Text) -> Text -> Maybe Text
forall a b. (a -> b) -> a -> b
$ Text
tableName Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"_ref_" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
columnName
        , $sel:columnName:ForeignKeyConstraint :: Text
columnName = Text
columnName
        , $sel:referenceTable:ForeignKeyConstraint :: Text
referenceTable = Text
referenceTable
        , $sel:referenceColumn:ForeignKeyConstraint :: Maybe Text
referenceColumn = Maybe Text
"id"
        , $sel:onDelete:ForeignKeyConstraint :: Maybe OnDelete
onDelete = (OnDelete -> Maybe OnDelete
forall a. a -> Maybe a
Just OnDelete
NoAction)
        }
    , $sel:deferrable:StatementCreateTable :: Maybe Bool
deferrable = Maybe Bool
forall a. Maybe a
Nothing
    , $sel:deferrableType:StatementCreateTable :: Maybe DeferrableType
deferrableType = Maybe DeferrableType
forall a. Maybe a
Nothing
    }

newColumnIndex :: Text -> Text -> Statement
newColumnIndex :: Text -> Text -> Statement
newColumnIndex Text
tableName Text
columnName =
    CreateIndex
    { $sel:indexName:StatementCreateTable :: Text
indexName = Text
tableName Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"_" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
columnName Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"_index"
    , $sel:unique:StatementCreateTable :: Bool
unique = Bool
False
    , Text
$sel:tableName:StatementCreateTable :: Text
tableName :: Text
tableName
    , $sel:columns:StatementCreateTable :: [IndexColumn]
columns = [IndexColumn { $sel:column:IndexColumn :: Expression
column = Text -> Expression
VarExpression Text
columnName, $sel:columnOrder:IndexColumn :: [IndexColumnOrder]
columnOrder = [] }]
    , $sel:whereClause:StatementCreateTable :: Maybe Expression
whereClause = Maybe Expression
forall a. Maybe a
Nothing
    , $sel:indexType:StatementCreateTable :: Maybe IndexType
indexType = Maybe IndexType
forall a. Maybe a
Nothing
    }

data AddIndexOptions = AddIndexOptions
    { AddIndexOptions -> Text
tableName :: !Text
    , AddIndexOptions -> Text
columnName :: !Text
    }
addIndex :: AddIndexOptions -> Schema -> Schema
addIndex :: AddIndexOptions -> [Statement] -> [Statement]
addIndex AddIndexOptions { Text
$sel:tableName:AddIndexOptions :: AddIndexOptions -> Text
tableName :: Text
tableName, Text
$sel:columnName:AddIndexOptions :: AddIndexOptions -> Text
columnName :: Text
columnName } =
    Statement -> [Statement] -> [Statement]
appendStatement (Text -> Text -> Statement
newColumnIndex Text
tableName Text
columnName)

appendStatement :: Statement -> [Statement] -> [Statement]
appendStatement :: Statement -> [Statement] -> [Statement]
appendStatement Statement
statement [Statement]
statements = [Statement]
statements [Statement] -> [Statement] -> [Statement]
forall a. Semigroup a => a -> a -> a
<> [Statement
statement]

arrayifytype :: Bool -> PostgresType -> PostgresType
arrayifytype :: Bool -> PostgresType -> PostgresType
arrayifytype Bool
False   (PArray PostgresType
coltype) = PostgresType
coltype
arrayifytype Bool
True  a :: PostgresType
a@(PArray PostgresType
coltype) = PostgresType
a
arrayifytype Bool
False PostgresType
coltype = PostgresType
coltype
arrayifytype Bool
True  PostgresType
coltype = PostgresType -> PostgresType
PArray PostgresType
coltype

addForeignKeyConstraint :: Text -> Text -> Text -> Text -> OnDelete -> [Statement] -> [Statement]
addForeignKeyConstraint :: Text
-> Text -> Text -> Text -> OnDelete -> [Statement] -> [Statement]
addForeignKeyConstraint Text
tableName Text
columnName Text
constraintName Text
referenceTable OnDelete
onDelete [Statement]
list = [Statement]
list [Statement] -> [Statement] -> [Statement]
forall a. Semigroup a => a -> a -> a
<> [AddConstraint { $sel:tableName:StatementCreateTable :: Text
tableName = Text
tableName, $sel:constraint:StatementCreateTable :: Constraint
constraint = ForeignKeyConstraint { $sel:name:ForeignKeyConstraint :: Maybe Text
name = Text -> Maybe Text
forall a. a -> Maybe a
Just Text
constraintName, $sel:columnName:ForeignKeyConstraint :: Text
columnName = Text
columnName, $sel:referenceTable:ForeignKeyConstraint :: Text
referenceTable = Text
referenceTable, $sel:referenceColumn:ForeignKeyConstraint :: Maybe Text
referenceColumn = Maybe Text
"id", $sel:onDelete:ForeignKeyConstraint :: Maybe OnDelete
onDelete = (OnDelete -> Maybe OnDelete
forall a. a -> Maybe a
Just OnDelete
onDelete) }, $sel:deferrable:StatementCreateTable :: Maybe Bool
deferrable = Maybe Bool
forall a. Maybe a
Nothing, $sel:deferrableType:StatementCreateTable :: Maybe DeferrableType
deferrableType = Maybe DeferrableType
forall a. Maybe a
Nothing }]

addTableIndex :: Text -> Bool -> Text -> [Text] -> [Statement] -> [Statement]
addTableIndex :: Text -> Bool -> Text -> [Text] -> [Statement] -> [Statement]
addTableIndex Text
indexName Bool
unique Text
tableName [Text]
columnNames [Statement]
list = [Statement]
list [Statement] -> [Statement] -> [Statement]
forall a. Semigroup a => a -> a -> a
<> [CreateIndex { Text
$sel:indexName:StatementCreateTable :: Text
indexName :: Text
indexName, Bool
$sel:unique:StatementCreateTable :: Bool
unique :: Bool
unique, Text
$sel:tableName:StatementCreateTable :: Text
tableName :: Text
tableName, $sel:columns:StatementCreateTable :: [IndexColumn]
columns = [Text]
columnNames [Text] -> ([Text] -> [IndexColumn]) -> [IndexColumn]
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> (Text -> IndexColumn) -> [Text] -> [IndexColumn]
forall a b. (a -> b) -> [a] -> [b]
map (\Text
columnName -> IndexColumn { $sel:column:IndexColumn :: Expression
column = Text -> Expression
VarExpression Text
columnName, $sel:columnOrder:IndexColumn :: [IndexColumnOrder]
columnOrder = [] }), $sel:whereClause:StatementCreateTable :: Maybe Expression
whereClause = Maybe Expression
forall a. Maybe a
Nothing, $sel:indexType:StatementCreateTable :: Maybe IndexType
indexType = Maybe IndexType
forall a. Maybe a
Nothing }]

-- | An enum is added after all existing enum statements, but right before @CREATE TABLE@ statements
addEnum :: Text -> Schema -> Schema
addEnum :: Text -> [Statement] -> [Statement]
addEnum Text
enumName [Statement]
statements = [Statement]
a [Statement] -> [Statement] -> [Statement]
forall a. Semigroup a => a -> a -> a
<> [Statement]
enum [Statement] -> [Statement] -> [Statement]
forall a. Semigroup a => a -> a -> a
<> [Statement]
b
    where
        enum :: [Statement]
enum = [CreateEnumType { $sel:name:StatementCreateTable :: Text
name = Text
enumName, $sel:values:StatementCreateTable :: [Text]
values = []}]
        ([Statement]
a, [Statement]
b) = Int -> [Statement] -> ([Statement], [Statement])
forall a. Int -> [a] -> ([a], [a])
List.splitAt Int
insertionIndex [Statement]
statements

        insertionIndex :: Int
insertionIndex = [Statement] -> Int -> Int
forall {t}. Num t => [Statement] -> t -> t
findInsertionIndex [Statement]
statements Int
0

        -- Finds the index after comments and existing enum types, just before the CREATE TABLE statements
        findInsertionIndex :: [Statement] -> t -> t
findInsertionIndex ((Comment{}):[Statement]
xs) !t
i = [Statement] -> t -> t
findInsertionIndex [Statement]
xs (t
i t -> t -> t
forall a. Num a => a -> a -> a
+ t
1)
        findInsertionIndex ((CreateEnumType{}):[Statement]
xs) !t
i = [Statement] -> t -> t
findInsertionIndex [Statement]
xs (t
i t -> t -> t
forall a. Num a => a -> a -> a
+ t
1)
        findInsertionIndex (Statement
x:[Statement]
xs) !t
i = t
i
        findInsertionIndex [] !t
i = t
i

addValueToEnum :: Text -> Text -> Schema -> Schema
addValueToEnum :: Text -> Text -> [Statement] -> [Statement]
addValueToEnum Text
enumName Text
enumValueName [Statement]
statements = (Statement -> Statement) -> [Statement] -> [Statement]
forall a b. (a -> b) -> [a] -> [b]
map Statement -> Statement
addValueToEnum' [Statement]
statements
    where
        addValueToEnum' :: Statement -> Statement
addValueToEnum' (table :: Statement
table@CreateEnumType { Text
$sel:name:StatementCreateTable :: Statement -> Text
name :: Text
name, [Text]
$sel:values:StatementCreateTable :: Statement -> [Text]
values :: [Text]
values }) | Text
name Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
enumName =
            Statement
table { $sel:values:StatementCreateTable :: [Text]
values = [Text]
values [Text] -> [Text] -> [Text]
forall a. Semigroup a => a -> a -> a
<> [Text
enumValueName] }
        addValueToEnum' Statement
statement = Statement
statement

data UpdatePolicyOptions = UpdatePolicyOptions
    { UpdatePolicyOptions -> Text
currentName :: !Text -- ^ Current name of the policy
    , UpdatePolicyOptions -> Text
tableName :: !Text -- ^ Table of the policy
    , UpdatePolicyOptions -> Text
name :: !Text -- ^ New name of the policy
    , UpdatePolicyOptions -> Maybe Expression
using :: !(Maybe Expression)
    , UpdatePolicyOptions -> Maybe Expression
check :: !(Maybe Expression)
    }

updatePolicy :: UpdatePolicyOptions -> Schema -> Schema
updatePolicy :: UpdatePolicyOptions -> [Statement] -> [Statement]
updatePolicy UpdatePolicyOptions { Maybe Expression
Text
$sel:currentName:UpdatePolicyOptions :: UpdatePolicyOptions -> Text
$sel:tableName:UpdatePolicyOptions :: UpdatePolicyOptions -> Text
$sel:name:UpdatePolicyOptions :: UpdatePolicyOptions -> Text
$sel:using:UpdatePolicyOptions :: UpdatePolicyOptions -> Maybe Expression
$sel:check:UpdatePolicyOptions :: UpdatePolicyOptions -> Maybe Expression
currentName :: Text
tableName :: Text
name :: Text
using :: Maybe Expression
check :: Maybe Expression
.. } [Statement]
statements =
        [Statement]
statements
        [Statement] -> ([Statement] -> [Statement]) -> [Statement]
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> (Statement -> Statement) -> [Statement] -> [Statement]
forall a b. (a -> b) -> [a] -> [b]
map Statement -> Statement
updatePolicy'
    where
        updatePolicy' :: Statement -> Statement
updatePolicy' policy :: Statement
policy@CreatePolicy { $sel:name:StatementCreateTable :: Statement -> Text
name = Text
pName, Maybe PolicyAction
action :: Maybe PolicyAction
$sel:action:StatementCreateTable :: Statement -> Maybe PolicyAction
action, $sel:tableName:StatementCreateTable :: Statement -> Text
tableName = Text
pTable } | Text
pName Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
currentName Bool -> Bool -> Bool
&& Text
pTable Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
tableName = CreatePolicy { Text
$sel:tableName:StatementCreateTable :: Text
tableName :: Text
tableName, Maybe PolicyAction
action :: Maybe PolicyAction
$sel:action:StatementCreateTable :: Maybe PolicyAction
action, Text
$sel:name:StatementCreateTable :: Text
name :: Text
name, Maybe Expression
using :: Maybe Expression
$sel:using:StatementCreateTable :: Maybe Expression
using, Maybe Expression
check :: Maybe Expression
$sel:check:StatementCreateTable :: Maybe Expression
check }
        updatePolicy' Statement
otherwise                                                                                              = Statement
otherwise

data AddPolicyOptions = AddPolicyOptions
    { AddPolicyOptions -> Text
tableName :: !Text
    , AddPolicyOptions -> Text
name :: !Text
    , AddPolicyOptions -> Maybe Expression
using :: !(Maybe Expression)
    , AddPolicyOptions -> Maybe Expression
check :: !(Maybe Expression)
    }

addPolicy :: AddPolicyOptions -> Schema -> Schema
addPolicy :: AddPolicyOptions -> [Statement] -> [Statement]
addPolicy AddPolicyOptions { Maybe Expression
Text
$sel:tableName:AddPolicyOptions :: AddPolicyOptions -> Text
$sel:name:AddPolicyOptions :: AddPolicyOptions -> Text
$sel:using:AddPolicyOptions :: AddPolicyOptions -> Maybe Expression
$sel:check:AddPolicyOptions :: AddPolicyOptions -> Maybe Expression
tableName :: Text
name :: Text
using :: Maybe Expression
check :: Maybe Expression
.. } [Statement]
statements = [Statement]
statements [Statement] -> [Statement] -> [Statement]
forall a. Semigroup a => a -> a -> a
<> [Statement]
createPolicyStatement
    where
        createPolicyStatement :: [Statement]
createPolicyStatement = [ CreatePolicy { Text
$sel:tableName:StatementCreateTable :: Text
tableName :: Text
tableName, $sel:action:StatementCreateTable :: Maybe PolicyAction
action = Maybe PolicyAction
forall a. Maybe a
Nothing, Text
$sel:name:StatementCreateTable :: Text
name :: Text
name, Maybe Expression
$sel:using:StatementCreateTable :: Maybe Expression
using :: Maybe Expression
using, Maybe Expression
$sel:check:StatementCreateTable :: Maybe Expression
check :: Maybe Expression
check } ]

data DeletePolicyOptions = DeletePolicyOptions
    { DeletePolicyOptions -> Text
tableName :: !Text
    , DeletePolicyOptions -> Text
policyName :: !Text
    }

deletePolicy :: DeletePolicyOptions -> Schema -> Schema
deletePolicy :: DeletePolicyOptions -> [Statement] -> [Statement]
deletePolicy DeletePolicyOptions { Text
$sel:tableName:DeletePolicyOptions :: DeletePolicyOptions -> Text
$sel:policyName:DeletePolicyOptions :: DeletePolicyOptions -> Text
tableName :: Text
policyName :: Text
.. } [Statement]
statements =
        [Statement]
statements
        [Statement] -> ([Statement] -> [Statement]) -> [Statement]
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> (Statement -> Bool) -> [Statement] -> [Statement]
forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
not (Bool -> Bool) -> (Statement -> Bool) -> Statement -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
forall {k} (cat :: k -> k -> *) (b :: k) (c :: k) (a :: k).
Category cat =>
cat b c -> cat a b -> cat a c
. Statement -> Bool
isSelectedPolicy)
    where
        isSelectedPolicy :: Statement -> Bool
        isSelectedPolicy :: Statement -> Bool
isSelectedPolicy policy :: Statement
policy@CreatePolicy { $sel:name:StatementCreateTable :: Statement -> Text
name = Text
pName, $sel:tableName:StatementCreateTable :: Statement -> Text
tableName = Text
pTable } = Text
pName Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
policyName Bool -> Bool -> Bool
&& Text
pTable Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
tableName
        isSelectedPolicy Statement
otherwise                                                = Bool
False

enableRowLevelSecurity :: Text -> Schema -> Schema
enableRowLevelSecurity :: Text -> [Statement] -> [Statement]
enableRowLevelSecurity Text
tableName [Statement]
schema =
    let
        rlsEnabled :: Bool
rlsEnabled = [Statement]
schema
                [Statement] -> ([Statement] -> Maybe Statement) -> Maybe Statement
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> (Statement -> Bool) -> [Statement] -> Maybe Statement
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find \case
                    EnableRowLevelSecurity { $sel:tableName:StatementCreateTable :: Statement -> Text
tableName = Text
rlsTable } -> Text
rlsTable Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
tableName
                    Statement
otherwise                                       -> Bool
False
                Maybe Statement -> (Maybe Statement -> Bool) -> Bool
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> Maybe Statement -> Bool
forall a. Maybe a -> Bool
isJust
    in if Bool
rlsEnabled
        then [Statement]
schema
        else [Statement]
schema [Statement] -> [Statement] -> [Statement]
forall a. Semigroup a => a -> a -> a
<> [ EnableRowLevelSecurity { Text
$sel:tableName:StatementCreateTable :: Text
tableName :: Text
tableName } ]

disableRowLevelSecurity :: Text -> Schema -> Schema
disableRowLevelSecurity :: Text -> [Statement] -> [Statement]
disableRowLevelSecurity Text
tableName [Statement]
schema = [Statement]
schema
        [Statement] -> ([Statement] -> [Statement]) -> [Statement]
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> (Statement -> Bool) -> [Statement] -> [Statement]
forall a. (a -> Bool) -> [a] -> [a]
filter \case
            EnableRowLevelSecurity { $sel:tableName:StatementCreateTable :: Statement -> Text
tableName = Text
rlsTable } -> Text
rlsTable Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
/= Text
tableName
            Statement
otherwise                                       -> Bool
True

disableRowLevelSecurityIfNoPolicies :: Text -> Schema -> Schema
disableRowLevelSecurityIfNoPolicies :: Text -> [Statement] -> [Statement]
disableRowLevelSecurityIfNoPolicies Text
tableName [Statement]
schema =
    let
        tableHasPolicies :: Bool
tableHasPolicies = [Statement]
schema
                [Statement] -> ([Statement] -> Maybe Statement) -> Maybe Statement
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> (Statement -> Bool) -> [Statement] -> Maybe Statement
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find \case
                    CreatePolicy { $sel:tableName:StatementCreateTable :: Statement -> Text
tableName = Text
policyTable } -> Text
policyTable Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
tableName
                    Statement
otherwise                                -> Bool
False
                Maybe Statement -> (Maybe Statement -> Bool) -> Bool
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> Maybe Statement -> Bool
forall a. Maybe a -> Bool
isJust
    in if Bool
tableHasPolicies
        then [Statement]
schema
        else Text -> [Statement] -> [Statement]
disableRowLevelSecurity Text
tableName [Statement]
schema


-- | Checks if there exists a @user_id@ column, and returns a policy based on that.
-- If there's no @user_id@ field on the table it will return an empty policy
--
-- This function also follows foreign keys to find the shortest path to a user_id.
-- E.g. when having a schema post_meta_tags (no user_id column) <-> posts (has a user_id) <-> users:
--
-- >                                                 post_id
-- >                            posts_meta_infos ────────────────►  posts
-- >                                                                 │
-- >                                                                 │
-- >                                                                 │
-- >                                                                 │
-- >                                                                 │
-- >                                                                 │ user_id
-- >                                                                 │
-- >                                                                 │
-- >                                                                 │
-- >                                                                 │
-- >                                          users  ◄───────────────┘
--
suggestPolicy :: Schema -> Statement -> Statement
suggestPolicy :: [Statement] -> Statement -> Statement
suggestPolicy [Statement]
schema (StatementCreateTable CreateTable { $sel:name:CreateTable :: CreateTable -> Text
name = Text
tableName, [Column]
$sel:columns:CreateTable :: CreateTable -> [Column]
columns :: [Column]
columns })
    | Maybe Column -> Bool
forall a. Maybe a -> Bool
isJust ((Column -> Bool) -> [Column] -> Maybe Column
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find Column -> Bool
isUserIdColumn [Column]
columns)  = CreatePolicy
        { $sel:name:StatementCreateTable :: Text
name = Text
"Users can manage their " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
tableName
        , $sel:action:StatementCreateTable :: Maybe PolicyAction
action = Maybe PolicyAction
forall a. Maybe a
Nothing
        , Text
$sel:tableName:StatementCreateTable :: Text
tableName :: Text
tableName
        , $sel:using:StatementCreateTable :: Maybe Expression
using = Expression -> Maybe Expression
forall a. a -> Maybe a
Just Expression
compareUserId
        , $sel:check:StatementCreateTable :: Maybe Expression
check = Expression -> Maybe Expression
forall a. a -> Maybe a
Just Expression
compareUserId
        }
    where
        compareUserId :: Expression
compareUserId = Expression -> Expression -> Expression
EqExpression (Text -> Expression
VarExpression Text
"user_id") (Text -> [Expression] -> Expression
CallExpression Text
"ihp_user_id" [])
suggestPolicy [Statement]
schema (StatementCreateTable CreateTable { $sel:name:CreateTable :: CreateTable -> Text
name = Text
tableName, [Column]
$sel:columns:CreateTable :: CreateTable -> [Column]
columns :: [Column]
columns }) =
            [(Column, Constraint, CreateTable)]
columnsWithFKAndRefTable
                [(Column, Constraint, CreateTable)]
-> ([(Column, Constraint, CreateTable)] -> [Statement])
-> [Statement]
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> ((Column, Constraint, CreateTable) -> Maybe Statement)
-> [(Column, Constraint, CreateTable)] -> [Statement]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe (Column, Constraint, CreateTable) -> Maybe Statement
columnWithFKAndRefTableToPolicy
                [Statement] -> ([Statement] -> Maybe Statement) -> Maybe Statement
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> [Statement] -> Maybe Statement
forall a. [a] -> Maybe a
head
                Maybe Statement -> (Maybe Statement -> Statement) -> Statement
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> Statement -> Maybe Statement -> Statement
forall a. a -> Maybe a -> a
fromMaybe (Statement
emptyPolicy)
        where
            referenced :: [Column]
referenced = [Column]
columns

            columnWithFKAndRefTableToPolicy :: (Column, Constraint, CreateTable) -> Maybe Statement
            columnWithFKAndRefTableToPolicy :: (Column, Constraint, CreateTable) -> Maybe Statement
columnWithFKAndRefTableToPolicy (Column
column, ForeignKeyConstraint { Maybe Text
$sel:referenceColumn:ForeignKeyConstraint :: Constraint -> Maybe Text
referenceColumn :: Maybe Text
referenceColumn }, CreateTable { $sel:name:CreateTable :: CreateTable -> Text
name = Text
refTableName, $sel:columns:CreateTable :: CreateTable -> [Column]
columns = [Column]
refTableColumns }) | Maybe Column -> Bool
forall a. Maybe a -> Bool
isJust ((Column -> Bool) -> [Column] -> Maybe Column
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find Column -> Bool
isUserIdColumn [Column]
refTableColumns) = Statement -> Maybe Statement
forall a. a -> Maybe a
Just CreatePolicy
                    { $sel:name:StatementCreateTable :: Text
name = Text
"Users can manage the " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
tableName Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" if they can see the " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text -> Text
tableNameToModelName Text
refTableName
                    , $sel:action:StatementCreateTable :: Maybe PolicyAction
action = Maybe PolicyAction
forall a. Maybe a
Nothing
                    , Text
$sel:tableName:StatementCreateTable :: Text
tableName :: Text
tableName
                    , $sel:using:StatementCreateTable :: Maybe Expression
using = Expression -> Maybe Expression
forall a. a -> Maybe a
Just Expression
delegateCheck
                    , $sel:check:StatementCreateTable :: Maybe Expression
check = Expression -> Maybe Expression
forall a. a -> Maybe a
Just Expression
delegateCheck
                    }
                where
                    delegateCheck :: Expression
delegateCheck = Expression -> Expression
ExistsExpression (
                            Select -> Expression
SelectExpression (
                                Select
                                { $sel:columns:Select :: [Expression]
columns = [Int -> Expression
IntExpression Int
1]
                                , $sel:from:Select :: Expression
from = Expression -> Text -> Expression
DotExpression (Text -> Expression
VarExpression Text
"public") Text
refTableName
                                , $sel:alias:Select :: Maybe Text
alias = Maybe Text
forall a. Maybe a
Nothing
                                , $sel:whereClause:Select :: Expression
whereClause = Expression -> Expression -> Expression
EqExpression (Expression -> Text -> Expression
DotExpression (Text -> Expression
VarExpression Text
refTableName) Text
refColumnName) (Expression -> Text -> Expression
DotExpression (Text -> Expression
VarExpression Text
tableName) (Column
column.name))
                                }
                            )
                        )
                    refColumnName :: Text
refColumnName = Maybe Text
referenceColumn Maybe Text -> (Maybe Text -> Text) -> Text
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> Text -> Maybe Text -> Text
forall a. a -> Maybe a -> a
fromMaybe Text
"id"

            columnWithFKAndRefTableToPolicy (Column, Constraint, CreateTable)
otherwise = Maybe Statement
forall a. Maybe a
Nothing


            columnsWithFKAndRefTable :: [(Column, Constraint, CreateTable)]
            columnsWithFKAndRefTable :: [(Column, Constraint, CreateTable)]
columnsWithFKAndRefTable =
                 [Column]
columns
                 [Column] -> ([Column] -> [Maybe Constraint]) -> [Maybe Constraint]
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> (Column -> Maybe Constraint) -> [Column] -> [Maybe Constraint]
forall a b. (a -> b) -> [a] -> [b]
map Column -> Maybe Constraint
findFK
                 [Maybe Constraint]
-> ([Maybe Constraint] -> [(Column, Maybe Constraint)])
-> [(Column, Maybe Constraint)]
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> [Column] -> [Maybe Constraint] -> [(Column, Maybe Constraint)]
forall a b. [a] -> [b] -> [(a, b)]
zip [Column]
columns
                 [(Column, Maybe Constraint)]
-> ([(Column, Maybe Constraint)] -> [(Column, Constraint)])
-> [(Column, Constraint)]
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> ((Column, Maybe Constraint) -> Maybe (Column, Constraint))
-> [(Column, Maybe Constraint)] -> [(Column, Constraint)]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe \case
                        (Column
col, Just Constraint
fk) -> (Column, Constraint) -> Maybe (Column, Constraint)
forall a. a -> Maybe a
Just (Column
col, Constraint
fk)
                        (Column
col, Maybe Constraint
Nothing) -> Maybe (Column, Constraint)
forall a. Maybe a
Nothing
                 [(Column, Constraint)]
-> ([(Column, Constraint)]
    -> [(Column, Constraint, Maybe CreateTable)])
-> [(Column, Constraint, Maybe CreateTable)]
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> ((Column, Constraint) -> (Column, Constraint, Maybe CreateTable))
-> [(Column, Constraint)]
-> [(Column, Constraint, Maybe CreateTable)]
forall a b. (a -> b) -> [a] -> [b]
map (\(Column
column, Constraint
fk) -> (Column
column, Constraint
fk, Constraint -> Maybe CreateTable
resolveFK Constraint
fk))
                 [(Column, Constraint, Maybe CreateTable)]
-> ([(Column, Constraint, Maybe CreateTable)]
    -> [(Column, Constraint, CreateTable)])
-> [(Column, Constraint, CreateTable)]
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> ((Column, Constraint, Maybe CreateTable)
 -> Maybe (Column, Constraint, CreateTable))
-> [(Column, Constraint, Maybe CreateTable)]
-> [(Column, Constraint, CreateTable)]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe  \case
                        (Column
column, Constraint
fk, Just CreateTable
refTable) -> (Column, Constraint, CreateTable)
-> Maybe (Column, Constraint, CreateTable)
forall a. a -> Maybe a
Just (Column
column, Constraint
fk, CreateTable
refTable)
                        (Column
column, Constraint
fk, Maybe CreateTable
Nothing)       -> Maybe (Column, Constraint, CreateTable)
forall a. Maybe a
Nothing

            findFK :: Column -> Maybe Constraint
            findFK :: Column -> Maybe Constraint
findFK Column
column =
                [Statement]
schema
                    [Statement] -> ([Statement] -> [Constraint]) -> [Constraint]
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> (Statement -> Maybe Constraint) -> [Statement] -> [Constraint]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe (\case
                        AddConstraint { $sel:tableName:StatementCreateTable :: Statement -> Text
tableName = Text
fkTable, $sel:constraint:StatementCreateTable :: Statement -> Constraint
constraint = fk :: Constraint
fk@(ForeignKeyConstraint { $sel:columnName:ForeignKeyConstraint :: Constraint -> Text
columnName = Text
fkCol }) } ->
                            if Text
fkTable Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
tableName Bool -> Bool -> Bool
&& Text
fkCol Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Column
column.name
                                then Constraint -> Maybe Constraint
forall a. a -> Maybe a
Just Constraint
fk
                                else Maybe Constraint
forall a. Maybe a
Nothing
                        Statement
otherwise -> Maybe Constraint
forall a. Maybe a
Nothing)
                    [Constraint]
-> ([Constraint] -> Maybe Constraint) -> Maybe Constraint
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> [Constraint] -> Maybe Constraint
forall a. [a] -> Maybe a
head

            resolveFK :: Constraint -> Maybe CreateTable
            resolveFK :: Constraint -> Maybe CreateTable
resolveFK ForeignKeyConstraint { Text
$sel:referenceTable:ForeignKeyConstraint :: Constraint -> Text
referenceTable :: Text
referenceTable } = [Statement]
schema
                    [Statement] -> ([Statement] -> Maybe Statement) -> Maybe Statement
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> (Statement -> Bool) -> [Statement] -> Maybe Statement
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find \case
                        StatementCreateTable CreateTable {  Text
$sel:name:CreateTable :: CreateTable -> Text
name :: Text
name }  -> Text
name Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
referenceTable
                        Statement
otheriwse                                   -> Bool
False
                    Maybe Statement
-> (Maybe Statement -> Maybe CreateTable) -> Maybe CreateTable
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> (Statement -> CreateTable) -> Maybe Statement -> Maybe CreateTable
forall a b. (a -> b) -> Maybe a -> Maybe b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap \case
                        StatementCreateTable CreateTable
table -> CreateTable
table

            emptyPolicy :: Statement
emptyPolicy = CreatePolicy { $sel:name:StatementCreateTable :: Text
name = Text
"", $sel:action:StatementCreateTable :: Maybe PolicyAction
action = Maybe PolicyAction
forall a. Maybe a
Nothing, Text
$sel:tableName:StatementCreateTable :: Text
tableName :: Text
tableName, $sel:using:StatementCreateTable :: Maybe Expression
using = Maybe Expression
forall a. Maybe a
Nothing, $sel:check:StatementCreateTable :: Maybe Expression
check = Maybe Expression
forall a. Maybe a
Nothing }

isUserIdColumn :: Column -> Bool
isUserIdColumn :: Column -> Bool
isUserIdColumn Column { $sel:name:Column :: Column -> Text
name = Text
"user_id" } = Bool
True
isUserIdColumn Column
otherwise                   = Bool
False


deleteTable :: Text -> Schema -> Schema
deleteTable :: Text -> [Statement] -> [Statement]
deleteTable Text
tableName [Statement]
statements =
    [Statement]
statements
    [Statement] -> ([Statement] -> [Statement]) -> [Statement]
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> (Statement -> Bool) -> [Statement] -> [Statement]
forall a. (a -> Bool) -> [a] -> [a]
filter \case
        StatementCreateTable CreateTable { Text
$sel:name:CreateTable :: CreateTable -> Text
name :: Text
name }       | Text
name Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
tableName            -> Bool
False
        AddConstraint { $sel:tableName:StatementCreateTable :: Statement -> Text
tableName = Text
constraintTable }   | Text
constraintTable Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
tableName -> Bool
False
        CreateIndex { $sel:tableName:StatementCreateTable :: Statement -> Text
tableName = Text
indexTable }          | Text
indexTable Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
tableName      -> Bool
False
        EnableRowLevelSecurity { $sel:tableName:StatementCreateTable :: Statement -> Text
tableName = Text
rlsTable } | Text
rlsTable Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
tableName        -> Bool
False
        CreatePolicy { $sel:tableName:StatementCreateTable :: Statement -> Text
tableName = Text
policyTable }        | Text
policyTable Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
tableName     -> Bool
False
        CreateTrigger { $sel:tableName:StatementCreateTable :: Statement -> Text
tableName = Text
triggerTable }      | Text
triggerTable Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
tableName    -> Bool
False
        Statement
otherwise -> Bool
True

updateTable :: Int -> Text -> Schema -> Schema
updateTable :: Int -> Text -> [Statement] -> [Statement]
updateTable Int
tableId Text
tableName [Statement]
statements =
    let
        oldTableName :: Text
oldTableName = ((.name) (CreateTable -> Text)
-> (Statement -> CreateTable) -> Statement -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
forall {k} (cat :: k -> k -> *) (b :: k) (c :: k) (a :: k).
Category cat =>
cat b c -> cat a b -> cat a c
. Statement -> CreateTable
unsafeGetCreateTable) ([Statement]
statements [Statement] -> Int -> Statement
forall a. HasCallStack => [a] -> Int -> a
!! Int
tableId)
    in
        [Statement]
statements
        [Statement] -> ([Statement] -> [Statement]) -> [Statement]
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> (Statement -> Statement) -> [Statement] -> [Statement]
forall a b. (a -> b) -> [a] -> [b]
map \case
            (StatementCreateTable table :: CreateTable
table@(CreateTable { Text
$sel:name:CreateTable :: CreateTable -> Text
name :: Text
name })) | Text
name Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
oldTableName -> CreateTable -> Statement
StatementCreateTable (CreateTable
table { $sel:name:CreateTable :: Text
name = Text
tableName })
            constraint :: Statement
constraint@(AddConstraint { $sel:tableName:StatementCreateTable :: Statement -> Text
tableName = Text
constraintTable, $sel:constraint:StatementCreateTable :: Statement -> Constraint
constraint = Constraint
c }) | Text
constraintTable Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
oldTableName -> (Statement
constraint :: Statement) { Text
$sel:tableName:StatementCreateTable :: Text
tableName :: Text
tableName, $sel:constraint:StatementCreateTable :: Constraint
constraint = Constraint
c { $sel:name:ForeignKeyConstraint :: Maybe Text
name = HasCallStack => Text -> Text -> Text -> Text
Text -> Text -> Text -> Text
Text.replace Text
oldTableName Text
tableName (Text -> Text) -> Maybe Text -> Maybe Text
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> (Constraint
c.name) } }
            index :: Statement
index@(CreateIndex { $sel:tableName:StatementCreateTable :: Statement -> Text
tableName = Text
indexTable, Text
$sel:indexName:StatementCreateTable :: Statement -> Text
indexName :: Text
indexName }) | Text
indexTable Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
oldTableName -> (Statement
index :: Statement) { Text
$sel:tableName:StatementCreateTable :: Text
tableName :: Text
tableName, $sel:indexName:StatementCreateTable :: Text
indexName = HasCallStack => Text -> Text -> Text -> Text
Text -> Text -> Text -> Text
Text.replace Text
oldTableName Text
tableName Text
indexName } 
            rls :: Statement
rls@(EnableRowLevelSecurity { $sel:tableName:StatementCreateTable :: Statement -> Text
tableName = Text
rlsTable }) | Text
rlsTable Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
oldTableName -> (Statement
rls :: Statement) { Text
$sel:tableName:StatementCreateTable :: Text
tableName :: Text
tableName }
            policy :: Statement
policy@(CreatePolicy { $sel:tableName:StatementCreateTable :: Statement -> Text
tableName = Text
policyTable, Text
$sel:name:StatementCreateTable :: Statement -> Text
name :: Text
name }) | Text
policyTable Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
oldTableName -> (Statement
policy :: Statement) { Text
$sel:tableName:StatementCreateTable :: Text
tableName :: Text
tableName, $sel:name:StatementCreateTable :: Text
name = HasCallStack => Text -> Text -> Text -> Text
Text -> Text -> Text -> Text
Text.replace Text
oldTableName Text
tableName Text
name }
            trigger :: Statement
trigger@(CreateTrigger { $sel:tableName:StatementCreateTable :: Statement -> Text
tableName = Text
triggerTable, Text
$sel:name:StatementCreateTable :: Statement -> Text
name :: Text
name }) | Text
triggerTable Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
oldTableName -> (Statement
trigger :: Statement) { Text
$sel:tableName:StatementCreateTable :: Text
tableName :: Text
tableName, $sel:name:StatementCreateTable :: Text
name = HasCallStack => Text -> Text -> Text -> Text
Text -> Text -> Text -> Text
Text.replace Text
oldTableName Text
tableName Text
name }
            Statement
otherwise -> Statement
otherwise  


updatedAtTriggerName :: Text -> Text
updatedAtTriggerName :: Text -> Text
updatedAtTriggerName Text
tableName = Text
"update_" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
tableName Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"_updated_at"

addUpdatedAtTrigger :: Text -> [Statement] -> [Statement]
addUpdatedAtTrigger :: Text -> [Statement] -> [Statement]
addUpdatedAtTrigger Text
tableName [Statement]
schema =
        [Statement]
addFunctionOperator [Statement] -> [Statement] -> [Statement]
forall a. Semigroup a => a -> a -> a
<> [Statement]
schema [Statement] -> [Statement] -> [Statement]
forall a. Semigroup a => a -> a -> a
<> [Statement
trigger]
    where
        trigger :: Statement
        trigger :: Statement
trigger = CreateTrigger
            { $sel:name:StatementCreateTable :: Text
name = Text -> Text
updatedAtTriggerName Text
tableName
            , $sel:eventWhen:StatementCreateTable :: TriggerEventWhen
eventWhen = TriggerEventWhen
Before
            , $sel:event:StatementCreateTable :: TriggerEvent
event = TriggerEvent
TriggerOnUpdate
            , Text
$sel:tableName:StatementCreateTable :: Text
tableName :: Text
tableName
            , $sel:for:StatementCreateTable :: TriggerFor
for = TriggerFor
ForEachRow
            , $sel:whenCondition:StatementCreateTable :: Maybe Expression
whenCondition = Maybe Expression
forall a. Maybe a
Nothing
            , $sel:functionName:StatementCreateTable :: Text
functionName = Statement
setUpdatedAtToNowTrigger.functionName
            , $sel:arguments:StatementCreateTable :: [Expression]
arguments = []
            }

        addFunctionOperator :: [Statement]
        addFunctionOperator :: [Statement]
addFunctionOperator =
            if Text -> Bool
hasFunction (Statement
setUpdatedAtToNowTrigger.functionName)
                then []
                else [Statement
setUpdatedAtToNowTrigger]

        hasFunction :: Text -> Bool
        hasFunction :: Text -> Bool
hasFunction Text
name = [Statement]
schema
                [Statement] -> ([Statement] -> Maybe Statement) -> Maybe Statement
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> (Statement -> Bool) -> [Statement] -> Maybe Statement
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find \case
                    CreateFunction { $sel:functionName:StatementCreateTable :: Statement -> Text
functionName = Text
fnName } -> Text
name Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
fnName
                    Statement
otherwise -> Bool
False
                Maybe Statement -> (Maybe Statement -> Bool) -> Bool
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> Maybe Statement -> Bool
forall a. Maybe a -> Bool
isJust

        setUpdatedAtToNowTrigger :: Statement
        setUpdatedAtToNowTrigger :: Statement
setUpdatedAtToNowTrigger = 
            CreateFunction
                { $sel:functionName:StatementCreateTable :: Text
functionName = Text
"set_updated_at_to_now"
                , $sel:functionBody:StatementCreateTable :: Text
functionBody = Text
"\n" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> [trimming|
                    BEGIN
                        NEW.updated_at = NOW();
                        RETURN NEW;
                    END;
                |] Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"\n"
                , $sel:functionArguments:StatementCreateTable :: [(Text, PostgresType)]
functionArguments = []
                , $sel:orReplace:StatementCreateTable :: Bool
orReplace = Bool
False
                , $sel:returns:StatementCreateTable :: PostgresType
returns = PostgresType
PTrigger
                , $sel:language:StatementCreateTable :: Text
language = Text
"plpgsql"
                }

deleteTriggerIfExists :: Text -> [Statement] -> [Statement]
deleteTriggerIfExists :: Text -> [Statement] -> [Statement]
deleteTriggerIfExists Text
triggerName [Statement]
statements = (Statement -> Bool) -> [Statement] -> [Statement]
forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
not (Bool -> Bool) -> (Statement -> Bool) -> Statement -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
forall {k} (cat :: k -> k -> *) (b :: k) (c :: k) (a :: k).
Category cat =>
cat b c -> cat a b -> cat a c
. Statement -> Bool
isTheTriggerToBeDeleted) [Statement]
statements
    where
        isTheTriggerToBeDeleted :: Statement -> Bool
isTheTriggerToBeDeleted CreateTrigger { Text
$sel:name:StatementCreateTable :: Statement -> Text
name :: Text
name } = Text
triggerName Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
name
        isTheTriggerToBeDeleted Statement
_                      = Bool
False

data DeleteColumnOptions
    = DeleteColumnOptions
    { DeleteColumnOptions -> Text
tableName :: !Text
    , DeleteColumnOptions -> Text
columnName :: !Text
    , DeleteColumnOptions -> Int
columnId :: !Int
    }

deleteColumn :: DeleteColumnOptions -> Schema -> Schema
deleteColumn :: DeleteColumnOptions -> [Statement] -> [Statement]
deleteColumn DeleteColumnOptions { Int
Text
$sel:tableName:DeleteColumnOptions :: DeleteColumnOptions -> Text
$sel:columnName:DeleteColumnOptions :: DeleteColumnOptions -> Text
$sel:columnId:DeleteColumnOptions :: DeleteColumnOptions -> Int
tableName :: Text
columnName :: Text
columnId :: Int
.. } [Statement]
schema =
        [Statement]
schema
        [Statement] -> ([Statement] -> [Statement]) -> [Statement]
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> (Statement -> Statement) -> [Statement] -> [Statement]
forall a b. (a -> b) -> [a] -> [b]
map Statement -> Statement
deleteColumnInTable
        [Statement] -> ([Statement] -> [Statement]) -> [Statement]
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> ((Statement -> Bool) -> [Statement] -> [Statement]
forall a. (a -> Bool) -> [a] -> [a]
filter \case
                AddConstraint { $sel:tableName:StatementCreateTable :: Statement -> Text
tableName = Text
fkTable, $sel:constraint:StatementCreateTable :: Statement -> Constraint
constraint = ForeignKeyConstraint { $sel:columnName:ForeignKeyConstraint :: Constraint -> Text
columnName = Text
fkColumn } } | Text
fkTable Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
tableName Bool -> Bool -> Bool
&& Text
fkColumn Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
columnName -> Bool
False
                index :: Statement
index@(CreateIndex {}) | Statement -> Text -> Text -> Bool
isIndexStatementReferencingTableColumn Statement
index Text
tableName Text
columnName -> Bool
False
                Statement
otherwise -> Bool
True
            )
        [Statement] -> ([Statement] -> [Statement]) -> [Statement]
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> (if Text
columnName Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
"updated_at"
                then Text -> [Statement] -> [Statement]
deleteTriggerIfExists (Text -> Text
updatedAtTriggerName Text
tableName)
                else \[Statement]
schema -> [Statement]
schema
            )
        [Statement] -> ([Statement] -> [Statement]) -> [Statement]
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> (Statement -> Bool) -> [Statement] -> [Statement]
forall a. (a -> Bool) -> [a] -> [a]
filter Statement -> Bool
deletePolicyReferencingPolicy
    where
        deleteColumnInTable :: Statement -> Statement
        deleteColumnInTable :: Statement -> Statement
deleteColumnInTable (StatementCreateTable table :: CreateTable
table@CreateTable { Text
$sel:name:CreateTable :: CreateTable -> Text
name :: Text
name, [Column]
$sel:columns:CreateTable :: CreateTable -> [Column]
columns :: [Column]
columns }) | Text
name Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
tableName = CreateTable -> Statement
StatementCreateTable (CreateTable -> Statement) -> CreateTable -> Statement
forall a b. (a -> b) -> a -> b
$ CreateTable
table { $sel:columns:CreateTable :: [Column]
columns = Column -> [Column] -> [Column]
forall a. Eq a => a -> [a] -> [a]
delete ([Column]
columns [Column] -> Int -> Column
forall a. HasCallStack => [a] -> Int -> a
!! Int
columnId) [Column]
columns}
        deleteColumnInTable Statement
statement = Statement
statement

        deletePolicyReferencingPolicy :: Statement -> Bool
        deletePolicyReferencingPolicy :: Statement -> Bool
deletePolicyReferencingPolicy CreatePolicy { $sel:tableName:StatementCreateTable :: Statement -> Text
tableName = Text
policyTable, Maybe Expression
$sel:using:StatementCreateTable :: Statement -> Maybe Expression
using :: Maybe Expression
using, Maybe Expression
$sel:check:StatementCreateTable :: Statement -> Maybe Expression
check :: Maybe Expression
check } | Text
policyTable Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
tableName = 
            case (Maybe Expression
using, Maybe Expression
check) of
                (Just Expression
using, Maybe Expression
Nothing) -> Bool -> Bool
not (Expression -> Bool
isRef Expression
using)
                (Maybe Expression
Nothing, Just Expression
check) -> Bool -> Bool
not (Expression -> Bool
isRef Expression
check)
                (Just Expression
using, Just Expression
check) -> Bool -> Bool
not (Expression -> Bool
isRef Expression
using Bool -> Bool -> Bool
&& Expression -> Bool
isRef Expression
check)
            where
                isRef :: Expression -> Bool
                isRef :: Expression -> Bool
isRef (TextExpression {}) = Bool
False
                isRef (VarExpression Text
var) = Text
var Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
columnName
                isRef (CallExpression Text
_ [Expression]
args) = (Bool -> Bool -> Bool) -> Bool -> [Bool] -> Bool
forall b a. (b -> a -> b) -> b -> [a] -> b
forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl' Bool -> Bool -> Bool
(||) Bool
False ((Expression -> Bool) -> [Expression] -> [Bool]
forall a b. (a -> b) -> [a] -> [b]
map Expression -> Bool
isRef [Expression]
args)
                isRef (NotEqExpression Expression
a Expression
b) = Expression -> Bool
isRef Expression
a Bool -> Bool -> Bool
|| Expression -> Bool
isRef Expression
b
                isRef (EqExpression Expression
a Expression
b) = Expression -> Bool
isRef Expression
a Bool -> Bool -> Bool
|| Expression -> Bool
isRef Expression
b
                isRef (AndExpression Expression
a Expression
b) = Expression -> Bool
isRef Expression
a Bool -> Bool -> Bool
|| Expression -> Bool
isRef Expression
b
                isRef (IsExpression Expression
a Expression
b) = Expression -> Bool
isRef Expression
a Bool -> Bool -> Bool
|| Expression -> Bool
isRef Expression
b
                isRef (InExpression Expression
a Expression
b) = Expression -> Bool
isRef Expression
a Bool -> Bool -> Bool
|| Expression -> Bool
isRef Expression
b
                isRef (NotExpression Expression
a) = Expression -> Bool
isRef Expression
a
                isRef (ExistsExpression Expression
a) = Expression -> Bool
isRef Expression
a
                isRef (OrExpression Expression
a Expression
b) = Expression -> Bool
isRef Expression
a Bool -> Bool -> Bool
|| Expression -> Bool
isRef Expression
b
                isRef (LessThanExpression Expression
a Expression
b) = Expression -> Bool
isRef Expression
a Bool -> Bool -> Bool
|| Expression -> Bool
isRef Expression
b
                isRef (LessThanOrEqualToExpression Expression
a Expression
b) = Expression -> Bool
isRef Expression
a Bool -> Bool -> Bool
|| Expression -> Bool
isRef Expression
b
                isRef (GreaterThanExpression Expression
a Expression
b) = Expression -> Bool
isRef Expression
a Bool -> Bool -> Bool
|| Expression -> Bool
isRef Expression
b
                isRef (GreaterThanOrEqualToExpression Expression
a Expression
b) = Expression -> Bool
isRef Expression
a Bool -> Bool -> Bool
|| Expression -> Bool
isRef Expression
b
                isRef (DoubleExpression Double
_) = Bool
False
                isRef (IntExpression Int
_) = Bool
False
                isRef (TypeCastExpression Expression
a PostgresType
_) = Expression -> Bool
isRef Expression
a
                isRef (SelectExpression Select
_) = Bool
False
                isRef (DotExpression Expression
a Text
_) = Expression -> Bool
isRef Expression
a
                isRef (ConcatenationExpression Expression
a Expression
b) = Expression -> Bool
isRef Expression
a Bool -> Bool -> Bool
|| Expression -> Bool
isRef Expression
b
        deletePolicyReferencingPolicy Statement
otherwise = Bool
True

-- | Returns True if a CreateIndex statement references a specific column
--
-- E.g. given a schema like this:
-- > CREATE TABLE users (
-- >     email TEXT NOT NULL
-- > );
-- >
-- > CREATE UNIQUE INDEX users_email_index ON users (LOWER(email));
-- >
--
-- You can find all indices to the email column of the users table like this:
--
-- >>> filter (isIndexStatementReferencingTableColumn "users" "email") database
-- [CreateIndex { indexName = "users_email", unique = True, tableName = "users", expressions = [CallExpression "LOWER" [VarEpression "email"]] }]
--
isIndexStatementReferencingTableColumn :: Statement -> Text -> Text -> Bool
isIndexStatementReferencingTableColumn :: Statement -> Text -> Text -> Bool
isIndexStatementReferencingTableColumn Statement
statement Text
tableName Text
columnName = Statement -> Bool
isReferenced Statement
statement
    where
        -- | Returns True if a statement is an CreateIndex statement that references our specific column
        --
        -- An index references a table if it references the target table and one of the index expressions contains a reference to our column
        isReferenced :: Statement -> Bool
        isReferenced :: Statement -> Bool
isReferenced CreateIndex { $sel:tableName:StatementCreateTable :: Statement -> Text
tableName = Text
indexTableName, [IndexColumn]
$sel:columns:StatementCreateTable :: Statement -> [IndexColumn]
columns :: [IndexColumn]
columns } = Text
indexTableName Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
tableName Bool -> Bool -> Bool
&& [Expression] -> Bool
expressionsReferencesColumn ((IndexColumn -> Expression) -> [IndexColumn] -> [Expression]
forall a b. (a -> b) -> [a] -> [b]
map (.column) [IndexColumn]
columns)
        isReferenced Statement
otherwise = Bool
False

        -- | Returns True if a list of expressions references the columnName
        expressionsReferencesColumn :: [Expression] -> Bool
        expressionsReferencesColumn :: [Expression] -> Bool
expressionsReferencesColumn [Expression]
expressions = [Expression]
expressions
                [Expression] -> ([Expression] -> [Bool]) -> [Bool]
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> (Expression -> Bool) -> [Expression] -> [Bool]
forall a b. (a -> b) -> [a] -> [b]
map Expression -> Bool
expressionReferencesColumn
                [Bool] -> ([Bool] -> Bool) -> Bool
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> [Bool] -> Bool
forall (t :: * -> *). Foldable t => t Bool -> Bool
List.or

        -- | Walks the expression tree and returns True if there's a VarExpression with the column name
        expressionReferencesColumn :: Expression -> Bool
        expressionReferencesColumn :: Expression -> Bool
expressionReferencesColumn = \case
            TextExpression Text
_ -> Bool
False
            VarExpression Text
varName -> Text
varName Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
columnName
            CallExpression Text
_ [Expression]
expressions -> [Expression]
expressions
                    [Expression] -> ([Expression] -> [Bool]) -> [Bool]
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> (Expression -> Bool) -> [Expression] -> [Bool]
forall a b. (a -> b) -> [a] -> [b]
map Expression -> Bool
expressionReferencesColumn
                    [Bool] -> ([Bool] -> Bool) -> Bool
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> [Bool] -> Bool
forall (t :: * -> *). Foldable t => t Bool -> Bool
List.or
            NotEqExpression Expression
a Expression
b -> Expression -> Bool
expressionReferencesColumn Expression
a Bool -> Bool -> Bool
|| Expression -> Bool
expressionReferencesColumn Expression
b
            EqExpression Expression
a Expression
b -> Expression -> Bool
expressionReferencesColumn Expression
a Bool -> Bool -> Bool
|| Expression -> Bool
expressionReferencesColumn Expression
b
            AndExpression Expression
a Expression
b -> Expression -> Bool
expressionReferencesColumn Expression
a Bool -> Bool -> Bool
|| Expression -> Bool
expressionReferencesColumn Expression
b
            IsExpression Expression
a Expression
b -> Expression -> Bool
expressionReferencesColumn Expression
a Bool -> Bool -> Bool
|| Expression -> Bool
expressionReferencesColumn Expression
b
            NotExpression Expression
a -> Expression -> Bool
expressionReferencesColumn Expression
a
            OrExpression Expression
a Expression
b -> Expression -> Bool
expressionReferencesColumn Expression
a Bool -> Bool -> Bool
|| Expression -> Bool
expressionReferencesColumn Expression
b
            LessThanExpression Expression
a Expression
b -> Expression -> Bool
expressionReferencesColumn Expression
a Bool -> Bool -> Bool
|| Expression -> Bool
expressionReferencesColumn Expression
b
            LessThanOrEqualToExpression Expression
a Expression
b -> Expression -> Bool
expressionReferencesColumn Expression
a Bool -> Bool -> Bool
|| Expression -> Bool
expressionReferencesColumn Expression
b
            GreaterThanExpression Expression
a Expression
b -> Expression -> Bool
expressionReferencesColumn Expression
a Bool -> Bool -> Bool
|| Expression -> Bool
expressionReferencesColumn Expression
b
            GreaterThanOrEqualToExpression Expression
a Expression
b -> Expression -> Bool
expressionReferencesColumn Expression
a Bool -> Bool -> Bool
|| Expression -> Bool
expressionReferencesColumn Expression
b

doesHaveExistingPolicies :: [Statement] -> Text -> Bool
doesHaveExistingPolicies :: [Statement] -> Text -> Bool
doesHaveExistingPolicies [Statement]
statements Text
tableName = [Statement]
statements
                [Statement] -> ([Statement] -> Maybe Statement) -> Maybe Statement
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> (Statement -> Bool) -> [Statement] -> Maybe Statement
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find \case
                    CreatePolicy { $sel:tableName:StatementCreateTable :: Statement -> Text
tableName = Text
tableName' } -> Text
tableName' Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
tableName
                    Statement
otherwise                               -> Bool
False
                Maybe Statement -> (Maybe Statement -> Bool) -> Bool
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> Maybe Statement -> Bool
forall a. Maybe a -> Bool
isJust


deleteIndex :: Text -> Schema -> Schema
deleteIndex :: Text -> [Statement] -> [Statement]
deleteIndex Text
indexName [Statement]
statements =
    [Statement]
statements
    [Statement] -> ([Statement] -> [Statement]) -> [Statement]
forall {t1} {t2}. t1 -> (t1 -> t2) -> t2
|> (Statement -> Bool) -> [Statement] -> [Statement]
forall a. (a -> Bool) -> [a] -> [a]
filter \case
        CreateIndex { $sel:indexName:StatementCreateTable :: Statement -> Text
indexName = Text
name } | Text
name Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
indexName -> Bool
False 
        Statement
otherwise                                            -> Bool
True


data UpdateIndexOptions = UpdateIndexOptions
    { UpdateIndexOptions -> Text
indexName :: !Text
    , UpdateIndexOptions -> Text
newIndexName :: !Text
    , UpdateIndexOptions -> [IndexColumn]
indexColumns :: ![IndexColumn]
    }
updateIndex :: UpdateIndexOptions -> Schema -> Schema
updateIndex :: UpdateIndexOptions -> [Statement] -> [Statement]
updateIndex UpdateIndexOptions { Text
$sel:indexName:UpdateIndexOptions :: UpdateIndexOptions -> Text
indexName :: Text
indexName, Text
$sel:newIndexName:UpdateIndexOptions :: UpdateIndexOptions -> Text
newIndexName :: Text
newIndexName, [IndexColumn]
$sel:indexColumns:UpdateIndexOptions :: UpdateIndexOptions -> [IndexColumn]
indexColumns :: [IndexColumn]
indexColumns } [Statement]
schema =
        (Statement -> Statement) -> [Statement] -> [Statement]
forall a b. (a -> b) -> [a] -> [b]
map Statement -> Statement
updateFn [Statement]
schema
     where
        updateFn :: Statement -> Statement
        updateFn :: Statement -> Statement
updateFn index :: Statement
index@(CreateIndex { $sel:indexName:StatementCreateTable :: Statement -> Text
indexName = Text
n }) | Text
n Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
indexName = Statement
index { $sel:indexName:StatementCreateTable :: Text
indexName = Text
newIndexName, $sel:columns:StatementCreateTable :: [IndexColumn]
columns = [IndexColumn]
indexColumns }
        updateFn Statement
statement = Statement
statement