Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sql: fix typing of CASE statements to match Postgres #108387

Merged
merged 1 commit into from Aug 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion pkg/sql/logictest/testdata/logic_test/order_by
Expand Up @@ -166,7 +166,7 @@ query I
query error pgcode 42601 multiple ORDER BY clauses not allowed
((SELECT a FROM t ORDER BY a)) ORDER BY a

query error expected c to be of type int, found type bool
query error expected b to be of type bool, found type int
SELECT CASE a WHEN 1 THEN b ELSE c END as val FROM t ORDER BY val

query error pgcode 42P10 ORDER BY position 0 is not in select list
Expand Down
22 changes: 13 additions & 9 deletions pkg/sql/logictest/testdata/logic_test/tuple
Expand Up @@ -1196,10 +1196,16 @@ CREATE TABLE t78159 (b BOOL)
statement ok
INSERT INTO t78159 VALUES (false)

query B
SELECT (CASE WHEN b THEN ((ROW(1) AS a)) ELSE NULL END).a from t78159
query I
SELECT (CASE WHEN b THEN NULL ELSE ((ROW(1) AS a)) END).a from t78159
----
NULL
1

# TODO(rytaft): Uncomment this case once #109105 is fixed.
# query B
# SELECT (CASE WHEN b THEN ((ROW(1) AS a)) ELSE NULL END).a from t78159
# ----
# NULL

# Regression test for #78515. Propagate tuple labels when type-checking
# expressions with multiple matching tuple types.
Expand All @@ -1208,14 +1214,12 @@ subtest 78515
statement ok
SELECT (CASE WHEN false THEN (ROW(1) AS a) ELSE (ROW(2) AS a) END).a;

# The label of the first tuple is used in the type of the CASE expression. This
# is similar to Postgres, but not exactly the same - Postgres uses the labels of
# the last tuple. This difference should be addressed in the future when we try
# to adhere more closely to Postgres's type conversion behavior (see #75101).
statement ok
# The label of the last tuple is used in the type of the CASE expression. This
# matches Postgres behavior.
statement error could not identify column \"a\" in tuple{int AS b}
SELECT (CASE WHEN false THEN (ROW(1) AS a) ELSE (ROW(2) AS b) END).a;

statement error could not identify column \"b\" in tuple{int AS a}
statement ok
SELECT (CASE WHEN false THEN (ROW(1) AS a) ELSE (ROW(2) AS b) END).b;

subtest end
Expand Down
40 changes: 40 additions & 0 deletions pkg/sql/logictest/testdata/logic_test/typing
Expand Up @@ -273,3 +273,43 @@ SELECT ARRAY[]::TIMESTAMPTZ[] >
query error pq: cannot determine type of empty array\. Consider casting to the desired type, for example ARRAY\[\]::int\[\]
SELECT ARRAY[]::TIMESTAMPTZ[] <
SOME (ARRAY[TIMESTAMPTZ '1969-12-29T21:20:13+01'], ARRAY[NULL]);

# Regression test for #102110. Ensure CASE is typed correctly.
statement ok
CREATE TABLE t102110_1 (t TEXT);
INSERT INTO t102110_1 VALUES ('tt');

statement ok
CREATE TABLE t102110_2 (c CHAR);
INSERT INTO t102110_2 VALUES ('c');

query T
SELECT t102110_1.t FROM t102110_1, t102110_2
WHERE t102110_1.t NOT BETWEEN t102110_1.t AND
(CASE WHEN NULL THEN t102110_2.c ELSE t102110_1.t END);
----

# IF is typed differently (Postgres does not support IF).
query T
SELECT t102110_1.t FROM t102110_1, t102110_2
WHERE t102110_1.t NOT BETWEEN t102110_1.t AND
IF(NULL, t102110_2.c, t102110_1.t);
----
tt

# TODO(#108360): implicit casts from STRING to CHAR should use bpchar.
statement ok
CREATE TABLE t108360_1 (t TEXT);
INSERT INTO t108360_1 VALUES ('tt');

statement ok
CREATE TABLE t108360_2 (c CHAR);
INSERT INTO t108360_2 VALUES ('c');

# This returns one row with 'tt' in Postgres. The results are different because
# Postgres casts t108360_1.t to bpchar rather than char.
query T
SELECT (CASE WHEN t108360_1.t > t108360_2.c THEN t108360_1.t ELSE t108360_2.c END)
FROM t108360_1, t108360_2
WHERE t108360_1.t = (CASE WHEN t108360_1.t > t108360_2.c THEN t108360_1.t ELSE t108360_2.c END);
----
9 changes: 7 additions & 2 deletions pkg/sql/opt/exec/execbuilder/testdata/scalar
Expand Up @@ -1285,16 +1285,21 @@ vectorized: true

