Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ihp-ide/IHP/IDE/SchemaDesigner/Compiler.hs
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ compilePostgresType PTSVector = "TSVECTOR"
compilePostgresType (PArray type_) = compilePostgresType type_ <> "[]"
compilePostgresType PTrigger = "TRIGGER"
compilePostgresType PEventTrigger = "EVENT_TRIGGER"
compilePostgresType (PCustomType theType) = theType
compilePostgresType (PCustomType theType) = compileIdentifier theType

compileIdentifier :: Text -> Text
compileIdentifier identifier = if identifierNeedsQuoting then tshow identifier else identifier
Expand Down
2 changes: 1 addition & 1 deletion ihp-ide/IHP/IDE/SchemaDesigner/Parser.hs
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,7 @@ sqlType = choice $ map optionalArray
optional do
lexeme "public"
char '.'
theType <- try (takeWhile1P (Just "Custom type") (\c -> isAlphaNum c || c == '_'))
theType <- try identifier
pure (PCustomType theType)


Expand Down
40 changes: 40 additions & 0 deletions ihp-ide/Test/IDE/CodeGeneration/MigrationGenerator.hs
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,46 @@ tests = do

diffSchemas targetSchema actualSchema `shouldBe` []

it "should handle uppercase quoted enum types with tables using them" do
let targetSchema = sql [i|
CREATE TYPE "SYMBOL_TYPE" AS ENUM ('stock', 'etf', 'future', 'option', 'fund');
CREATE TABLE symbol (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
code TEXT NOT NULL,
name TEXT NOT NULL,
symbol_type "SYMBOL_TYPE" NOT NULL,
list_date TIMESTAMP WITHOUT TIME ZONE NOT NULL,
delist_date TIMESTAMP WITHOUT TIME ZONE,
UNIQUE(code, symbol_type)
);
|]

let actualSchema = targetSchema

diffSchemas targetSchema actualSchema `shouldBe` []

it "should handle creating a new table with uppercase quoted enum type" do
let targetSchema = sql [i|
CREATE TYPE "SYMBOL_TYPE" AS ENUM ('stock', 'etf', 'future');
CREATE TABLE symbol (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
symbol_type "SYMBOL_TYPE" NOT NULL
);
|]

let actualSchema = sql [i|
CREATE TYPE "SYMBOL_TYPE" AS ENUM ('stock', 'etf', 'future');
|]

let migration = sql [i|
CREATE TABLE symbol (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
symbol_type "SYMBOL_TYPE" NOT NULL
);
|]

diffSchemas targetSchema actualSchema `shouldBe` migration

it "should handle a deleted table" do
let targetSchema = sql ""
let actualSchema = sql [i|
Expand Down
92 changes: 91 additions & 1 deletion ihp-ide/Test/IDE/SchemaDesigner/CompilerSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,42 @@ tests = do
}
compileSql [statement] `shouldBe` sql

it "should compile a CREATE TYPE .. AS ENUM with uppercase name" do
let sql = cs [plain|CREATE TYPE "SYMBOL_TYPE" AS ENUM ('stock', 'etf', 'future');\n|]
let statement = CreateEnumType
{ name = "SYMBOL_TYPE"
, values = ["stock", "etf", "future"]
}
compileSql [statement] `shouldBe` sql

it "should compile a CREATE TABLE with uppercase custom type column" do
let sql = cs [plain|CREATE TABLE symbol (\n id UUID,\n symbol_type "SYMBOL_TYPE"\n);\n|]
let statement = StatementCreateTable CreateTable
{ name = "symbol"
, columns =
[ Column
{ name = "id"
, columnType = PUUID
, defaultValue = Nothing
, notNull = False
, isUnique = False
, generator = Nothing
}
, Column
{ name = "symbol_type"
, columnType = PCustomType "SYMBOL_TYPE"
, defaultValue = Nothing
, notNull = False
, isUnique = False
, generator = Nothing
}
]
, primaryKeyConstraint = PrimaryKeyConstraint []
, constraints = []
, unlogged = False
}
compileSql [statement] `shouldBe` sql

it "should compile a CREATE TABLE with (deprecated) NUMERIC, NUMERIC(x), NUMERIC (x,y), VARYING(n) columns" do
let sql = cs [plain|CREATE TABLE deprecated_variables (\n a NUMERIC,\n b NUMERIC(1),\n c NUMERIC(1,2),\n d CHARACTER VARYING(10)\n);\n|]
let statement = StatementCreateTable CreateTable
Expand Down Expand Up @@ -1091,4 +1127,58 @@ tests = do
, check = Just (VarExpression "false")
}
]
compileSql statements `shouldBe` sql
compileSql statements `shouldBe` sql

