{-|
Module: IHP.IDE.SchemaDesigner.Compiler
Description: Compiles AST of SQL to DDL
Copyright: (c) digitally induced GmbH, 2020
-}
module IHP.IDE.SchemaDesigner.Compiler (compileSql, writeSchema, compileIdentifier, compileExpression, compilePostgresType) where

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

writeSchema :: [Statement] -> IO ()
writeSchema :: [Statement] -> IO ()
writeSchema ![Statement]
statements = do
    let sortedStatements :: [Statement]
sortedStatements = (Statement -> Statement -> Ordering) -> [Statement] -> [Statement]
forall a. (a -> a -> Ordering) -> [a] -> [a]
sortBy Statement -> Statement -> Ordering
compareStatement [Statement]
statements
    FilePath -> Text -> IO ()
Text.writeFile FilePath
"Application/Schema.sql" ([Statement] -> Text
compileSql [Statement]
sortedStatements)

compileSql :: [Statement] -> Text
compileSql :: [Statement] -> Text
compileSql [Statement]
statements = [Statement]
statements
    [Statement] -> ([Statement] -> [Text]) -> [Text]
forall t1 t2. t1 -> (t1 -> t2) -> t2
|> (Statement -> Text) -> [Statement] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map Statement -> Text
compileStatement
    [Text] -> ([Text] -> Text) -> Text
forall t1 t2. t1 -> (t1 -> t2) -> t2
|> [Text] -> Text
unlines

compileStatement :: Statement -> Text
compileStatement :: Statement -> Text
compileStatement (StatementCreateTable 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, [Constraint]
$sel:constraints:CreateTable :: CreateTable -> [Constraint]
constraints :: [Constraint]
constraints }) = Text
"CREATE TABLE " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text -> Text
compileIdentifier Text
name Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" (\n" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text -> [Text] -> Text
intercalate Text
",\n" ((Column -> Text) -> [Column] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map (PrimaryKeyConstraint -> Column -> Text
compileColumn PrimaryKeyConstraint
primaryKeyConstraint) [Column]
columns [Text] -> [Text] -> [Text]
forall a. Semigroup a => a -> a -> a
<> [Text] -> (Text -> [Text]) -> Maybe Text -> [Text]
forall b a. b -> (a -> b) -> Maybe a -> b
maybe [] ((Text -> [Text] -> [Text]
forall a. a -> [a] -> [a]
:[]) (Text -> [Text]) -> (Text -> Text) -> Text -> [Text]
forall k (cat :: k -> k -> *) (b :: k) (c :: k) (a :: k).
Category cat =>
cat b c -> cat a b -> cat a c
. Text -> Text
forall a. (Semigroup a, IsString a) => a -> a
indent) (PrimaryKeyConstraint -> Maybe Text
compilePrimaryKeyConstraint PrimaryKeyConstraint
primaryKeyConstraint) [Text] -> [Text] -> [Text]
forall a. Semigroup a => a -> a -> a
<> (Constraint -> Text) -> [Constraint] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map (Text -> Text
forall a. (Semigroup a, IsString a) => a -> a
indent (Text -> Text) -> (Constraint -> Text) -> Constraint -> Text
forall k (cat :: k -> k -> *) (b :: k) (c :: k) (a :: k).
Category cat =>
cat b c -> cat a b -> cat a c
. Constraint -> Text
compileConstraint) [Constraint]
constraints) Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"\n);"
compileStatement CreateEnumType { Text
$sel:name:StatementCreateTable :: Statement -> Text
name :: Text
name, [Text]
$sel:values:StatementCreateTable :: Statement -> [Text]
values :: [Text]
values } = Text
"CREATE TYPE " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text -> Text
compileIdentifier Text
name Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" AS ENUM (" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text -> [Text] -> Text
intercalate Text
", " ([Text]
values [Text] -> ([Text] -> [Expression]) -> [Expression]
forall t1 t2. t1 -> (t1 -> t2) -> t2
|> (Text -> Expression) -> [Text] -> [Expression]
forall a b. (a -> b) -> [a] -> [b]
map Text -> Expression
TextExpression [Expression] -> ([Expression] -> [Text]) -> [Text]
forall t1 t2. t1 -> (t1 -> t2) -> t2
|> (Expression -> Text) -> [Expression] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map Expression -> Text
compileExpression) Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
");"
compileStatement CreateExtension { Text
name :: Text
$sel:name:StatementCreateTable :: Statement -> Text
name, Bool
$sel:ifNotExists:StatementCreateTable :: Statement -> Bool
ifNotExists :: Bool
ifNotExists } = Text
"CREATE EXTENSION " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> (if Bool
ifNotExists then Text
"IF NOT EXISTS " else Text
"") Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"\"" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text -> Text
compileIdentifier Text
name Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"\";"
compileStatement AddConstraint { Text
$sel:tableName:StatementCreateTable :: Statement -> Text
tableName :: Text
tableName, Text
$sel:constraintName:StatementCreateTable :: Statement -> Text
constraintName :: Text
constraintName, Constraint
$sel:constraint:StatementCreateTable :: Statement -> Constraint
constraint :: Constraint
constraint } = Text
"ALTER TABLE " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text -> Text
compileIdentifier Text
tableName Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" ADD CONSTRAINT " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text -> Text
compileIdentifier Text
constraintName Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Constraint -> Text
compileConstraint Constraint
constraint Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
";"
compileStatement Comment { Text
$sel:content:StatementCreateTable :: Statement -> Text
content :: Text
content } = Text
"-- " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
content
compileStatement CreateIndex { Text
$sel:indexName:StatementCreateTable :: Statement -> Text
indexName :: Text
indexName, Text
tableName :: Text
$sel:tableName:StatementCreateTable :: Statement -> Text
tableName, [Text]
$sel:columnNames:StatementCreateTable :: Statement -> [Text]
columnNames :: [Text]
columnNames } = Text
"CREATE INDEX " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
indexName Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" ON " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
tableName Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" (" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> (Text -> [Text] -> Text
intercalate Text
", " [Text]
columnNames) Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
");"
compileStatement UnknownStatement { Text
$sel:raw:StatementCreateTable :: Statement -> Text
raw :: Text
raw } = Text
raw