# Regression test for #57959. This should not cause an error due to
# "comparison overload not found".
query T
query error pq: incompatible value type: cannot determine type of empty array. Consider casting to the desired type, for example ARRAY[]::int[]
EXPLAIN (VERBOSE) SELECT * FROM t0 WHERE (CASE WHEN (t0.c0) IN (t0.c0) THEN ARRAY[NULL] ELSE ARRAY[] END) IS NULL

# TODO(rytaft): On Postgres this case returns ERROR: cannot determine type of
# empty array.
query T
EXPLAIN (VERBOSE) SELECT * FROM t0 WHERE (CASE WHEN (t0.c0) IN (t0.c0) THEN ARRAY[] ELSE ARRAY[NULL] END) IS NULL
----
distribution: local
vectorized: true
·
• filter
│ columns: (c0)
│ estimated row count: 333 (missing stats)
│ filter: CASE WHEN (c0 IS DISTINCT FROM CAST(NULL AS DECIMAL)) OR CAST(NULL AS BOOL) THEN ARRAY[NULL] ELSE ARRAY[] END IS NULL
│ filter: CASE WHEN (c0 IS DISTINCT FROM CAST(NULL AS DECIMAL)) OR CAST(NULL AS BOOL) THEN ARRAY[] ELSE ARRAY[NULL] END IS NULL
└── • scan
columns: (c0)
Expand Down
2 changes: 1 addition & 1 deletion pkg/sql/opt/optbuilder/testdata/orderby
Expand Up @@ -364,7 +364,7 @@ error (42601): multiple ORDER BY clauses not allowed
build
SELECT CASE a WHEN 1 THEN b ELSE c END as val FROM t ORDER BY val
----
error (22023): incompatible value type: expected c to be of type int, found type bool
error (22023): incompatible value type: expected b to be of type bool, found type int

build
SELECT * FROM t ORDER BY 0
Expand Down
166 changes: 154 additions & 12 deletions pkg/sql/opt/optbuilder/testdata/scalar
Expand Up @@ -1343,10 +1343,9 @@ case [type=oid]
├── when [type=oid]
│ ├── true [type=bool]
│ └── const: 1234 [type=oid]
└── coalesce [type=oid]
├── cast: OID [type=oid]
│ └── null [type=unknown]
└── cast: OID [type=oid]
└── cast: OID [type=oid]
└── coalesce [type=unknown]
├── null [type=unknown]
└── null [type=unknown]

# Make sure NULL arguments for AND/OR/NOT are typed as boolean.
Expand Down Expand Up @@ -1470,7 +1469,7 @@ error: unsupported comparison operator: <geometry[]> && <geometry>
# Regression test for #57959. Ensure that the CASE statement is typed as
# string[].
build-scalar vars=(a varbit)
(CASE WHEN (a) IN (a) THEN ARRAY[NULL] ELSE ARRAY[] END) IS NULL
(CASE WHEN (a) IN (a) THEN ARRAY[] ELSE ARRAY[NULL] END) IS NULL
----
is [type=bool]
├── case [type=string[]]
Expand All @@ -1481,32 +1480,71 @@ is [type=bool]
│ │ │ └── tuple [type=tuple{varbit}]
│ │ │ └── variable: a:1 [type=varbit]
│ │ └── array: [type=string[]]
│ │ └── null [type=unknown]
│ └── array: [type=string[]]
│ └── null [type=unknown]
└── null [type=unknown]

build-scalar vars=(a varbit)
(CASE WHEN (a) IN (a) THEN ARRAY[NULL] ELSE ARRAY[] END) IS NULL
----
error: incompatible value type: cannot determine type of empty array. Consider casting to the desired type, for example ARRAY[]::int[]

# Regression test for #75365. Do not create invalid casts when building CASE
# expressions. We build a Select expressions here instead of a scalar so that
# logical properties are generated, which is required to reproduce the bug.
# TODO(#75103): We should be more permissive with casts of arrays of tuples.
# These tests should be successful, not user-facing errors.
build
SELECT CASE WHEN false THEN ARRAY[]::RECORD[] ELSE ARRAY[('', 0)] END
----
project
├── columns: array:1!null
├── values
│ └── ()
└── projections
└── CASE WHEN false THEN ARRAY[] ELSE ARRAY[('', 0)]::RECORD[] END [as=array:1]

build
SELECT CASE WHEN false THEN ARRAY[('', 0)] WHEN true THEN ARRAY[('', 0)] ELSE ARRAY[]::RECORD[] END
----
project
├── columns: array:1!null
├── values
│ └── ()
└── projections
└── CASE WHEN false THEN ARRAY[('', 0)]::RECORD[] WHEN true THEN ARRAY[('', 0)]::RECORD[] ELSE ARRAY[] END [as=array:1]