it "should compile schema with uppercase enum type and table using it" do
let sql = cs [plain|CREATE TYPE "SYMBOL_TYPE" AS ENUM ('stock', 'etf', 'future', 'option', 'fund');
CREATE TABLE symbol (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
code TEXT NOT NULL,
name TEXT NOT NULL,
symbol_type "SYMBOL_TYPE" NOT NULL
);
|]
let statements =
[ CreateEnumType { name = "SYMBOL_TYPE", values = ["stock", "etf", "future", "option", "fund"] }
, StatementCreateTable CreateTable
{ name = "symbol"
, columns =
[ Column
{ name = "id"
, columnType = PUUID
, defaultValue = Just (CallExpression "uuid_generate_v4" [])
, notNull = True
, isUnique = False
, generator = Nothing
}
, Column
{ name = "code"
, columnType = PText
, defaultValue = Nothing
, notNull = True
, isUnique = False
, generator = Nothing
}
, Column
{ name = "name"
, columnType = PText
, defaultValue = Nothing
, notNull = True
, isUnique = False
, generator = Nothing
}
, Column
{ name = "symbol_type"
, columnType = PCustomType "SYMBOL_TYPE"
, defaultValue = Nothing
, notNull = True
, isUnique = False
, generator = Nothing
}
]
, primaryKeyConstraint = PrimaryKeyConstraint ["id"]
, constraints = []
, unlogged = False
}
]
compileSql statements `shouldBe` sql
103 changes: 103 additions & 0 deletions ihp-ide/Test/IDE/SchemaDesigner/ParserSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,54 @@ tests = do
it "should parse a CREATE TABLE with quoted identifiers" do
parseSql "CREATE TABLE \"quoted name\" ();" `shouldBe` StatementCreateTable CreateTable { name = "quoted name", columns = [], primaryKeyConstraint = PrimaryKeyConstraint [], constraints = [], unlogged = False }

it "should parse a CREATE TABLE with uppercase custom type column" do
let sql = cs [plain|CREATE TABLE symbol (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
code TEXT NOT NULL,
name TEXT NOT NULL,
symbol_type "SYMBOL_TYPE" NOT NULL
);|]
parseSql sql `shouldBe` StatementCreateTable CreateTable
{ name = "symbol"
, columns = [
Column
{ name = "id"
, columnType = PUUID
, defaultValue = Just (CallExpression "uuid_generate_v4" [])
, notNull = True
, isUnique = False
, generator = Nothing
}
, Column
{ name = "code"
, columnType = PText
, defaultValue = Nothing
, notNull = True
, isUnique = False
, generator = Nothing
}
, Column
{ name = "name"
, columnType = PText
, defaultValue = Nothing
, notNull = True
, isUnique = False
, generator = Nothing
}
, Column
{ name = "symbol_type"
, columnType = PCustomType "SYMBOL_TYPE"
, defaultValue = Nothing
, notNull = True
, isUnique = False
, generator = Nothing
}
]
, primaryKeyConstraint = PrimaryKeyConstraint ["id"]
, constraints = []
, unlogged = False
}

it "should parse a CREATE TABLE with public schema prefix" do
parseSql "CREATE TABLE public.users ();" `shouldBe` StatementCreateTable CreateTable { name = "users", columns = [], primaryKeyConstraint = PrimaryKeyConstraint [], constraints = [], unlogged = False }

Expand Down Expand Up @@ -482,6 +530,9 @@ tests = do
it "should parse CREATE TYPE .. AS ENUM" do
parseSql "CREATE TYPE colors AS ENUM ('yellow', 'red', 'green');" `shouldBe` CreateEnumType { name = "colors", values = ["yellow", "red", "green"] }

it "should parse CREATE TYPE .. AS ENUM with quoted uppercase name" do
parseSql "CREATE TYPE \"SYMBOL_TYPE\" AS ENUM ('stock', 'etf', 'future', 'option', 'fund');" `shouldBe` CreateEnumType { name = "SYMBOL_TYPE", values = ["stock", "etf", "future", "option", "fund"] }

it "should parse CREATE TYPE .. AS ENUM with extra whitespace" do
parseSql "CREATE TYPE Numbers AS ENUM (\n\t'One',\t'Two',\t'Three',\t'Four',\t'Five',\t'Six',\t'Seven',\t'Eight',\t'Nine',\t'Ten'\n);" `shouldBe` CreateEnumType { name = "Numbers", values = ["One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten"] }

Expand Down Expand Up @@ -1176,6 +1227,58 @@ COMMENT ON EXTENSION "uuid-ossp" IS 'generate universally unique identifiers (UU
|]
parseSqlStatements sql `shouldBe` [Comment { content = "" }, Comment { content = "" }]

