server/postgres + server/mssql: Insert empty objects with default values

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/4487
Co-authored-by: Abby Sassel <3883855+sassela@users.noreply.github.com>
GitOrigin-RevId: 3413f0b5dbe6ec42fff360d83b5202e4aa4aa86e
This commit is contained in:
Gil Mizrahi 2022-05-11 19:00:10 +03:00 committed by hasura-bot
parent b4f89569c8
commit cd38a9a1fc
5 changed files with 362 additions and 10 deletions

View File

@ -15,6 +15,7 @@
- server: fixes remote relationships on actions (fix #8399)
- server: fixes url/query date variable bug in REST endpoints
- server: makes url/query variables in REST endpoints assume string if other types not applicable
- server: fix inserting empty objects with default values to postgres, citus, and sql server (fix #8475)
- console: add remote database relationships for views
- console: bug fixes for RS-to-RS relationships
- console: allow users to remove prefix / suffix / root field namespace from a remote schema

View File

@ -1124,6 +1124,7 @@ test-suite tests-hspec
Test.EventTriggersRunSQLSpec
Test.HelloWorldSpec
Test.InsertCheckPermissionSpec
Test.InsertDefaultsSpec
Test.InsertEnumColumnSpec
Test.LimitOffsetSpec
Test.NestedRelationshipsSpec

View File

@ -235,12 +235,17 @@ fromInsert :: Insert -> Printer
fromInsert Insert {..} =
SepByPrinter
NewlinePrinter
[ "INSERT INTO " <+> fromTableName insertTable,
"(" <+> SepByPrinter ", " (map (fromNameText . columnNameText) insertColumns) <+> ")",
fromInsertOutput insertOutput,
"INTO " <+> fromTempTable insertTempTable,
fromValuesList insertValues
]
$ ["INSERT INTO " <+> fromTableName insertTable]
<> ( if null insertColumns
then []
else ["(" <+> SepByPrinter ", " (map (fromNameText . columnNameText) insertColumns) <+> ")"]
)
<> [ fromInsertOutput insertOutput,
"INTO " <+> fromTempTable insertTempTable,
if null insertColumns
then "DEFAULT VALUES"
else fromValuesList insertValues
]
fromSetValue :: SetValue -> Printer
fromSetValue = \case

View File

@ -1079,10 +1079,10 @@ instance ToSQL SQLInsert where
toSQL si =
"INSERT INTO"
<~> toSQL (siTable si)
<~> "("
<~> (", " <+> siCols si)
<~> ")"
<~> toSQL (siValues si)
<~> ( if null (siCols si)
then "DEFAULT VALUES"
else "(" <~> (", " <+> siCols si) <~> ")" <~> toSQL (siValues si)
)
<~> maybe "" toSQL (siConflict si)
<~> toSQL (siRet si)

View File

@ -0,0 +1,345 @@
{-# LANGUAGE QuasiQuotes #-}
-- | Test insert with default values
module Test.InsertDefaultsSpec (spec) where
import Harness.Backend.Citus qualified as Citus
import Harness.Backend.Postgres qualified as Postgres
import Harness.Backend.Sqlserver qualified as Sqlserver
import Harness.GraphqlEngine qualified as GraphqlEngine
import Harness.Quoter.Graphql (graphql)
import Harness.Quoter.Yaml (shouldReturnYaml, yaml)
import Harness.Test.Context qualified as Context
import Harness.Test.Schema qualified as Schema
import Harness.TestEnvironment (TestEnvironment)
import Test.Hspec (SpecWith, it)
import Prelude
--------------------------------------------------------------------------------
-- ** Preamble
spec :: SpecWith TestEnvironment
spec = do
Context.run
[ postgresContext,
citusContext,
mssqlContext
]
commonTests
Context.run [postgresContext, citusContext] postgresTests
Context.run [mssqlContext] mssqlTests
where
postgresContext =
Context.Context
{ name = Context.Backend Context.Postgres,
mkLocalTestEnvironment = Context.noLocalTestEnvironment,
setup = Postgres.setup schema,
teardown = Postgres.teardown schema,
customOptions = Nothing
}
citusContext =
Context.Context
{ name = Context.Backend Context.Citus,
mkLocalTestEnvironment = Context.noLocalTestEnvironment,
setup = Citus.setup schema,
teardown = Citus.teardown schema,
customOptions = Nothing
}
mssqlContext =
Context.Context
{ name = Context.Backend Context.SQLServer,
mkLocalTestEnvironment = Context.noLocalTestEnvironment,
setup = Sqlserver.setup schema,
teardown = Sqlserver.teardown schema,
customOptions = Nothing
}
--------------------------------------------------------------------------------
-- ** Schema
schema :: [Schema.Table]
schema =
[ alldefaults,
somedefaults,
withrelationship
]
alldefaults :: Schema.Table
alldefaults =
Schema.Table
{ tableName = "alldefaults",
tableColumns =
[ Schema.column "id" defaultSerialType,
Schema.column "dt" defaultDateTimeType
],
tablePrimaryKey = ["id"],
tableReferences = [],
tableData = []
}
somedefaults :: Schema.Table
somedefaults =
Schema.Table
{ tableName = "somedefaults",
tableColumns =
[ Schema.column "id" defaultSerialType,
Schema.column "dt" defaultDateTimeType,
Schema.column "name" Schema.TStr
],
tablePrimaryKey = ["name"],
tableReferences = [],
tableData = []
}
withrelationship :: Schema.Table
withrelationship =
Schema.Table
{ tableName = "withrelationship",
tableColumns =
[ Schema.column "id" defaultSerialType,
Schema.column "nickname" Schema.TStr,
Schema.column "time_id" Schema.TInt
],
tablePrimaryKey = ["nickname"],
tableReferences = [Schema.Reference "time_id" "alldefaults" "id"],
tableData = []
}
defaultSerialType :: Schema.ScalarType
defaultSerialType =
Schema.TCustomType $
Schema.defaultBackendScalarType
{ Schema.bstMysql = Nothing,
Schema.bstMssql = Just "INT IDENTITY(1,1)",
Schema.bstCitus = Just "SERIAL",
Schema.bstPostgres = Just "SERIAL",
Schema.bstBigQuery = Nothing
}
defaultDateTimeType :: Schema.ScalarType
defaultDateTimeType =
Schema.TCustomType $
Schema.defaultBackendScalarType
{ Schema.bstMysql = Nothing,
Schema.bstMssql = Just "DATETIME DEFAULT GETDATE()",
Schema.bstCitus = Just "TIMESTAMP DEFAULT NOW()",
Schema.bstPostgres = Just "TIMESTAMP DEFAULT NOW()",
Schema.bstBigQuery = Nothing
}
--------------------------------------------------------------------------------
-- * Tests
commonTests :: Context.Options -> SpecWith TestEnvironment
commonTests opts = do
it "Insert empty object with default values" $ \testEnvironment ->
shouldReturnYaml
opts
( GraphqlEngine.postGraphql
testEnvironment
[graphql|
mutation {
insert_hasura_alldefaults(
objects:[{}]
){
affected_rows
}
}
|]
)
[yaml|
data:
insert_hasura_alldefaults:
affected_rows: 1
|]
it "Insert simple object with default values" $ \testEnvironment ->
shouldReturnYaml
opts
( GraphqlEngine.postGraphql
testEnvironment
[graphql|
mutation {
insert_hasura_somedefaults(
objects:[{ name: "a" }]
){
affected_rows
returning {
id
name
}
}
}
|]
)
[yaml|
data:
insert_hasura_somedefaults:
affected_rows: 1
returning:
- id: 1
name: "a"
|]
postgresTests :: Context.Options -> SpecWith TestEnvironment
postgresTests opts = do
it "Upsert simple object with default values - check empty constraints" $ \testEnvironment ->
shouldReturnYaml
opts
( GraphqlEngine.postGraphql
testEnvironment
[graphql|
mutation {
insert_hasura_somedefaults(
objects: [{ name: "a" }]
on_conflict: {
constraint: somedefaults_pkey,
update_columns: []
}
){
affected_rows
returning {
id
}
}
}
|]
)
[yaml|
data:
insert_hasura_somedefaults:
affected_rows: 1
returning:
- id: 1
|]
it "Upsert simple object with default values - check conflict doesn't update" $ \testEnvironment ->
shouldReturnYaml
opts
( GraphqlEngine.postGraphql
testEnvironment
[graphql|
mutation {
insert_hasura_somedefaults(
objects: [{ name: "a" }]
on_conflict: {
constraint: somedefaults_pkey,
update_columns: []
}
){
affected_rows
returning {
id
}
}
}
|]
)
[yaml|
data:
insert_hasura_somedefaults:
affected_rows: 0
returning: []
|]
it "Nested insert with empty object" $ \testEnvironment ->
shouldReturnYaml
opts
( GraphqlEngine.postGraphql
testEnvironment
[graphql|
mutation {
insert_hasura_withrelationship(
objects: [{ nickname: "the a", alldefaults_by_time_id: {data: {} } }]
on_conflict: {
constraint: withrelationship_pkey,
update_columns: []
}
){
affected_rows
returning {
id
nickname
alldefaults_by_time_id {
id
}
}
}
}
|]
)
[yaml|
data:
insert_hasura_withrelationship:
affected_rows: 2
returning:
- id: 1
nickname: "the a"
alldefaults_by_time_id:
id: 1
|]
mssqlTests :: Context.Options -> SpecWith TestEnvironment
mssqlTests opts = do
it "Upsert simple object with default values - check empty if_matched" $ \testEnvironment ->
shouldReturnYaml
opts
( GraphqlEngine.postGraphql
testEnvironment
[graphql|
mutation {
insert_hasura_somedefaults(
objects: [{ name: "a" }]
if_matched: {
match_columns: [],
update_columns: []
}
){
affected_rows
returning {
id
}
}
}
|]
)
[yaml|
data:
insert_hasura_somedefaults:
affected_rows: 1
returning:
- id: 1
|]
it "Upsert simple object with default values - check conflict doesn't update" $ \testEnvironment ->
shouldReturnYaml
opts
( GraphqlEngine.postGraphql
testEnvironment
[graphql|
mutation {
insert_hasura_somedefaults(
objects: [{ name: "a" }]
if_matched: {
match_columns: name,
update_columns: []
}
){
affected_rows
returning {
id
}
}
}
|]
)
[yaml|
data:
insert_hasura_somedefaults:
affected_rows: 0
returning: []
|]