build
SELECT CASE WHEN false THEN ARRAY[('', 0)] ELSE ARRAY[]::RECORD[] END
----
error (42804): CASE ELSE type tuple[] cannot be matched to WHEN type tuple{string, int}[]
project
├── columns: array:1!null
├── values
│ └── ()
└── projections
└── CASE WHEN false THEN ARRAY[('', 0)]::RECORD[] ELSE ARRAY[] END [as=array:1]

build
SELECT CASE WHEN false THEN ARRAY[('', 0)] WHEN true THEN ARRAY[]::RECORD[] ELSE ARRAY[('', 0)] END
----
error (42804): CASE WHEN types tuple[] and tuple{string, int}[] cannot be matched
project
├── columns: array:1!null
├── values
│ └── ()
└── projections
└── CASE WHEN false THEN ARRAY[('', 0)]::RECORD[] WHEN true THEN ARRAY[] ELSE ARRAY[('', 0)]::RECORD[] END [as=array:1]

# Regression test for #76807. Do not create invalid casts when building COALESCE
# and IF expressions.
build
SELECT COALESCE(t.v, ARRAY[]:::RECORD[])
FROM (VALUES (ARRAY[(1, 'foo')])) AS t(v)
----
error (42804): COALESCE types tuple[] and tuple{int, string}[] cannot be matched
project
├── columns: coalesce:2
├── values
│ ├── columns: column1:1
│ └── (ARRAY[(1, 'foo')],)
└── projections
└── COALESCE(column1:1::RECORD[], ARRAY[]) [as=coalesce:2]

build
SELECT COALESCE(ARRAY[]:::RECORD[], t.v)
Expand All @@ -1524,7 +1562,13 @@ build
SELECT IF(true, t.v, ARRAY[]:::RECORD[])
FROM (VALUES (ARRAY[(1, 'foo')])) AS t(v)
----
error (42804): IF types tuple[] and tuple{int, string}[] cannot be matched
project
├── columns: if:2
├── values
│ ├── columns: column1:1
│ └── (ARRAY[(1, 'foo')],)
└── projections
└── CASE WHEN true THEN column1:1::RECORD[] ELSE ARRAY[] END [as=if:2]

build
SELECT IF(true, ARRAY[]:::RECORD[], t.v)
Expand All @@ -1537,3 +1581,101 @@ project
│ └── (ARRAY[(1, 'foo')],)
└── projections
└── CASE WHEN true THEN ARRAY[] ELSE column1:1::RECORD[] END [as=if:2]

# Regression test for #102110. Ensure that CASE is typed correctly when
# different types are used in different branches.
exec-ddl
CREATE TABLE t102110_1 (t TEXT);
----

exec-ddl
CREATE TABLE t102110_2 (c CHAR);
----

build
SELECT t102110_1.t FROM t102110_1, t102110_2
WHERE t102110_1.t NOT BETWEEN t102110_1.t AND
(CASE WHEN NULL THEN t102110_2.c ELSE t102110_1.t END);
----
project
├── columns: t:1
└── select
├── columns: t:1 t102110_1.rowid:2!null t102110_1.crdb_internal_mvcc_timestamp:3 t102110_1.tableoid:4 c:5 t102110_2.rowid:6!null t102110_2.crdb_internal_mvcc_timestamp:7 t102110_2.tableoid:8
├── inner-join (cross)
│ ├── columns: t:1 t102110_1.rowid:2!null t102110_1.crdb_internal_mvcc_timestamp:3 t102110_1.tableoid:4 c:5 t102110_2.rowid:6!null t102110_2.crdb_internal_mvcc_timestamp:7 t102110_2.tableoid:8
│ ├── scan t102110_1
│ │ └── columns: t:1 t102110_1.rowid:2!null t102110_1.crdb_internal_mvcc_timestamp:3 t102110_1.tableoid:4
│ ├── scan t102110_2
│ │ └── columns: c:5 t102110_2.rowid:6!null t102110_2.crdb_internal_mvcc_timestamp:7 t102110_2.tableoid:8
│ └── filters (true)
└── filters
└── NOT ((t:1 >= t:1) AND (t:1 <= CASE WHEN NULL THEN c:5::STRING ELSE t:1 END))

