diff --git a/src/PostgREST/Query/SqlFragment.hs b/src/PostgREST/Query/SqlFragment.hs index 21e31236a3..a56abdce35 100644 --- a/src/PostgREST/Query/SqlFragment.hs +++ b/src/PostgREST/Query/SqlFragment.hs @@ -336,7 +336,7 @@ pgFmtArrayLiteralForField values _ = unknownLiteral (pgBuildArrayLiteral values) pgFmtFilter :: QualifiedIdentifier -> CoercibleFilter -> SQL.Snippet -pgFmtFilter _ (CoercibleFilterNullEmbed hasNot fld) = pgFmtIdent fld <> " IS " <> (if hasNot then "NOT" else mempty) <> " NULL" +pgFmtFilter _ (CoercibleFilterNullEmbed hasNot fld) = pgFmtIdent fld <> " IS " <> (if not hasNot then "NOT " else mempty) <> "DISTINCT FROM NULL" pgFmtFilter _ (CoercibleFilter _ (NoOpExpr _)) = mempty -- TODO unreachable because NoOpExpr is filtered on QueryParams pgFmtFilter table (CoercibleFilter fld (OpExpr hasNot oper)) = notOp <> " " <> pgFmtField table fld <> case oper of Op op val -> " " <> simpleOperator op <> " " <> pgFmtUnknownLiteralForField (unknownLiteral val) fld diff --git a/test/spec/Feature/Query/RelatedQueriesSpec.hs b/test/spec/Feature/Query/RelatedQueriesSpec.hs index 0db178cf9d..0b83cbb680 100644 --- a/test/spec/Feature/Query/RelatedQueriesSpec.hs +++ b/test/spec/Feature/Query/RelatedQueriesSpec.hs @@ -256,3 +256,22 @@ spec = describe "related queries" $ do { matchStatus = 200 , matchHeaders = [matchContentTypeJson] } + + -- "?table=not.is.null" does a "table IS DISTINCT FROM NULL" instead of a "table IS NOT NULL" + -- https://github.com/PostgREST/postgrest/issues/2800#issuecomment-1720315818 + it "embeds verifying that the entire target table row is not null" $ do + get "/table_b?select=name,table_a(name)&table_a=not.is.null" `shouldRespondWith` + [json|[ + {"name":"Test 1","table_a":{"name":"Not null 1"}}, + {"name":"Test 2","table_a":{"name":null}} + ]|] + { matchStatus = 200 + , matchHeaders = [matchContentTypeJson] + } + get "/table_b?select=name,table_a()&table_a=is.null" `shouldRespondWith` + [json|[ + {"name":"Test 3"} + ]|] + { matchStatus = 200 + , matchHeaders = [matchContentTypeJson] + } diff --git a/test/spec/fixtures/data.sql b/test/spec/fixtures/data.sql index a2ba2e24fc..b3f478fc7f 100644 --- a/test/spec/fixtures/data.sql +++ b/test/spec/fixtures/data.sql @@ -853,3 +853,8 @@ INSERT INTO datarep_next_two_todos VALUES (2, 1, 3, 'do these first'); TRUNCATE TABLE bitchar_with_length CASCADE; INSERT INTO bitchar_with_length(bit, char) VALUES ('00000', 'aaaaa'); INSERT INTO bitchar_with_length(bit, char) VALUES ('11111', 'bbbbb'); + +TRUNCATE TABLE table_a CASCADE; +INSERT INTO table_a(id, name) VALUES (1, 'Not null 1'), (2, null), (3, 'Not null 2'); +TRUNCATE TABLE table_b CASCADE; +INSERT INTO table_b(table_a_id, name) VALUES (1, 'Test 1'), (2, 'Test 2'), (null, 'Test 3'); diff --git a/test/spec/fixtures/schema.sql b/test/spec/fixtures/schema.sql index f01b5efb20..9a0fa2fd74 100644 --- a/test/spec/fixtures/schema.sql +++ b/test/spec/fixtures/schema.sql @@ -3437,3 +3437,13 @@ begin message = '{"code":"123","message":"ABC","details":"DEF"}'; end $$; + +create table table_a ( + id int primary key, + name text +); + +create table table_b ( + table_a_id int references table_a(id), + name text +);