it "should parse schema with uppercase enum type and table using it" do
let sql = cs [plain|CREATE TYPE "SYMBOL_TYPE" AS ENUM ('stock', 'etf', 'future', 'option', 'fund');
CREATE TABLE symbol (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
code TEXT NOT NULL,
name TEXT NOT NULL,
symbol_type "SYMBOL_TYPE" NOT NULL
);|]
parseSqlStatements sql `shouldBe`
[ CreateEnumType { name = "SYMBOL_TYPE", values = ["stock", "etf", "future", "option", "fund"] }
, StatementCreateTable CreateTable
{ name = "symbol"
, columns =
[ Column
{ name = "id"
, columnType = PUUID
, defaultValue = Just (CallExpression "uuid_generate_v4" [])
, notNull = True
, isUnique = False
, generator = Nothing
}
, Column
{ name = "code"
, columnType = PText
, defaultValue = Nothing
, notNull = True
, isUnique = False
, generator = Nothing
}
, Column
{ name = "name"
, columnType = PText
, defaultValue = Nothing
, notNull = True
, isUnique = False
, generator = Nothing
}
, Column
{ name = "symbol_type"
, columnType = PCustomType "SYMBOL_TYPE"
, defaultValue = Nothing
, notNull = True
, isUnique = False
, generator = Nothing
}
]
, primaryKeyConstraint = PrimaryKeyConstraint ["id"]
, constraints = []
, unlogged = False
}
]

col :: Column
col = Column
{ name = ""
Expand Down
50 changes: 50 additions & 0 deletions ihp-ide/Test/SchemaCompilerSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,56 @@ tests = do
instance DeepSeq.NFData PropertyType where rnf a = seq a ()
instance IHP.Controller.Param.ParamReader PropertyType where readParameter = IHP.Controller.Param.enumParamReader; readParameterJSON = IHP.Controller.Param.enumParamReaderJSON
|]
it "should compile uppercase quoted enum type correctly" do
let statement = CreateEnumType { name = "SYMBOL_TYPE", values = ["stock", "etf", "future", "option", "fund"] }
let output = compileStatementPreview [statement] statement |> Text.strip

output `shouldBe` [trimming|
data SymbolType = Stock | Etf | Future | Option | Fund deriving (Eq, Show, Read, Enum, Bounded, Ord)
instance FromField SymbolType where
fromField field (Just value) | value == (Data.Text.Encoding.encodeUtf8 "stock") = pure Stock
fromField field (Just value) | value == (Data.Text.Encoding.encodeUtf8 "etf") = pure Etf
fromField field (Just value) | value == (Data.Text.Encoding.encodeUtf8 "future") = pure Future
fromField field (Just value) | value == (Data.Text.Encoding.encodeUtf8 "option") = pure Option
fromField field (Just value) | value == (Data.Text.Encoding.encodeUtf8 "fund") = pure Fund
fromField field (Just value) = returnError ConversionFailed field ("Unexpected value for enum value. Got: " <> Data.String.Conversions.cs value)
fromField field Nothing = returnError UnexpectedNull field "Unexpected null for enum value"
instance Default SymbolType where def = Stock
instance ToField SymbolType where
toField Stock = toField ("stock" :: Text)
toField Etf = toField ("etf" :: Text)
toField Future = toField ("future" :: Text)
toField Option = toField ("option" :: Text)
toField Fund = toField ("fund" :: Text)
instance InputValue SymbolType where
inputValue Stock = "stock" :: Text
inputValue Etf = "etf" :: Text
inputValue Future = "future" :: Text
inputValue Option = "option" :: Text
inputValue Fund = "fund" :: Text
instance DeepSeq.NFData SymbolType where rnf a = seq a ()
instance IHP.Controller.Param.ParamReader SymbolType where readParameter = IHP.Controller.Param.enumParamReader; readParameterJSON = IHP.Controller.Param.enumParamReaderJSON
|]
it "should compile a table with an uppercase quoted custom type column" do
let enumType = CreateEnumType { name = "SYMBOL_TYPE", values = ["stock", "etf", "future", "option", "fund"] }
let table = StatementCreateTable $ CreateTable {
name = "symbol",
columns = [
Column "id" PUUID (Just (CallExpression "uuid_generate_v4" [])) True False Nothing,
Column "code" PText Nothing True False Nothing,
Column "name" PText Nothing True False Nothing,
Column "symbol_type" (PCustomType "SYMBOL_TYPE") Nothing True False Nothing
],
primaryKeyConstraint = PrimaryKeyConstraint ["id"],
constraints = [],
unlogged = False
}
let output = compileStatementPreview [enumType, table] table |> Text.strip

-- The Haskell type should be SymbolType (converted from SYMBOL_TYPE)
-- Extract just the data type line to verify the custom type is correctly referenced
let dataTypeLine = output |> Text.lines |> find (Text.isPrefixOf "data Symbol'") |> fromMaybe ""
dataTypeLine `shouldBe` "data Symbol' = Symbol {id :: (Id' \"symbol\"), code :: Text, name :: Text, symbolType :: SymbolType, meta :: MetaBag} deriving (Eq, Show)"
describe "compileCreate" do
let statement = StatementCreateTable $ CreateTable {
name = "users",
Expand Down
Loading