build
SELECT t102110_1.t FROM t102110_1, t102110_2
WHERE t102110_1.t NOT BETWEEN t102110_1.t AND
(CASE WHEN NULL THEN t102110_1.t ELSE t102110_2.c END);
----
project
├── columns: t:1
└── select
├── columns: t:1 t102110_1.rowid:2!null t102110_1.crdb_internal_mvcc_timestamp:3 t102110_1.tableoid:4 c:5 t102110_2.rowid:6!null t102110_2.crdb_internal_mvcc_timestamp:7 t102110_2.tableoid:8
├── inner-join (cross)
│ ├── columns: t:1 t102110_1.rowid:2!null t102110_1.crdb_internal_mvcc_timestamp:3 t102110_1.tableoid:4 c:5 t102110_2.rowid:6!null t102110_2.crdb_internal_mvcc_timestamp:7 t102110_2.tableoid:8
│ ├── scan t102110_1
│ │ └── columns: t:1 t102110_1.rowid:2!null t102110_1.crdb_internal_mvcc_timestamp:3 t102110_1.tableoid:4
│ ├── scan t102110_2
│ │ └── columns: c:5 t102110_2.rowid:6!null t102110_2.crdb_internal_mvcc_timestamp:7 t102110_2.tableoid:8
│ └── filters (true)
└── filters
└── NOT ((t:1 >= t:1) AND (t:1 <= CASE WHEN NULL THEN t:1::CHAR ELSE c:5 END))

# IF is typed differently (Postgres does not support IF).
build
SELECT t102110_1.t FROM t102110_1, t102110_2
WHERE t102110_1.t NOT BETWEEN t102110_1.t AND
IF(NULL, t102110_2.c, t102110_1.t);
----
project
├── columns: t:1
└── select
├── columns: t:1 t102110_1.rowid:2!null t102110_1.crdb_internal_mvcc_timestamp:3 t102110_1.tableoid:4 c:5 t102110_2.rowid:6!null t102110_2.crdb_internal_mvcc_timestamp:7 t102110_2.tableoid:8
├── inner-join (cross)
│ ├── columns: t:1 t102110_1.rowid:2!null t102110_1.crdb_internal_mvcc_timestamp:3 t102110_1.tableoid:4 c:5 t102110_2.rowid:6!null t102110_2.crdb_internal_mvcc_timestamp:7 t102110_2.tableoid:8
│ ├── scan t102110_1
│ │ └── columns: t:1 t102110_1.rowid:2!null t102110_1.crdb_internal_mvcc_timestamp:3 t102110_1.tableoid:4
│ ├── scan t102110_2
│ │ └── columns: c:5 t102110_2.rowid:6!null t102110_2.crdb_internal_mvcc_timestamp:7 t102110_2.tableoid:8
│ └── filters (true)
└── filters
└── NOT ((t:1 >= t:1) AND (t:1 <= CASE NULL WHEN true THEN c:5 ELSE t:1::CHAR END))

# TODO(#108360): implicit casts from STRING to CHAR should use bpchar.
exec-ddl
CREATE TABLE t108360_1 (t TEXT)
----

exec-ddl
CREATE TABLE t108360_2 (c CHAR)
----

build
SELECT (CASE WHEN t108360_1.t > t108360_2.c THEN t108360_1.t ELSE t108360_2.c END)
FROM t108360_1, t108360_2
WHERE t108360_1.t = (CASE WHEN t108360_1.t > t108360_2.c THEN t108360_1.t ELSE t108360_2.c END);
----
project
├── columns: c:9
├── select
│ ├── columns: t:1!null t108360_1.rowid:2!null t108360_1.crdb_internal_mvcc_timestamp:3 t108360_1.tableoid:4 t108360_2.c:5 t108360_2.rowid:6!null t108360_2.crdb_internal_mvcc_timestamp:7 t108360_2.tableoid:8
│ ├── inner-join (cross)
│ │ ├── columns: t:1 t108360_1.rowid:2!null t108360_1.crdb_internal_mvcc_timestamp:3 t108360_1.tableoid:4 t108360_2.c:5 t108360_2.rowid:6!null t108360_2.crdb_internal_mvcc_timestamp:7 t108360_2.tableoid:8
│ │ ├── scan t108360_1
│ │ │ └── columns: t:1 t108360_1.rowid:2!null t108360_1.crdb_internal_mvcc_timestamp:3 t108360_1.tableoid:4
│ │ ├── scan t108360_2
│ │ │ └── columns: t108360_2.c:5 t108360_2.rowid:6!null t108360_2.crdb_internal_mvcc_timestamp:7 t108360_2.tableoid:8
│ │ └── filters (true)
│ └── filters
│ └── t:1 = CASE WHEN t:1 > t108360_2.c:5 THEN t:1::CHAR ELSE t108360_2.c:5 END
└── projections
└── CASE WHEN t:1 > t108360_2.c:5 THEN t:1::CHAR ELSE t108360_2.c:5 END [as=c:9]