cockroachdb / cockroachdb/cockroach
`= ANY (subquery)` on the WHERE/semi-join path ignores the session time zone when comparing TIMESTAMPTZ against TIMESTAMP — the relocated (projection) form evaluates correctly
- Dominant language
- Go
- Stars
- 32.5k
- Forks
- 4.1k
- PR merge metrics
- PR metrics pending
Description
**Describe the problem**
When a query compares a `TIMESTAMPTZ` column against a subquery returning `TIMESTAMP` values via
`= ANY (...)`, CockroachDB evaluates the `TIMESTAMP` values **as if the session time zone were UTC** on the
WHERE (decorrelated semi-join) path, while the same predicate relocated into a derived-table projection is
evaluated correctly with the session time zone. Under any non-UTC session time zone the two equivalent
query forms disagree: the WHERE form keeps rows that must have been filtered out.
A related inconsistency: the semantically equivalent `IN (SELECT ...)` form rejects the same cross-type
comparison outright (`unsupported comparison operator: IN `), so `= ANY`
accepts a comparison that `IN` refuses — and then computes it with the wrong time zone.
**To Reproduce**
1. Start a single-node CockroachDB (tested on v26.3.1, `cockroachdb/cockroach:v26.3.1` docker image,
`start-single-node --insecure`).
2. Create the schema and two rows:
```sql
CREATE TABLE t0 (c1 TIMESTAMPTZ);
CREATE TABLE t1 (c1 TIMESTAMP);
INSERT INTO t0 VALUES (TIMESTAMPTZ '1969-12-27 19:31:21+00:00');
INSERT INTO t1 VALUES (TIMESTAMP '1969-12-27 19:31:21');
```
3. Set any non-UTC session time zone and run the two equivalent forms:
```sql
SET TIME ZONE 'Asia/Shanghai'; -- also verified with America/New_York
-- (a) predicate in WHERE
SELECT t0.c1 FROM t0 WHERE t0.c1 = ANY (SELECT t1.c1 FROM t1);
-- (b) the same predicate relocated into a derived-table projection
SELECT ref0 FROM (
SELECT t0.c1 AS ref0, (t0.c1 = ANY (SELECT t1.c1 FROM t1)) AS ref1 FROM t0
) AS s WHERE ref1;
```
4. See the disagreement:
```
(a) c1 = 1969-12-28 03:31:21+08 -- 1 row <-- WRONG (expected: 0 rows)
(b) ref0 = (empty) -- 0 rows <-- correct
```
`TIMESTAMP '1969-12-27 19:31:21'` interpreted at +08:00 is `1969-12-27 11:31:21 UTC`, which does NOT
equal the `t0` value — form (a) should return 0 rows. With the default UTC session zone both forms agree
(return the same 1 row), so the bug only manifests under a non-UTC session time zone (e.g. what JDBC sets
from the client host zone).
The same defect also fires through a scalar aggregate comparison (same 2-row shape, verified twice):
```sql
CREATE TABLE s0 (c1 TIMESTAMP);
CREATE TABLE s1 (c1 TIMESTAMPTZ);
INSERT INTO s0 VALUES (TIMESTAMP '1969-12-25 05:05:05');
INSERT INTO s1 VALUES (TIMESTAMPTZ '1969-12-25 05:05:05+00:00');
SET TIME ZONE 'Asia/Shanghai';
SELECT s0.c1 FROM s0 WHERE s0.c1 >= (SELECT MIN(s1.c1) FROM s1);
-- 1 row (1969-12-25 05:05:05) <-- WRONG: at +08:00, s0.c1 is 1969-12-24 21:05:05 UTC < MIN(s1.c1)
SELECT ref0 FROM (
SELECT s0.c1 AS ref0, (s0.c1 >= (SELECT MIN(s1.c1) FROM s1)) AS ref1 FROM s0
) AS s WHERE ref1;
-- 0 rows <-- correct
```
And the equivalent `IN` form is rejected instead:
```sql
SET TIME ZONE 'Asia/Shanghai';
SELECT t0.c1 FROM t0 WHERE t0.c1 IN (SELECT t1.c1 FROM t1);
-- ERROR: unsupported comparison operator: IN (SQLSTATE 22023)
```
**Expected behavior**
The WHERE form must interpret `TIMESTAMP` values with the session time zone (returning 0 rows in the
examples above), consistently with the relocated projection form — or reject the cross-type comparison the
way `IN` does. Two semantically equivalent query forms must not return different results.
**Additional data / screenshots**
Schema and queries: included above (bare single-column tables, 1 row each; no indexes or other objects
involved).
Mechanism (from plan behavior): the WHERE-path `= ANY` is decorrelated into a (hash) semi-join and the
`TIMESTAMP` build-side values are compared against the `TIMESTAMPTZ` probe with a UTC interpretation,
ignoring the session time zone; the relocated form keeps the comparison as a per-row scalar evaluation,
which honors the session zone. Both directions of the cross-type pair (TIMESTAMP outer / TIMESTAMPTZ
outer) reproduce for `= ANY`.
**Environment:**
- CockroachDB version `v26.3.1`
- Server OS: Linux
- Client app `cockroach sql`
**Additional context**
What was the impact?
Add any other context about the problem here.
Jira issue: CRDB-68361
Contributor guide
Assessment
This issue has not been assessed yet.