-- | Emit a PRIMARY KEY constraint when there are multiple primary key columns
compilePrimaryKeyConstraint :: PrimaryKeyConstraint -> Maybe Text
compilePrimaryKeyConstraint :: PrimaryKeyConstraint -> Maybe Text
compilePrimaryKeyConstraint PrimaryKeyConstraint { [Text]
$sel:primaryKeyColumnNames:PrimaryKeyConstraint :: PrimaryKeyConstraint -> [Text]
primaryKeyColumnNames :: [Text]
primaryKeyColumnNames } =
    case [Text]
primaryKeyColumnNames of
        [] -> Maybe Text
forall a. Maybe a
Nothing
        [Text
_] -> Maybe Text
forall a. Maybe a
Nothing
        [Text]
names -> Text -> Maybe Text
forall a. a -> Maybe a
Just (Text -> Maybe Text) -> Text -> Maybe Text
forall a b. (a -> b) -> a -> b
$ Text
"PRIMARY KEY(" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text -> [Text] -> Text
intercalate Text
", " [Text]
names Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
")"

compileConstraint :: Constraint -> Text
compileConstraint :: Constraint -> Text
compileConstraint ForeignKeyConstraint { Text
$sel:columnName:ForeignKeyConstraint :: Constraint -> Text
columnName :: Text
columnName, Text
$sel:referenceTable:ForeignKeyConstraint :: Constraint -> Text
referenceTable :: Text
referenceTable, Maybe Text
$sel:referenceColumn:ForeignKeyConstraint :: Constraint -> Maybe Text
referenceColumn :: Maybe Text
referenceColumn, Maybe OnDelete
$sel:onDelete:ForeignKeyConstraint :: Constraint -> Maybe OnDelete
onDelete :: Maybe OnDelete
onDelete } = Text
"FOREIGN KEY (" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text -> Text
compileIdentifier Text
columnName Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
") REFERENCES " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text -> Text
compileIdentifier Text
referenceTable Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> (if Maybe Text -> Bool
forall a. Maybe a -> Bool
isJust Maybe Text
referenceColumn then Text
" (" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Maybe Text -> Text
forall a. HasCallStack => Maybe a -> a
fromJust Maybe Text
referenceColumn Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
")" else Text
"") Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Maybe OnDelete -> Text
compileOnDelete Maybe OnDelete
onDelete
compileConstraint UniqueConstraint { [Text]
$sel:columnNames:ForeignKeyConstraint :: Constraint -> [Text]
columnNames :: [Text]
columnNames } = Text
"UNIQUE(" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text -> [Text] -> Text
intercalate Text
", " [Text]
columnNames Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
")"
compileConstraint CheckConstraint { Expression
$sel:checkExpression:ForeignKeyConstraint :: Constraint -> Expression
checkExpression :: Expression
checkExpression } = Text
"CHECK (" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Expression -> Text
compileExpression Expression
checkExpression Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
")"

