diff --git a/misc/python/materialize/checks/all_checks/float_canonicalization.py b/misc/python/materialize/checks/all_checks/float_canonicalization.py new file mode 100644 index 0000000000000..f622383a5f7f5 --- /dev/null +++ b/misc/python/materialize/checks/all_checks/float_canonicalization.py @@ -0,0 +1,220 @@ +# Copyright Materialize, Inc. and contributors. All rights reserved. +# +# Use of this software is governed by the Business Source License +# included in the LICENSE file at the root of this repository. +# +# As of the Change Date specified in that file, in accordance with +# the Business Source License, use of this software will be governed +# by the Apache License, Version 2.0. +from textwrap import dedent + +from materialize.checks.actions import Testdrive +from materialize.checks.checks import Check, externally_idempotent + +# Row packing canonicalizes floats (-0.0 packs as +0.0, every NaN packs as one +# bit pattern) so that byte equality of packed rows agrees with SQL float +# equality. Data written by versions without that canonicalization carries the +# raw bit patterns, so these checks exercise the cross-version story: rows +# written by an older version must cancel against retractions written by a +# newer one, and must land in the same DISTINCT/GROUP BY/index groups. +# +# In multi-version scenarios validate() also runs on versions without the +# canonicalization, where -0.0 is a distinct arrangement key, so the +# assertions that depend on it are gated on version 26.33 (which is when the +# canonicalization was introduced). Row counts and upsert results hold on all +# versions and are asserted unconditionally. +# +# NOTE: a genuine -0.0 needs a text->float cast ('-0'), a -0.0 literal goes +# through numeric (no signed zero) and arrives as +0.0. + + +class FloatCanonicalizationTable(Check): + """-0.0 and NaN in a table across versions: retraction of old-encoding + rows, DISTINCT/GROUP BY collapse, and index point lookups.""" + + def initialize(self) -> Testdrive: + return Testdrive(dedent(""" + > CREATE TABLE float_canon_table (id INT, f DOUBLE PRECISION); + > INSERT INTO float_canon_table VALUES + (1, '-0'), (2, '0'), (3, 'NaN'), (4, '-0'), (5, 1.5); + """)) + + def manipulate(self) -> list[Testdrive]: + return [ + Testdrive(dedent(s)) + for s in [ + """ + > CREATE MATERIALIZED VIEW float_canon_table_mv AS + SELECT f, COUNT(*) AS c FROM float_canon_table GROUP BY f; + > CREATE DEFAULT INDEX ON float_canon_table; + > INSERT INTO float_canon_table VALUES (6, '-0'); + """, + """ + > DELETE FROM float_canon_table WHERE id = 4; + > INSERT INTO float_canon_table VALUES (7, '0'), (8, 'NaN'); + """, + ] + ] + + def validate(self) -> Testdrive: + return Testdrive(dedent(""" + > SELECT count(*) FROM float_canon_table; + 7 + + >[version>=2603300] SELECT count(*) FROM (SELECT DISTINCT f FROM float_canon_table); + 3 + + >[version>=2603300] SELECT f::text, c FROM float_canon_table_mv; + 0 4 + 1.5 1 + NaN 2 + + >[version>=2603300] SELECT id FROM float_canon_table WHERE f = 0; + 1 + 2 + 6 + 7 + + >[version>=2603300] SELECT id FROM float_canon_table WHERE f = 'NaN'; + 3 + 8 + """)) + + +@externally_idempotent(False) +class FloatCanonicalizationPgCdc(Check): + """-0.0 and NaN ingested from a Postgres source across versions: an + upstream DELETE/UPDATE after an upgrade must retract rows whose additions + were written with the old float encoding.""" + + def initialize(self) -> Testdrive: + return Testdrive(dedent(""" + $ postgres-execute connection=postgres://postgres:postgres@postgres + CREATE USER postgres_float_canon WITH SUPERUSER PASSWORD 'postgres'; + ALTER USER postgres_float_canon WITH replication; + DROP PUBLICATION IF EXISTS float_canon_publication; + DROP TABLE IF EXISTS float_canon_pg_table; + CREATE TABLE float_canon_pg_table (id INT PRIMARY KEY, f DOUBLE PRECISION); + ALTER TABLE float_canon_pg_table REPLICA IDENTITY FULL; + INSERT INTO float_canon_pg_table VALUES (1, '-0'), (2, '0'), (3, 'NaN'), (4, '-0'); + CREATE PUBLICATION float_canon_publication FOR ALL TABLES; + + > CREATE SECRET float_canon_pgpass AS 'postgres'; + + > CREATE CONNECTION float_canon_pg_conn FOR POSTGRES + HOST 'postgres', + DATABASE postgres, + USER postgres_float_canon, + PASSWORD SECRET float_canon_pgpass; + + > CREATE SOURCE float_canon_pg_source + FROM POSTGRES CONNECTION float_canon_pg_conn + (PUBLICATION 'float_canon_publication'); + > CREATE TABLE float_canon_pg FROM SOURCE float_canon_pg_source + (REFERENCE float_canon_pg_table); + + # Wait for the snapshot so the initial rows are ingested (and + # thus encoded) by the version running this phase. + > SELECT count(*) FROM float_canon_pg; + 4 + """)) + + def manipulate(self) -> list[Testdrive]: + return [ + Testdrive(dedent(s)) + for s in [ + """ + $ postgres-execute connection=postgres://postgres:postgres@postgres + INSERT INTO float_canon_pg_table VALUES (5, '-0'), (6, 'NaN'); + """, + """ + $ postgres-execute connection=postgres://postgres:postgres@postgres + DELETE FROM float_canon_pg_table WHERE id IN (1, 6); + UPDATE float_canon_pg_table SET f = '0' WHERE id = 4; + """, + ] + ] + + def validate(self) -> Testdrive: + return Testdrive(dedent(""" + > SELECT count(*) FROM float_canon_pg; + 4 + + >[version>=2603300] SELECT count(*) FROM (SELECT DISTINCT f FROM float_canon_pg); + 2 + + >[version>=2603300] SELECT id FROM float_canon_pg WHERE f = 0; + 2 + 4 + 5 + + >[version>=2603300] SELECT id FROM float_canon_pg WHERE f = 'NaN'; + 3 + """)) + + +def float_canon_schemas() -> str: + return dedent(""" + $ set float-canon-keyschema={ + "type": "record", + "name": "Key", + "fields": [ {"name": "key1", "type": "double"} ] + } + + $ set float-canon-schema={ + "type" : "record", + "name" : "test", + "fields" : [ {"name": "f1", "type": "double"} ] + } + """) + + +class FloatCanonicalizationUpsert(Check): + """-0.0 in a Kafka upsert source's key and value across versions: a -0.0 + and a +0.0 key are the same key, and a post-upgrade tombstone must retract + a value row written with the old float encoding.""" + + def initialize(self) -> Testdrive: + return Testdrive(float_canon_schemas() + dedent(""" + $ kafka-create-topic topic=float-canon-upsert + + $ kafka-ingest format=avro key-format=avro topic=float-canon-upsert key-schema=${float-canon-keyschema} schema=${float-canon-schema} + {"key1": -0.0} {"f1": 1.0} + {"key1": 2.0} {"f1": -0.0} + + > CREATE SOURCE float_canon_upsert_src + FROM KAFKA CONNECTION kafka_conn (TOPIC 'testdrive-float-canon-upsert-${testdrive.seed}') + > CREATE TABLE float_canon_upsert FROM SOURCE float_canon_upsert_src (REFERENCE "testdrive-float-canon-upsert-${testdrive.seed}") + FORMAT AVRO USING CONFLUENT SCHEMA REGISTRY CONNECTION csr_conn + ENVELOPE UPSERT + + # Wait for the snapshot so the initial rows are ingested (and + # thus encoded) by the version running this phase. + > SELECT count(*) FROM float_canon_upsert; + 2 + """)) + + def manipulate(self) -> list[Testdrive]: + return [ + Testdrive(float_canon_schemas() + dedent(s)) + for s in [ + """ + # The +0.0 key is the same key as the -0.0 key, so this + # replaces the (0, 1) row rather than adding a third row. + $ kafka-ingest format=avro key-format=avro topic=float-canon-upsert key-schema=${float-canon-keyschema} schema=${float-canon-schema} + {"key1": 0.0} {"f1": 3.0} + """, + """ + # Tombstone the key whose value row (f1 = -0.0) may have been + # written with the old float encoding. + $ kafka-ingest format=avro key-format=avro topic=float-canon-upsert key-schema=${float-canon-keyschema} schema=${float-canon-schema} + {"key1": 2.0} + """, + ] + ] + + def validate(self) -> Testdrive: + return Testdrive(dedent(""" + > SELECT key1::text, f1::text FROM float_canon_upsert; + 0 3 + """)) diff --git a/src/expr/src/scalar/func.rs b/src/expr/src/scalar/func.rs index 34b21e795723e..9b52c50918690 100644 --- a/src/expr/src/scalar/func.rs +++ b/src/expr/src/scalar/func.rs @@ -1305,12 +1305,14 @@ fn neg_interval_inner(a: Interval) -> Result { } fn log_guard_numeric(val: &Numeric, function_name: &str) -> Result<(), EvalError> { - if val.is_negative() { - return Err(EvalError::NegativeOutOfDomain(function_name.into())); - } + // Check zero before the sign, like PostgreSQL, so that a negative zero + // (which the dec crate considers negative) reports the same error as +0. if val.is_zero() { return Err(EvalError::ZeroOutOfDomain(function_name.into())); } + if val.is_negative() { + return Err(EvalError::NegativeOutOfDomain(function_name.into())); + } Ok(()) } @@ -1353,12 +1355,17 @@ fn log_base_numeric(mut a: Numeric, mut b: Numeric) -> Result Result { - if a == 0.0 && b.is_sign_negative() { + // Strict less-than, so that a -0.0 exponent counts as zero (x^0 = 1) + // rather than as negative, like PostgreSQL. + if a == 0.0 && b < 0.0 { return Err(EvalError::Undefined( "zero raised to a negative power".into(), )); } - if a.is_sign_negative() && b.fract() != 0.0 { + // Strict less-than, like PostgreSQL, so that -0.0 does not count as + // negative (row packing canonicalizes -0.0 to +0.0, so the sign of a zero + // must not be observable) and NaN propagates to a NaN result. + if a < 0.0 && b.fract() != 0.0 { // Equivalent to PG error: // > a negative number raised to a non-integer power yields a complex result return Err(EvalError::ComplexOutOfRange("pow".into())); diff --git a/src/expr/src/scalar/func/impls/float64.rs b/src/expr/src/scalar/func/impls/float64.rs index b709703da35b8..cfec89299c2ef 100644 --- a/src/expr/src/scalar/func/impls/float64.rs +++ b/src/expr/src/scalar/func/impls/float64.rs @@ -371,6 +371,12 @@ fn cot(a: f64) -> Result { if a.is_infinite() { return Err(EvalError::InfinityOutOfDomain("cot".into())); } + // -0.0 behaves as +0.0, so cot(-0.0) is +Infinity rather than + // PostgreSQL's -Infinity. Row packing canonicalizes -0.0 to +0.0, so + // honoring the sign here would make the result depend on whether the + // input crossed a packing boundary (e.g. a view vs a materialized view + // of the same query). + let a = if a == 0.0 { 0.0 } else { a }; Ok(1.0 / a.tan()) } @@ -384,25 +390,30 @@ fn degrees(a: f64) -> f64 { a.to_degrees() } +// The guards in `log10` and `ln` use `== 0.0` and `< 0.0` rather than the +// sign bit, so that -0.0 errors like +0.0 and NaN propagates to a NaN result, +// like PostgreSQL. The sign of a zero must not be observable, since row +// packing canonicalizes -0.0 to +0.0. + #[sqlfunc(sqlname = "log10f64")] fn log10(a: f64) -> Result { - if a.is_sign_negative() { - return Err(EvalError::NegativeOutOfDomain("log10".into())); - } if a == 0.0 { return Err(EvalError::ZeroOutOfDomain("log10".into())); } + if a < 0.0 { + return Err(EvalError::NegativeOutOfDomain("log10".into())); + } Ok(a.log10()) } #[sqlfunc(sqlname = "lnf64")] fn ln(a: f64) -> Result { - if a.is_sign_negative() { - return Err(EvalError::NegativeOutOfDomain("ln".into())); - } if a == 0.0 { return Err(EvalError::ZeroOutOfDomain("ln".into())); } + if a < 0.0 { + return Err(EvalError::NegativeOutOfDomain("ln".into())); + } Ok(a.ln()) } diff --git a/src/expr/src/scalar/func/impls/numeric.rs b/src/expr/src/scalar/func/impls/numeric.rs index 6a7bc6d67a57c..a25eabfe7cdd7 100644 --- a/src/expr/src/scalar/func/impls/numeric.rs +++ b/src/expr/src/scalar/func/impls/numeric.rs @@ -79,12 +79,14 @@ fn floor_numeric(mut a: Numeric) -> Numeric { } fn log_guard_numeric(val: &Numeric, function_name: &str) -> Result<(), EvalError> { - if val.is_negative() { - return Err(EvalError::NegativeOutOfDomain(function_name.into())); - } + // Check zero before the sign, like PostgreSQL, so that a negative zero + // (which the dec crate considers negative) reports the same error as +0. if val.is_zero() { return Err(EvalError::ZeroOutOfDomain(function_name.into())); } + if val.is_negative() { + return Err(EvalError::NegativeOutOfDomain(function_name.into())); + } Ok(()) } diff --git a/src/persist-client/src/iter.rs b/src/persist-client/src/iter.rs index 050062e4ec68a..6a0c4b5fb7078 100644 --- a/src/persist-client/src/iter.rs +++ b/src/persist-client/src/iter.rs @@ -34,7 +34,6 @@ use mz_persist_types::arrow::{ArrayBound, ArrayIdx, ArrayOrd}; use mz_persist_types::columnar::data_type; use mz_persist_types::part::Part; use mz_persist_types::{Codec, Codec64}; -use semver::Version; use timely::progress::Timestamp; use tracing::{Instrument, debug_span}; @@ -45,11 +44,6 @@ use crate::internal::metrics::{ReadMetrics, ShardMetrics}; use crate::internal::state::{HollowRun, RunMeta, RunOrder, RunPart}; use crate::metrics::Metrics; -/// Versions prior to this had bugs in consolidation, or used a different sort. However, -/// we can assume that consolidated parts at this version or higher were consolidated -/// according to the current definition. -pub const MINIMUM_CONSOLIDATED_VERSION: Version = Version::new(0, 67, 0); - /// The data needed to fetch a batch part, bundled up to make it easy /// to send between threads. #[derive(Debug, Clone)] diff --git a/src/repr/src/row.rs b/src/repr/src/row.rs index 35403dc70b00c..d003aee5a2b50 100644 --- a/src/repr/src/row.rs +++ b/src/repr/src/row.rs @@ -1839,6 +1839,43 @@ const TINY: usize = 1 << 8; const SHORT: usize = 1 << 16; const LONG: usize = 1 << 32; +/// The quiet positive NaN. This is the bit pattern of `f64::NAN` on x86 and +/// aarch64, spelled out because Rust does not guarantee the bit pattern of +/// `f64::NAN` and packed bytes must be identical across platforms. +const CANONICAL_F64_NAN_BITS: u64 = 0x7ff8_0000_0000_0000; +/// The `f32` analogue of [`CANONICAL_F64_NAN_BITS`]. +const CANONICAL_F32_NAN_BITS: u32 = 0x7fc0_0000; + +/// Rewrites `f` to the canonical representative of its `Datum` equality class. +/// +/// `Datum` float equality is semantic (via `OrderedFloat`): -0.0 equals +0.0 +/// and all NaN bit patterns are equal to each other. Packed rows are compared +/// as raw bytes (`Row` equality, arrangement keys, index lookups), so equal +/// datums must pack to equal bytes. Each equality class therefore encodes as a +/// single bit pattern: zeros as +0.0 and NaNs as the quiet positive NaN. +#[inline] +fn canonicalize_float32(f: OrderedFloat) -> f32 { + if f.is_nan() { + f32::from_bits(CANONICAL_F32_NAN_BITS) + } else if *f == 0.0 { + 0.0 + } else { + f.into_inner() + } +} + +/// See [`canonicalize_float32`]. +#[inline] +fn canonicalize_float64(f: OrderedFloat) -> f64 { + if f.is_nan() { + f64::from_bits(CANONICAL_F64_NAN_BITS) + } else if *f == 0.0 { + 0.0 + } else { + f.into_inner() + } +} + fn push_datum(data: &mut D, datum: Datum) where D: Vector, @@ -1906,11 +1943,11 @@ where } Datum::Float32(f) => { data.push(Tag::Float32.into()); - data.extend_from_slice(&f.to_bits().to_le_bytes()); + data.extend_from_slice(&canonicalize_float32(f).to_bits().to_le_bytes()); } Datum::Float64(f) => { data.push(Tag::Float64.into()); - data.extend_from_slice(&f.to_bits().to_le_bytes()); + data.extend_from_slice(&canonicalize_float64(f).to_bits().to_le_bytes()); } Datum::Date(d) => { data.push(Tag::Date.into()); @@ -4149,11 +4186,9 @@ mod tests { //test_list_encoding_inner(LONG + 1); // huge } - /// Demonstrates that DatumList's Eq (bytewise) and Ord (datum-by-datum) are now consistent. - /// A list containing -0.0 and one containing +0.0 have different byte representations - /// (IEEE 754 distinguishes them), originally Eq says they are not equal. But after - /// using the new Datum::cmp, Eq says they are equal, which matches what Ord - /// compares via iter().cmp(other.iter()), and them as equal. + /// DatumList's Eq and Ord (both datum-by-datum) agree that lists + /// containing -0.0 and +0.0 are equal. Packing also canonicalizes the two + /// zeros to the same bytes, see `test_float_packing_canonicalizes`. #[mz_ore::test] fn test_datum_list_eq_ord_consistency() { // Build list containing +0.0 @@ -4170,12 +4205,10 @@ mod tests { }); let list_neg = row_neg.unpack_first().unwrap_list(); - // Eq is bytewise: different encodings => not equal - // This was a bug in the past, so we test it. - assert_eq!( - list_pos, list_neg, - "Eq should see different encodings as equal" - ); + assert_eq!(list_pos, list_neg, "-0.0 and +0.0 lists must be equal"); + + // Packing canonicalizes -0.0 to +0.0, so the rows are equal bytewise. + assert_eq!(row_pos, row_neg, "-0.0 and +0.0 must pack to equal bytes"); // Ord is datum-by-datum: -0.0 and +0.0 compare equal as Datums assert_eq!( @@ -4185,8 +4218,47 @@ mod tests { ); } - /// Demonstrates that DatumMap's derived Eq (bytewise) can make maps with equal keys and - /// values compare equal when values have different encodings (e.g. -0.0 vs +0.0). + /// Equal float datums must pack to identical bytes. Arrangement keys, + /// index lookups, and `Row` equality compare packed rows bytewise, so the + /// packer canonicalizes -0.0 to +0.0 and every NaN to one bit pattern. + #[mz_ore::test] + fn test_float_packing_canonicalizes() { + let f64_classes: &[(f64, f64)] = &[ + (-0.0, 0.0), + // Negative quiet NaN, what x86 produces for e.g. inf - inf. + (f64::NAN, f64::from_bits(0xfff8_0000_0000_0000)), + // Quiet NaN with a payload. + (f64::NAN, f64::from_bits(0x7ff8_0000_dead_beef)), + // Signaling NaN. + (f64::NAN, f64::from_bits(0x7ff0_0000_0000_0001)), + ]; + for (a, b) in f64_classes { + let row_a = Row::pack_slice(&[Datum::Float64(OrderedFloat(*a))]); + let row_b = Row::pack_slice(&[Datum::Float64(OrderedFloat(*b))]); + assert_eq!(row_a, row_b, "{a:?} and {b:?} must pack to equal bytes"); + // Unpacking still yields an equal datum. + assert_eq!(row_a.unpack_first(), Datum::Float64(OrderedFloat(*a))); + } + + let f32_classes: &[(f32, f32)] = &[ + (-0.0, 0.0), + (f32::NAN, f32::from_bits(0xffc0_0000)), + (f32::NAN, f32::from_bits(0x7fc0_dead)), + ]; + for (a, b) in f32_classes { + let row_a = Row::pack_slice(&[Datum::Float32(OrderedFloat(*a))]); + let row_b = Row::pack_slice(&[Datum::Float32(OrderedFloat(*b))]); + assert_eq!(row_a, row_b, "{a:?} and {b:?} must pack to equal bytes"); + assert_eq!(row_a.unpack_first(), Datum::Float32(OrderedFloat(*a))); + } + + // Non-zero, non-NaN values keep their exact bits. + let row = Row::pack_slice(&[Datum::Float64(OrderedFloat(-1.5))]); + assert_eq!(row.unpack_first(), Datum::Float64(OrderedFloat(-1.5))); + } + + /// Maps with equal keys and equal values (-0.0 vs +0.0) compare equal, + /// both datum-by-datum and bytewise (packing canonicalizes the zeros). #[mz_ore::test] fn test_datum_map_eq_bytewise_consistency() { // Build map {"k": +0.0} @@ -4205,11 +4277,8 @@ mod tests { }); let map_neg = row_neg.unpack_first().unwrap_map(); - // Same keys and semantically equal values, but Eq (bytewise) says not equal - assert_eq!( - map_pos, map_neg, - "DatumMap Eq is semantic; -0.0 and +0.0 have different encodings but are equal" - ); + assert_eq!(map_pos, map_neg, "-0.0 and +0.0 maps must be equal"); + assert_eq!(row_pos, row_neg, "-0.0 and +0.0 must pack to equal bytes"); // Verify they have the same logical content let entries_pos: Vec<_> = map_pos.iter().collect(); let entries_neg: Vec<_> = map_neg.iter().collect(); diff --git a/src/repr/src/strconv.rs b/src/repr/src/strconv.rs index ac4ac8fa3d194..e9d3b2168ec08 100644 --- a/src/repr/src/strconv.rs +++ b/src/repr/src/strconv.rs @@ -325,16 +325,21 @@ where // // Note that we have to fix up ryu's formatting in a few cases to match // PostgreSQL. PostgreSQL spells out "Infinity" in full, never emits a - // trailing ".0", formats positive exponents as e.g. "1e+10" rather than - // "1e10", and emits a negative sign for negative zero. If we need to speed - // up float formatting, we can look into forking ryu and making these edits - // directly, but for now it doesn't seem worth it. + // trailing ".0", and formats positive exponents as e.g. "1e+10" rather + // than "1e10". If we need to speed up float formatting, we can look into + // forking ryu and making these edits directly, but for now it doesn't + // seem worth it. match f.classify() { FpCategory::Infinite if f.is_sign_negative() => buf.write_str("-Infinity"), FpCategory::Infinite => buf.write_str("Infinity"), FpCategory::Nan => buf.write_str("NaN"), - FpCategory::Zero if f.is_sign_negative() => buf.write_str("-0"), + // Negative zero renders as "0". Packed rows canonicalize -0.0 to +0.0 + // (see `push_datum`), so a -0.0 here can only be an unpacked + // intermediate. Rendering it as "0" keeps text output identical + // whether or not the value crossed a packing boundary. This + // deliberately deviates from PostgreSQL, which prints "-0". + FpCategory::Zero if f.is_sign_negative() => buf.write_str("0"), _ => { debug_assert!(f.is_finite()); let mut ryu_buf = ryu::Buffer::new(); diff --git a/src/sql/src/func.rs b/src/sql/src/func.rs index 4ecbc80717aee..5dbf6153710b7 100644 --- a/src/sql/src/func.rs +++ b/src/sql/src/func.rs @@ -6057,11 +6057,18 @@ pub static OP_IMPLS: LazyLock> = LazyLock::new(|| { // - If you are writing functions here that do not simply use // `BinaryFunc::Eq`, you will break row equality (used in e.g. // DISTINCT operations and JOINs). In short, this is totally verboten. - // - The implementation of `BinaryFunc::Eq` is byte equality on two - // datums, and we enforce that both inputs to the function are of the - // same type in planning. However, it's possible that we will perform - // equality on types not listed here (e.g. `Varchar`) due to decisions - // made in the optimizer. + // - `BinaryFunc::Eq` is `Datum` equality, and we enforce that both + // inputs to the function are of the same type in planning. `Datum` + // equality must agree with byte equality of packed rows, because + // arrangement keys and index lookups compare packed bytes. Packing + // upholds this by canonicalizing every type whose `Datum` equality + // is coarser than bit equality (see `push_datum` in `mz_repr`: + // floats collapse -0.0/+0.0 and all NaN payloads, numerics are + // reduced). A type whose `Datum` equality identifies values with + // distinct packed encodings must not be listed here without a + // matching canonicalization in packing. + // - It's possible that we will perform equality on types not listed + // here (e.g. `Varchar`) due to decisions made in the optimizer. // - Null inputs are handled by `BinaryFunc::eval` checking `propagates_nulls`. "=" => Scalar { params!(Numeric, Numeric) => BF::from(func::Eq) => Bool, 1752; diff --git a/src/storage-types/src/snapshots/source-datas.txt b/src/storage-types/src/snapshots/source-datas.txt index b1cbc40f37bd8..1df1872496b5e 100644 --- a/src/storage-types/src/snapshots/source-datas.txt +++ b/src/storage-types/src/snapshots/source-datas.txt @@ -4,7 +4,7 @@ ChgKBQoDggEACgYKAioAEAEKBwoDggEAEAESAwoBWBIFCgNcZ2MSBQoDaE1f,ClIKJUIjW/KIr78nbfK Cr4BCgUKA4ICAAoECgJCAAquAQqrAbIBpwEKEQoICgZVel9lRl8SBQoDmgIAChQKCgoITFBrb0tmelASBgoCIgAQAQoYCg8KDV1qdGhXZldLRkpbYFISBQoDsgIACg8KBQoDbW9lEgYKAkIAEAEKGAoECgJTUBIQCgyKAgkKBzoFCgMIigEQAQoUCgkKB0J2Xk5MR2kSBwoDogIAEAEKFAoJCgd2Y09Eb0JCEgcKA/oBABABIgsQseHs9rfmiPfSARIGCgRBcl9lEg0KC2Z6XlN0Tl5ETExiEgQKAnRr,CsIBCgvwAb2/6rGEn63vRQoIggEFCOG2xhcKqAGyAaQBCjFCLwQNYlUJXFzzi5KkAOKArmnyvayh85OTpUT0grmwAFxJ0ahFYOSQne+/vQs0Ij0uCgsg5Zrij/HOorSNAQoTmgEQCNiMDhAFGOqOAiDo08P3AQoHggEECIm1agog+gEdChsiGcIBFgoJORRyZSaQc2JsEOr//////////wEKFYoCEgiqyqF3EJKJ4hkaBgiOgICAFAoL6AHzzI3i+IrGw1Q= ChYKBwoD6gEAEAEKBQoD8gEACgQKAmIAEg8KDVVtVVRgSUtOR1NHbl4SEQoPYHpJYE9ScXp2YHpnXnNjEgQKAnFb,CjEKBeABn5cBCgfQAc/yuY8HCh+iARwIvt6DqwcQhYOLg/z/////ARiI68f3+vWtxJoB CgA=,CgA= -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OVImwrHCk8KUJAkL86eBhfGjrY8mGxIfogEcCJL93LwFELTgte3+/////wEYz8qZyISI99XnAQowCg4vXPK4pIfDqyHwp4CBJRIeogEbCJHv3tD8/////wEQhvSi6gMYgde735qT5O1IClQKLTpmJy/ChVwm77u/0agl77u/PPGVkp46bwlcctGo8pS5qvKTjKvzp62H8ZGXpxIjogEgCLXBjJP//////wEQ9Jvirf3/////ARjvo7au1eTU/S8KWAowOvCsnr8DJXvIuvCdsq3zs4Sg8YCIiWAAOvSPs7vigK7xi5m9Gwt/Ae+7v8KkDX9LEiSiASEInt/Jwfj/////ARCe5uqJ+P////8BGO3i1J6+vurQ7gEKQAodP/KcgIcuOvKBqpbzjb2iACQyw6RmKvCflbTvu78SH6IBHAirw+L++f////8BEIuqxdwGGNj9647N69T4swEKWgo8QHPDjg3yk6SVC/GfuKnwuIeuB++/vfKOvYFxIgtgDe+/vX/zv4mdY/GRg5/yko2j8YKZuiZ8PV7CpVxgEhqiARcI2c+ntwUQhfr8rQQYrpHDpuLF0ur4AQpKCidNfwcLw6444oCudDEk77u/LnwlJCDyv7muL/CppKnypoeV8Jeksw0SH6IBHAjWsqfzBRCJq7Sd+v////8BGIzRhabu3+jw/AEKOQobYRsNRUAL9IW7ii42InvyvaOH85CamQt/e8OIEhqiARcIgI/H8gUQ/5D9zAUYpaeg9cbglPfPAQpeCjZz8KS6gHtyOfCaq41PBAd/Vy9K8biRv/GVl5hp8Yesjj8md07zmY+78J+VtAABXncAYO+7vwkSJKIBIQiMheea+f////8BEOX+nJj9/////wEYyc7x4+W39Y+4AQo6ChN1J9GoWAD0gIqBPPKQo4DwkqK7EiOiASAI+qDE/Pn/////ARCEhei5+P////8BGOWXkPL2j8/VTwpGCh52Ksi6AjzysbC28ZmflCpo77+9yLomfPOziJbCpWASJKIBIQjD65X0/P////8BEKab0KH8/////wEY3avb883fmebvAQpDCiV2PWDIuvCflbReL2A9SiTvu797YOWgkAALVvOrqZs9bHvygqWvEhqiARcIr6SxvQUQvvuSqAIYt7mwwdinptivAQprCkN58aCojCck8r6nsyY885+9vvCimr4/8qWUkQM98pWrkfOvp5ANe/KkmbbxqI60XG/zvri9PfCngqlk4oCu8bm8g9GoEiSiASEI+6Xr4vz/////ARCnn7jX+P////8BGLHvq/aT/PyB8wEKVgo4wqXwoKWFKFwlPNGo8qqSkPKLg6w/Y/G5raBWfDwGV8i6PHZAJPConJ/xrpSEP/Ctto3yprCpL3oSGqIBFwjlxNrrBhDXmIvLBhiu+svdhJOvwbcBCjUKDcOjPyov4oCu0ajigK4SJKIBIQi12ZTP+/////8BEKvQrvb7/////wEYjoG8tM/k353CAQpMCiTIujQbYAkl77+99ImEvCJ6dCcNwph18LWquMOaPTwb8JO9lAESJKIBIQiE+Jze/f////8BELPNsKX5/////wEY7ZaJ2J7k54P0AQoVCg/RqCbIuiEuBMi6atGowrUSAggBCikKI+G8p0xeBPORmK3RqEZjPCrCq3sLQyIG8r6MuRvwoIqHayc1EgIIAQo9ChXigK5gJ0fwkKOkYAUbL/Goh69cWi4SJKIBIQjEoaz9+/////8BEO3Rh874/////wEYm+62x8+e08+3AQo0ChLigK7wkpqHLirvgJ0n7IiSAFUSHqIBGwiL8KncBRDjifWI/P////8BGIm0wdqFk9ncZwo1Chjvv70qe/Ctsavwn5W0JPSGq4XvjJolf38SGaIBFgj2sMS4BBCjxNfNAhjm7auV8ITTmWcKIwoF8b+qjRsSGqIBFwjHg7KzAxDhhOLhARivr+m57fKdpL8BCjIKCvOTt6Zg85ezviUSJKIBIQisubaC//////8BEOOYxeD7/////wEYvtei8sCt0rmkAQo1ChLzvpKP8o+OjD0FPD8mXGI9PyISH6IBHAjf183Z+/////8BEJyj3KQBGIy2iracxqHunAEKSAol9IC6kX0uevCjpZIv87+9miZdT/GcgLX0iLy1wqLwnZy/86yeoRIfogEcCJGchrz5/////wEQ/5iWugIYnra4p6ac8fToAQpRCi70hIW+8r+NmPG8u4888J+VtEdTwqUqc1wkBGDznaKy77u/yLpxw5gbP0nyq7m5Eh+iARwIsZTx8f7/////ARD1/IyoBhjp//3NjuzU+fUBCgzCAQkKBVghUVRMEAgKCIIBBQi20agF diff --git a/src/storage-types/src/sources.rs b/src/storage-types/src/sources.rs index 0fcdda02d8783..370a0dcca0a0b 100644 --- a/src/storage-types/src/sources.rs +++ b/src/storage-types/src/sources.rs @@ -2180,9 +2180,14 @@ mod tests { // and the two versions would not consolidate out. // This can impact correctness! // - // If you need to change how SourceDatas are encoded, that's still fine... - // but we'll also need to increase - // the MINIMUM_CONSOLIDATED_VERSION as part of the same release. + // If you need to change how SourceDatas are encoded, that can be + // okay, but think through the consequences: a record whose old and + // new encodings differ never consolidates away inside existing + // persist shards, so an addition written by an old version and its + // retraction written by a new version both stay in the shard + // forever. Readers stay correct because they consolidate rows after + // decoding, where the two encodings become identical, but every + // reader must tolerate such pairs. Loop in the persist team. assert_eq!( encoded, reencoded.as_str(), diff --git a/test/sqllogictest/float.slt b/test/sqllogictest/float.slt index f1b1dd21cf9d4..eb9b5140404f4 100644 --- a/test/sqllogictest/float.slt +++ b/test/sqllogictest/float.slt @@ -12,7 +12,7 @@ mode cockroach query T SELECT '-0'::float::text ---- --0 +0 query T SELECT '+0'::float::text @@ -42,7 +42,7 @@ SELECT '.0'::float::text query T SELECT '-.0'::float::text ---- --0 +0 query T SELECT '+.0'::float::text @@ -57,7 +57,7 @@ SELECT '+0.'::float::text query T SELECT '-0.'::float::text ---- --0 +0 query error invalid input syntax SELECT '++0'::float::text @@ -186,3 +186,112 @@ SELECT 'e'::float::text query error invalid input syntax SELECT 'e10'::float::text + +# Row packing canonicalizes floats so that byte equality of packed rows agrees +# with SQL float equality: -0.0 packs as +0.0 and every NaN packs as one bit +# pattern. Arrangement keys (joins, GROUP BY, DISTINCT) and index lookups +# compare packed bytes, so without this a stored -0.0 and +0.0 would be +# distinct keys even though `-0.0 = 0.0` is true. See SQL-452. +# Text formatting renders -0.0 as "0" as well (see the `'-0'::float::text` +# tests above), so text output does not depend on whether a value crossed a +# packing boundary. Negative zero is thereby unobservable in Materialize. +# This deviates from PostgreSQL, which prints "-0". + +statement ok +CREATE TABLE zeros (id int, f float8) + +statement ok +INSERT INTO zeros VALUES (1, '-0'::float8), (2, '0'::float8) + +# The stored -0.0 reads back as +0.0. +query T rowsort +SELECT f::text FROM zeros +---- +0 +0 + +query I +SELECT count(*) FROM (SELECT DISTINCT f FROM zeros) +---- +1 + +# One group, so a single count of 2. +query I +SELECT count(*) FROM zeros GROUP BY f +---- +2 + +query I +SELECT count(*) FROM (SELECT DISTINCT ON (f) f FROM zeros) +---- +1 + +query I +SELECT count(*) FROM (SELECT f FROM zeros UNION SELECT f FROM zeros) +---- +1 + +statement ok +CREATE TABLE zeros2 (id int, f float8) + +statement ok +INSERT INTO zeros2 VALUES (3, '0'::float8) + +query II +SELECT z1.id, z2.id FROM zeros z1 JOIN zeros2 z2 ON z1.f = z2.f ORDER BY z1.id +---- +1 3 +2 3 + +# NaN bit patterns: parsed NaNs and arithmetic NaNs are one value. +statement ok +CREATE TABLE nans (f float8) + +statement ok +INSERT INTO nans VALUES ('NaN'::float8), ('-NaN'::float8), ('inf'::float8 - 'inf'::float8) + +query I +SELECT count(*) FROM (SELECT DISTINCT f FROM nans) +---- +1 + +query T +SELECT DISTINCT f::text FROM nans +---- +NaN + +# float4 gets the same treatment. +statement ok +CREATE TABLE zeros_f4 (f float4) + +statement ok +INSERT INTO zeros_f4 VALUES ('-0'::float4), ('0'::float4), ('NaN'::float4) + +query I +SELECT count(*) FROM (SELECT DISTINCT f FROM zeros_f4) +---- +2 + +# The same query must render identically through a view (inlined, the text +# cast evaluates on unpacked datums) and a materialized view (the float is +# packed and persisted before the cast). +statement ok +CREATE VIEW v_negzero AS SELECT -1.0::float8 * 0.0 AS f + +statement ok +CREATE MATERIALIZED VIEW mv_negzero AS SELECT -1.0::float8 * 0.0 AS f + +query T +SELECT f::text FROM v_negzero +---- +0 + +query T +SELECT f::text FROM mv_negzero +---- +0 + +query T +SELECT (-1.0::float8 * 0.0)::text +---- +0 diff --git a/test/sqllogictest/funcs.slt b/test/sqllogictest/funcs.slt index 17c1c0fc5509d..a40d6a8068319 100644 --- a/test/sqllogictest/funcs.slt +++ b/test/sqllogictest/funcs.slt @@ -1240,10 +1240,13 @@ SELECT cot(0::double) ---- inf +# cot treats -0.0 as +0.0 so that the result does not depend on whether the +# input crossed a packing boundary (packing canonicalizes -0.0 to +0.0). +# PostgreSQL returns -Infinity here. query R SELECT cot(-0::double) ---- --inf +inf query R SELECT sin(1::double) @@ -1477,6 +1480,42 @@ SELECT ln(-1) query error function ln is not defined for zero SELECT ln(0) +# A float -0.0 input behaves exactly like +0.0 in ln, log10, and power, so +# that results (and error messages) do not depend on whether the input +# crossed a packing boundary (packing canonicalizes -0.0 to +0.0). + +query error function ln is not defined for zero +SELECT ln('-0'::double) + +query error function log10 is not defined for zero +SELECT log10('-0'::double) + +query R +SELECT power('-0'::double, 0.5) +---- +0 + +query R +SELECT power(0::double, '-0'::double) +---- +1 + +# NaN inputs propagate in ln and power regardless of the NaN's sign bit, +# matching PostgreSQL. +query R +SELECT ln('-NaN'::double) +---- +NaN + +query R +SELECT power('-NaN'::double, 0.5) +---- +NaN + +# A numeric negative zero (reachable via rounding) also errors like zero. +query error function log10 is not defined for zero +SELECT log10((-0.1)::decimal(10,0)) + query R SELECT ln(13.0000::decimal(15, 5)) ---- diff --git a/test/sqllogictest/transform/literal_constraints.slt b/test/sqllogictest/transform/literal_constraints.slt index 3f6b96eb59daa..022786e118974 100644 --- a/test/sqllogictest/transform/literal_constraints.slt +++ b/test/sqllogictest/transform/literal_constraints.slt @@ -1242,6 +1242,133 @@ SELECT * FROM t3 WHERE t3.c0 = 0.8::INT OR t3.c0 = -0.1; -0.1 1 +# Regression tests for SQL-452: `LiteralConstraints` packs the equality +# literal into a `Row` and the index lookup seeks that exact byte-encoded key. +# SQL float equality treats -0.0 = 0.0 (and NaN = NaN), so row packing must +# canonicalize the encodings that float equality conflates, otherwise a lookup +# for +0.0 misses a stored -0.0. +# NOTE: a genuine -0.0 needs a text->float cast (`'-0'::float8`), a `-0.0` +# literal goes through `numeric` (no signed zero) and arrives as +0.0. + +statement ok +CREATE TABLE t_neg_zero (id int, c0 float8) + +statement ok +CREATE INDEX t_neg_zero_i ON t_neg_zero (c0) + +statement ok +INSERT INTO t_neg_zero VALUES (1, '-0'::float8) + +# `-0.0 = 0.0` is true, so id=1 must be returned. +query I +SELECT id FROM t_neg_zero WHERE c0 = 0.0 +---- +1 + +# Control: without a matching index (a filter scan) the row is also returned. +statement ok +CREATE TABLE t_neg_zero_noidx (id int, c0 float8) + +statement ok +INSERT INTO t_neg_zero_noidx VALUES (1, '-0'::float8) + +query I +SELECT id FROM t_neg_zero_noidx WHERE c0 = 0.0 +---- +1 + +# Multiplicity (also covers float4): both zeros match `c0 = 0.0` and one +# lookup key must find both rows. +statement ok +CREATE TABLE t_both_zero (c0 float4) + +statement ok +CREATE INDEX t_both_zero_i ON t_both_zero (c0) + +statement ok +INSERT INTO t_both_zero VALUES ('0'::float4), ('-0'::float4) + +query I +SELECT count(*) FROM t_both_zero WHERE c0 = 0.0 +---- +2 + +# The mirror image: a -0.0 literal must find a stored +0.0. +statement ok +CREATE TABLE t_pos_zero (c0 float8) + +statement ok +CREATE INDEX t_pos_zero_i ON t_pos_zero (c0) + +statement ok +INSERT INTO t_pos_zero VALUES ('0'::float8) + +query I +SELECT count(*) FROM t_pos_zero WHERE c0 = '-0'::float8 +---- +1 + +statement ok +CREATE TABLE t_neg_zero_f4 (c0 float4) + +statement ok +CREATE INDEX t_neg_zero_f4_i ON t_neg_zero_f4 (c0) + +statement ok +INSERT INTO t_neg_zero_f4 VALUES ('-0'::float4) + +query I +SELECT count(*) FROM t_neg_zero_f4 WHERE c0 = 0.0 +---- +1 + +# NaN: SQL float equality treats NaN = NaN, but NaN bit patterns differ +# (parsed 'NaN' vs arithmetic results like inf - inf), so packing must +# canonicalize them too. +statement ok +CREATE TABLE t_nan (c0 float8) + +statement ok +CREATE INDEX t_nan_i ON t_nan (c0) + +statement ok +INSERT INTO t_nan VALUES ('inf'::float8 - 'inf'::float8) + +query I +SELECT count(*) FROM t_nan WHERE c0 = 'NaN'::float8 +---- +1 + +# Two equal literals that used to have distinct encodings must dedupe into a +# single lookup key. +query T multiline +EXPLAIN OPTIMIZED PLAN WITH(humanized expressions) AS VERBOSE TEXT FOR +SELECT count(*) FROM t_both_zero WHERE c0 = '0'::float4 OR c0 = '-0'::float4; +---- +Explained Query: + With + cte l0 = + Reduce aggregates=[count(*)] + Project () + ReadIndex on=materialize.public.t_both_zero t_both_zero_i=[lookup value=(0)] + Return + Union + Get l0 + Map (0) + Union + Negate + Project () + Get l0 + Constant + - () + +Used Indexes: + - materialize.public.t_both_zero_i (lookup) + +Target cluster: quickstart + +EOF + # Check the nullability- and unique key inference: # The `ReadIndex` should have # - non-nullable first 3 columns