Bug #121181 Temporal BETWEEN returns FALSE instead of NULL when LHS->temporal conversion fails (uninitialized Time_val/Date_val comp
Submitted: 28 Aug 8:03 Modified: 31 Aug 5:40
Reporter: Chunling Qin Email Updates:
Status: Verified Impact on me:
None 
Category:MySQL Server: Optimizer Severity:S2 (Serious)
Version:8.0.46, 8.4.11, 9.7.2 OS:Any
Assigned to: CPU Architecture:Any

[28 Aug 8:03] Chunling Qin
Description:
When the left-hand side operand of BETWEEN cannot be converted to the temporal type of the bounds (out-of-range numeric -> TIME/DATE), MySQL deterministically returns FALSE (0) instead of NULL. The value actually being compared lies OUTSIDE the entire legal temporal domain, which proves the server is comparing an uninitialized Time_val/Date_val (raw m_value, since Time_val() = default does not initialize it).

The conversion failure is acknowledged elsewhere (CAST returns NULL with a warning), and the equivalent >=/<= predicates do not exhibit this, so the NULL (UNKNOWN) semantics is silently violated. In strict mode the error is also swallowed (no ER 1525), while other paths (e.g. 'abc' = DATE'...') do raise ERROR 1525 in the same session.

Environment: self-built debug build of mysql-server trunk (commit 23e360a7fac, 2026-08-20), reports itself as 26.7.0-debug, Linux x86_64.

Related (not duplicates):

Bug #119727 (BETWEEN with NULL bound + mixed temporal column types returns 0; its community fix PR mysql-server#644 only handles all_temporal operands and explicitly does NOT cover a numeric LHS).
Bug #121149 (TIMEDIFF NOT BETWEEN after materialization; lenient string->TIME conversion semantics, different failure mode).

How to repeat:
-- Conversion failure is acknowledged by CAST:
SELECT CAST(99999999 AS TIME);   -- NULL (+warning): HHMMSS 99999999 is invalid
SELECT CAST(1e300 AS DATE);      -- NULL (+warning): out of range

-- BETWEEN returns FALSE (0) where NULL is expected:
SELECT 99999999 BETWEEN TIME'00:00:00' AND TIME'23:59:59';        -- expected NULL, got 0
SELECT 99999999 NOT BETWEEN TIME'00:00:00' AND TIME'23:59:59';    -- expected NULL, got 1

-- Phantom-value proof: result is 0 even over the FULL legal TIME domain,
-- i.e. the compared value is not any legal Time_val:
SELECT 99999999 BETWEEN TIME'-838:59:59' AND TIME'838:59:59';     -- expected NULL, got 0
SELECT 1e300 BETWEEN DATE'0000-01-01' AND DATE'9999-12-31';       -- expected NULL, got 0

-- Other LHS shapes (int / double / decimal) all affected:
SELECT 99999999999 BETWEEN DATE'2024-01-01' AND DATE'2030-01-01'; -- 0, expected NULL
SELECT CAST(1e300 AS DECIMAL(65,0)) BETWEEN DATE'2024-01-01' AND DATE'2030-01-01'; -- 0

-- Strict mode stays silent (compare: SELECT 'abc' = DATE'2024-01-01'; -> ERROR 1525):
SET sql_mode = DEFAULT;
SELECT 1e300 BETWEEN DATE'2024-01-01' AND DATE'2030-01-01';       -- silent 0

-- Row-wise execution (rules out constant folding):
CREATE TABLE m (id INT, x BIGINT UNSIGNED);
INSERT INTO m VALUES (1, 99999999), (2, 8385959);   -- 8385959 = 838:59:59, control row
SELECT id, x BETWEEN TIME'-838:59:59' AND TIME'838:59:59' AS full_domain FROM m;
-- row 1: expected NULL, got 0 (bug); row 2: 1 (correct control)

-- Controls with successful conversion work fine:
SELECT 20240615 BETWEEN DATE'2024-01-01' AND DATE'2030-01-01';   -- 1 (correct)
SELECT 123045 BETWEEN TIME'00:00:00' AND TIME'23:59:59';         -- 1 (correct)

Suggested fix:
if (compare_as_times) {
  Time_val time;
  (void)args[0]->val_time(&time);        // <-- return value (conversion failure) discarded
  value = 0;
  if (!args[0]->null_value) {            // <-- not set on conversion failure
    value = time.for_comparison();       // <-- reads uninitialized value
  }
} else if (compare_as_dates) {
  Date_val date;
  (void)args[0]->val_date(&date, 0);     // <-- same pattern
  ...
}
[31 Aug 5:40] Chaithra Marsur Gopala Reddy
Hi Chunling Qin,

Thank you for the test case. Verified as described.