compileOnDelete :: Maybe OnDelete -> Text
compileOnDelete :: Maybe OnDelete -> Text
compileOnDelete Maybe OnDelete
Nothing = Text
""
compileOnDelete (Just OnDelete
NoAction) = Text
"ON DELETE NO ACTION"
compileOnDelete (Just OnDelete
Restrict) = Text
"ON DELETE RESTRICT"
compileOnDelete (Just OnDelete
SetNull) = Text
"ON DELETE SET NULL"
compileOnDelete (Just OnDelete
SetDefault) = Text
"ON DELETE SET DEFAULT"
compileOnDelete (Just OnDelete
Cascade) = Text
"ON DELETE CASCADE"

compileColumn :: PrimaryKeyConstraint -> Column -> Text
compileColumn :: PrimaryKeyConstraint -> Column -> Text
compileColumn PrimaryKeyConstraint
primaryKeyConstraint Column { Text
$sel:name:Column :: Column -> Text
name :: Text
name, PostgresType
$sel:columnType:Column :: Column -> PostgresType
columnType :: PostgresType
columnType, Maybe Expression
$sel:defaultValue:Column :: Column -> Maybe Expression
defaultValue :: Maybe Expression
defaultValue, Bool
$sel:notNull:Column :: Column -> Bool
notNull :: Bool
notNull, Bool
$sel:isUnique:Column :: Column -> Bool
isUnique :: Bool
isUnique } =
    Text
"    " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> [Text] -> Text
unwords ([Maybe Text] -> [Text]
forall a. [Maybe a] -> [a]
catMaybes
        [ Text -> Maybe Text
forall a. a -> Maybe a
Just (Text -> Text
compileIdentifier Text
name)
        , Text -> Maybe Text
forall a. a -> Maybe a
Just (PostgresType -> Text
compilePostgresType PostgresType
columnType)
        , (Expression -> Text) -> Maybe Expression -> Maybe Text
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Expression -> Text
compileDefaultValue Maybe Expression
defaultValue
        , Maybe Text
primaryKeyColumnConstraint
        , if Bool
notNull then Text -> Maybe Text
forall a. a -> Maybe a
Just Text
"NOT NULL" else Maybe Text
forall a. Maybe a
Nothing
        , if Bool
isUnique then Text -> Maybe Text
forall a. a -> Maybe a
Just Text
"UNIQUE" else Maybe Text
forall a. Maybe a
Nothing
        ])
    where
        -- Emit a PRIMARY KEY column constraint if this is the only primary key column
        primaryKeyColumnConstraint :: Maybe Text
primaryKeyColumnConstraint = case PrimaryKeyConstraint
primaryKeyConstraint of
            PrimaryKeyConstraint [Text
primaryKeyColumn]
                | Text
name Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
primaryKeyColumn -> Text -> Maybe Text
forall a. a -> Maybe a
Just Text
"PRIMARY KEY"
                | Bool
otherwise -> Maybe Text
forall a. Maybe a
Nothing
            PrimaryKeyConstraint [Text]
_ -> Maybe Text
forall a. Maybe a
Nothing

compileDefaultValue :: Expression -> Text
compileDefaultValue :: Expression -> Text
compileDefaultValue Expression
value = Text
"DEFAULT " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Expression -> Text
compileExpression Expression
value

compileExpression :: Expression -> Text
compileExpression :: Expression -> Text
compileExpression (TextExpression Text
value) = Text
"'" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
value Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"'"
compileExpression (VarExpression Text
name) = Text
name
compileExpression (CallExpression Text
func [Expression]
args) = Text
func Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"(" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text -> [Text] -> Text
intercalate Text
", " ((Expression -> Text) -> [Expression] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map Expression -> Text
compileExpression [Expression]
args) Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
")"
compileExpression (NotEqExpression Expression
a Expression
b) = Expression -> Text
compileExpression Expression
a Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" <> " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Expression -> Text
compileExpression Expression
b

compareStatement :: Statement -> Statement -> Ordering
compareStatement (CreateEnumType {}) Statement
_ = Ordering
LT
compareStatement (StatementCreateTable CreateTable {}) (AddConstraint {}) = Ordering
LT
compareStatement (a :: Statement
a@AddConstraint {}) (b :: Statement
b@AddConstraint {}) = Text -> Text -> Ordering
forall a. Ord a => a -> a -> Ordering
compare (Proxy "constraintName" -> Statement -> Text
forall model (name :: Symbol) value.
(KnownSymbol name, HasField name model value) =>
Proxy name -> model -> value
get IsLabel "constraintName" (Proxy "constraintName")
Proxy "constraintName"
#constraintName Statement
a) (Proxy "constraintName" -> Statement -> Text
forall model (name :: Symbol) value.
(KnownSymbol name, HasField name model value) =>
Proxy name -> model -> value
get IsLabel "constraintName" (Proxy "constraintName")
Proxy "constraintName"
#constraintName Statement
b)
compareStatement (AddConstraint {}) Statement
_ = Ordering
GT
compareStatement Statement
_ Statement
_ = Ordering
EQ

compilePostgresType :: PostgresType -> Text
compilePostgresType :: PostgresType -> Text
compilePostgresType PostgresType
PUUID = Text
"UUID"
compilePostgresType PostgresType
PText = Text
"TEXT"
compilePostgresType PostgresType
PInt = Text
"INT"
compilePostgresType PostgresType
PSmallInt = Text
"SMALLINT"
compilePostgresType PostgresType
PBigInt = Text
"BIGINT"
compilePostgresType PostgresType
PBoolean = Text
"BOOLEAN"
compilePostgresType PostgresType
PTimestamp = Text
"TIMESTAMP WITHOUT TIME ZONE"
compilePostgresType PostgresType
PTimestampWithTimezone = Text
"TIMESTAMP WITH TIME ZONE"
compilePostgresType PostgresType
PReal = Text
"REAL"
compilePostgresType PostgresType
PDouble = Text
"DOUBLE PRECISION"
compilePostgresType PostgresType
PPoint = Text
"POINT"
compilePostgresType PostgresType
PDate = Text
"DATE"
compilePostgresType PostgresType
PBinary = Text
"BYTEA"
compilePostgresType PostgresType
PTime = Text
"TIME"
compilePostgresType (PNumeric (Just Int
precision) (Just Int
scale)) = Text
"NUMERIC(" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Int -> Text
forall a. Show a => a -> Text
show Int
precision Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"," Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Int -> Text
forall a. Show a => a -> Text
show Int
scale Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
")"
compilePostgresType (PNumeric (Just Int
precision) Maybe Int
Nothing) = Text
"NUMERIC(" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Int -> Text
forall a. Show a => a -> Text
show Int
precision Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
")"
compilePostgresType (PNumeric Maybe Int
Nothing Maybe Int
_) = Text
"NUMERIC"
compilePostgresType (PVaryingN Int
limit) = Text
"CHARACTER VARYING(" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Int -> Text
forall a. Show a => a -> Text
show Int
limit Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
")"
compilePostgresType (PCharacterN Int
length) = Text
"CHARACTER(" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Int -> Text
forall a. Show a => a -> Text
show Int
length Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
")"
compilePostgresType PostgresType
PSerial = Text
"SERIAL"
compilePostgresType PostgresType
PBigserial = Text
"BIGSERIAL"
compilePostgresType PostgresType
PJSONB = Text
"JSONB"
compilePostgresType PostgresType
PInet = Text
"INET"
compilePostgresType (PArray PostgresType
type_) = PostgresType -> Text
compilePostgresType PostgresType
type_ Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"[]"
compilePostgresType (PCustomType Text
theType) = Text
theType

compileIdentifier :: Text -> Text
compileIdentifier :: Text -> Text
compileIdentifier Text
identifier = if Bool
identifierNeedsQuoting then Text -> Text
forall a. Show a => a -> Text
tshow Text
identifier else Text
identifier
    where
        identifierNeedsQuoting :: Bool
identifierNeedsQuoting = Bool
isKeyword Bool -> Bool -> Bool
|| Bool
containsSpace
        isKeyword :: Bool
isKeyword = Text -> Text
IHP.Prelude.toUpper Text
identifier Text -> [Text] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Text]
keywords
        containsSpace :: Bool
containsSpace = (Char -> Bool) -> Text -> Bool
Text.any (Char
' ' Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
==) Text
identifier

        keywords :: [Text]
keywords = [ Text
"ABORT"
            , Text
"ABSOLUTE"
            , Text
"ACCESS"
            , Text
"ACTION"
            , Text
"ADD"
            , Text
"ADMIN"
            , Text
"AFTER"
            , Text
"AGGREGATE"
            , Text
"ALSO"
            , Text
"ALTER"
            , Text
"ASSERTION"
            , Text
"ASSIGNMENT"
            , Text
"AT"
            , Text
"BACKWARD"
            , Text
"BEFORE"
            , Text
"BEGIN"
            , Text
"BY"
            , Text
"CACHE"
            , Text
"CALLED"
            , Text
"CASCADE"
            , Text
"CHAIN"
            , Text
"CHARACTERISTICS"
            , Text
"CHECKPOINT"
            , Text
"CLASS"
            , Text
"CLOSE"
            , Text
"CLUSTER"
            , Text
"COMMENT"
            , Text
"COMMIT"
            , Text
"COMMITTED"
            , Text
"CONNECTION"
            , Text
"CONSTRAINTS"
            , Text
"CONVERSION"
            , Text
"COPY"
            , Text
"CREATEDB"
            , Text
"CREATEROLE"
            , Text
"CREATEUSER"
            , Text
"CSV"
            , Text
"CURSOR"
            , Text
"CYCLE"
            , Text
"DATABASE"
            , Text
"DAY"
            , Text
"DEALLOCATE"
            , Text
"DECLARE"
            , Text
"DEFAULTS"
            , Text
"DEFERRED"
            , Text
"DEFINER"
            , Text
"DELETE"
            , Text
"DELIMITER"
            , Text
"DELIMITERS"
            , Text
"DISABLE"
            , Text
"DOMAIN"
            , Text
"DOUBLE"
            , Text
"DROP"
            , Text
"EACH"
            , Text
"ENABLE"
            , Text
"ENCODING"
            , Text
"ENCRYPTED"
            , Text
"ESCAPE"
            , Text
"EXCLUDING"
            , Text
"EXCLUSIVE"
            , Text
"EXECUTE"
            , Text
"EXPLAIN"
            , Text
"EXTERNAL"
            , Text
"FETCH"
            , Text
"FIRST"
            , Text
"FORCE"
            , Text
"FORWARD"
            , Text
"FUNCTION"
            , Text
"GLOBAL"
            , Text
"GRANTED"
            , Text
"HANDLER"
            , Text
"HEADER"
            , Text
"HOLD"
            , Text
"HOUR"
            , Text
"IMMEDIATE"
            , Text
"IMMUTABLE"
            , Text
"IMPLICIT"
            , Text
"INCLUDING"
            , Text
"INCREMENT"
            , Text
"INDEX"
            , Text
"INHERIT"
            , Text
"INHERITS"
            , Text
"INPUT"
            , Text
"INSENSITIVE"
            , Text
"INSERT"
            , Text
"INSTEAD"
            , Text
"INVOKER"
            , Text
"ISOLATION"
            , Text
"KEY"
            , Text
"LANCOMPILER"
            , Text
"LANGUAGE"
            , Text
"LARGE"
            , Text
"LAST"
            , Text
"LEVEL"
            , Text
"LISTEN"
            , Text
"LOAD"
            , Text
"LOCAL"
            , Text
"LOCATION"
            , Text
"LOCK"
            , Text
"LOGIN"
            , Text
"MATCH"
            , Text
"MAXVALUE"
            , Text
"MINUTE"
            , Text
"MINVALUE"
            , Text
"MODE"
            , Text
"MONTH"
            , Text
"MOVE"
            , Text
"NAMES"
            , Text
"NEXT"
            , Text
"NO"
            , Text
"NOCREATEDB"
            , Text
"NOCREATEROLE"
            , Text
"NOCREATEUSER"
            , Text
"NOINHERIT"
            , Text
"NOLOGIN"
            , Text
"NOSUPERUSER"
            , Text
"NOTHING"
            , Text
"NOTIFY"
            , Text
"NOWAIT"
            , Text
"OBJECT"
            , Text
"OF"
            , Text
"OIDS"
            , Text
"OPERATOR"
            , Text
"OPTION"
            , Text
"OWNER"
            , Text
"PARTIAL"
            , Text
"PASSWORD"
            , Text
"PREPARE"
            , Text
"PREPARED"
            , Text
"PRESERVE"
            , Text
"PRIOR"
            , Text
"PRIVILEGES"
            , Text
"PROCEDURAL"
            , Text
"PROCEDURE"
            , Text
"QUOTE"
            , Text
"READ"
            , Text
"RECHECK"
            , Text
"REINDEX"
            , Text
"RELATIVE"
            , Text
"RELEASE"
            , Text
"RENAME"
            , Text
"REPEATABLE"
            , Text
"REPLACE"
            , Text
"RESET"
            , Text
"RESTART"
            , Text
"RESTRICT"
            , Text
"RETURNS"
            , Text
"REVOKE"
            , Text
"ROLE"
            , Text
"ROLLBACK"
            , Text
"ROWS"
            , Text
"RULE"
            , Text
"SAVEPOINT"
            , Text
"SCHEMA"
            , Text
"SCROLL"
            , Text
"SECOND"
            , Text
"SECURITY"
            , Text
"SEQUENCE"
            , Text
"SERIALIZABLE"
            , Text
"SESSION"
            , Text
"SET"
            , Text
"SHARE"
            , Text
"tshow"
            , Text
"SIMPLE"
            , Text
"STABLE"
            , Text
"START"
            , Text
"STATEMENT"
            , Text
"STATISTICS"
            , Text
"STDIN"
            , Text
"STDOUT"
            , Text
"STORAGE"
            , Text
"STRICT"
            , Text
"SUPERUSER"
            , Text
"SYSID"
            , Text
"SYSTEM"
            , Text
"TABLESPACE"
            , Text
"TEMP"
            , Text
"TEMPLATE"
            , Text
"TEMPORARY"
            , Text
"TOAST"
            , Text
"TRANSACTION"
            , Text
"TRIGGER"
            , Text
"TRUNCATE"
            , Text
"TRUSTED"
            , Text
"TYPE"
            , Text
"UNCOMMITTED"
            , Text
"UNENCRYPTED"
            , Text
"UNKNOWN"
            , Text
"UNLISTEN"
            , Text
"UNTIL"
            , Text
"UPDATE"
            , Text
"VACUUM"
            , Text
"VALID"
            , Text
"VALIDATOR"
            , Text
"VALUES"
            , Text
"VARYING"
            , Text
"VIEW"
            , Text
"VOLATILE"
            , Text
"WITH"
            , Text
"WITHOUT"
            , Text
"WORK"
            , Text
"WRITE"
            , Text
"YEAR"
            , Text
"ZONE"
            , Text
"BIGINT"
            , Text
"BIT"
            , Text
"BOOLEAN"
            , Text
"CHAR"
            , Text
"CHARACTER"
            , Text
"COALESCE"
            , Text
"CONVERT"
            , Text
"DEC"
            , Text
"DECIMAL"
            , Text
"EXISTS"
            , Text
"EXTRACT"
            , Text
"FLOAT"
            , Text
"GREATEST"
            , Text
"INOUT"
            , Text
"INT"
            , Text
"INTEGER"
            , Text
"INTERVAL"
            , Text
"LEAST"
            , Text
"NATIONAL"
            , Text
"NCHAR"
            , Text
"NONE"
            , Text
"NULLIF"
            , Text
"NUMERIC"
            , Text
"OUT"
            , Text
"OVERLAY"
            , Text
"POSITION"
            , Text
"PRECISION"
            , Text
"REAL"
            , Text
"ROW"
            , Text
"SETOF"
            , Text
"SMALLINT"
            , Text
"SUBSTRING"
            , Text
"TIME"
            , Text
"TIMESTAMP"
            , Text
"TREAT"
            , Text
"TRIM"
            , Text
"VARCHAR"
            ]

indent :: a -> a
indent a
text = a
"    " a -> a -> a
forall a. Semigroup a => a -> a -> a
<> a
text