Skip to content

fix(cubesql): preserve floating-point literal types in pushdown SQL - #11781

Open
davidda wants to merge 17 commits into
cube-js:masterfrom
davidda:feature/codex-float-literal-semantics
Open

davidda wants to merge 17 commits into
cube-js:masterfrom
davidda:feature/codex-float-literal-semantics

Conversation

@davidda

@davidda davidda commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Render Float32 and Float64 literals through dialect casts so integral-valued floats do not become integer arithmetic after serialization or constant folding. This follows existing decimal literal handling and preserves intentional integer division.

Explicit float typing can change source-inferred decimal arithmetic and comparisons and requires compatible dialect cast support. In PostgreSQL, pushed comparisons against NUMERIC/DECIMAL columns can coerce the column to floating point, changing precision-boundary results and preventing use of its numeric index for filtering. This trade-off preserves the planned Float32/Float64 type and needs maintainer agreement.

Check List

  • [ x ] Tests have been run in packages where changes have been made if available
  • [ x ] Linter has been run for changed code
  • [ x ] Tests for the changes have been added if not covered yet
  • [ x ] Docs have been added / updated if required

Issue Reference this PR resolves
#11780

Render Float32 and Float64 literals through dialect casts so integral-valued floats do not become integer arithmetic after serialization or constant folding. This follows existing decimal literal handling and preserves intentional integer division.

Explicit float typing can change source-inferred decimal arithmetic and requires compatible dialect cast support. Add renderer and planner regressions and update affected SQL expectations.

Validation: 821 compiler tests passed, 6 ignored; workspace formatting and all-targets Clippy passed. Cross-dialect end-to-end validation remains outstanding.
@davidda
davidda requested a review from a team as a code owner September 6, 2026 07:24
@github-actions github-actions Bot added rust Pull requests that update Rust code pr:community Contribution from Cube.js community members. labels Sep 6, 2026
@ovr

ovr commented Sep 8, 2026

Copy link
Copy Markdown
Member

/bot-deep-review

@claude

claude Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Claude finished @ovr's task in 7m 17s —— View job


Deep review complete — the fix is correct in principle, but the cast target isn't valid on every shipped dialect. 1 high, 1 medium, 2 low (4 inline comments).

Full review

Todos

  • Gather context (read diff, trace generate_sql_for_literal / sql_type / cast_expr)
  • Review wrapper.rs literal rendering changes
  • Cross-check types/float / types/double templates across all BaseQuery subclasses
  • Review test changes in compile/mod.rs and test_wrapper.rs
  • Check for stale prior review threads (none exist on this PR)
  • Post findings

What the change does

ScalarValue::Float32/Float64 literals used to render via format!("{f}"), which drops the decimal point for integral values — 100.0f64 became the token 100, and the target database then re-inferred integer semantics for the surrounding arithmetic. The PR routes them through sql_type + cast_expr instead, mirroring the existing Decimal128 arm.

The diagnosis is right and the test expectation changes confirm it. The clearest one is wrapper.rs-side:

- CEIL((CAST(EXTRACT(doy FROM …) AS INTEGER) / 7))
+ CEIL((CAST(EXTRACT(doy FROM …) AS INTEGER) / CAST(7 AS DOUBLE)))

integer / 7 is truncating division in Postgres, so CEIL was operating on an already-floored value — a genuine wrong-results bug in week-number computation, now fixed. This also composes correctly with the __int_division marker added earlier (wrapper.rs:688): that mechanism keeps real int/int division intact, and this change stops float/int division from masquerading as it.

Findings

# Severity Where Issue
1 High wrapper.rs:3118-3134 types/double defaults to the bare string DOUBLE; Oracle, Vertica and MySQL/MongoBI don't override it, and CAST(x AS DOUBLE) is invalid (Oracle, Vertica) or version-gated (MySQL < 8.0.17, MariaDB < 10.4) there. Missing/invalid template → DataFusionError → the query fails, with no fallback to non-pushdown.
2 Medium test_wrapper.rs:4097 APPROX_PERCENTILE(x, CAST(0.5 AS DOUBLE)) — percentile arguments must be constants on Presto/Trino/Athena/Snowflake/Databricks, the only engines that keep this function. MSSQL (the validated dialect) deletes it, so this line is unverified against anything that would execute it.
3 Low wrapper.rs:3103-3117 The two arms are byte-identical apart from the DataType; NaN/inf render as bare identifiers inside the cast (pre-existing, but the Decimal128 arm quotes its value and quoting would fix it).
4 Low mod.rs:15537 New test is missing the sql_push_down_enabled() guard used by the rest of the file; loop assertions don't identify the failing constant.

Finding 1 is the one that decides whether this is mergeable as-is. Details and two possible fix directions are in the inline comment; the short version is either gate float-literal pushdown on template availability in rules/wrapper/literal.rs (the way timestamp literals already are), or fill in types.double for the dialects that lack a valid one — or sidestep the whole dependency by formatting with {f:?}, which always emits 100.0 and needs no template at all.

Smaller notes (no inline comment)

  • wrapper.rs:4783-4813 — the new #[test] is inserted between use super::*; and the rest of the use block, splitting the imports around a function. Moving it below use crate::{…} reads better; rustfmt won't do it for you.
  • Member-expression SQL text changes. Five expectations in mod.rs changed shape (e.g. … > 10… > CAST(10 AS DOUBLE)). These strings are what gets pushed to Cube as member_expression.sql, so upgrading changes the generated SQL for queries users already run. Worth a release note, and worth confirming with the pre-aggregation folks whether member-expression SQL participates in matching/caching keys — if it does, this forces rebuilds.
  • Comment quality — the two-line comment on the Float64 arm is load-bearing (it's exactly the reason nobody should "simplify" this back to format!("{f}")) and stays within budget. Good. It should apply to the Float32 arm too, which is another argument for the shared helper in finding 3.
  • No security or performance concerns: no user input reaches the template name, the cast is constant-folded by every target engine, and the extra allocations per literal are negligible.

Validation

Reviewed statically only — I did not build or run the Rust test suite in this job (cargo toolchain setup for the cubesql workspace is out of scope for the review runner). The author reports 821 compiler tests passing plus cargo fmt / clippy --all-targets clean, and the small number of changed expectations is consistent with that. The dialect-template claims in finding 1 were verified by reading packages/cubejs-schema-compiler/src/adapter/* directly, not from memory.

· branch [`feature/codex-float-literal-semantics`](https://github.com/cube-js/cube/tree/feature/codex-float-literal-semantics)

Comment thread rust/cubesql/cubesql/src/compile/engine/df/wrapper.rs
Comment thread rust/cubesql/cubesql/src/compile/test/test_wrapper.rs
Comment thread rust/cubesql/cubesql/src/compile/engine/df/wrapper.rs
Comment thread rust/cubesql/cubesql/src/compile/mod.rs
@davidda
davidda requested review from a team as code owners September 9, 2026 13:07
@github-actions github-actions Bot added the javascript Pull requests that update Javascript code label Sep 9, 2026
@davidda

davidda commented Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the review comments. I've added a provider test which I ran on all platforms I could easily setup in my docker: MySQL 8.0.22
MySQL 5.6.51
PostgreSQL 16 — also verified percentile casts
MSSQL
Oracle
ClickHouse
CrateDB

@MazterQyou

Copy link
Copy Markdown
Member

/bot-deep-review

@claude

claude Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Claude finished @MazterQyou's task in 10m 55s —— View job


Deep review complete — the template gate and the MySQL/Oracle work hold up; the Vertica fix is on the wrong class. 1 high, 3 low (4 inline comments), 1 prior thread resolved.

Full review

Todos

  • Fetch base and read the updated diff
  • Re-check the four prior findings against the new code
  • Review new template-availability guards (literal.rs, service.rs)
  • Review dialect template additions (MySQL / Oracle / Vertica)
  • Review new tests (float-literal-template.test.ts, testQueries.ts, mod.rs, wrapper.rs)
  • Check CI, resolve stale threads, post findings

Prior round

Three of the four threads were addressed and are already resolved. The percentile one I've now resolved too — a CAST of a literal is a constant expression, and you verified PERCENTILE_CONT(CAST(0.5 AS DOUBLE PRECISION)) executing on Postgres under both planners, which is an engine that actually keeps the function. Snowflake stays untested, but I don't think that's worth holding the PR for.

The restructuring is a clear improvement over the first round:

  • float_literal_expr (service.rs:919) collapses the duplicated arms into one place and gives dialects an escape hatch that doesn't need CAST at all.
  • literal.rs:93-110 gates float-literal pushdown on template availability, matching the expressions/timestamp_literal precedent. Worth noting the expressions/cast half of supports_float_literal is always true — no dialect anywhere overrides or deletes expressions.cast — so the gate is really "does this dialect have types/float (or types/double), or its own float_literal". That's the right condition; it just means the gate only saves you from dialects that delete the type templates, not from ones whose value is wrong. Which is finding 1.
  • NaN/infinity excluded from pushdown rather than emitted as bare identifiers. Strictly better than either the old bare NaN or a dialect-specific string cast.
  • MySQL exponent literals are a neat answer to the 8.0.17 CAST floor, and (NULL + 0e0) for the NULL branch is a genuinely clever way to keep a typed NULL floating-point without the cast. Validating on 5.6.51 was the right call.
  • Oracle BINARY_FLOAT/BINARY_DOUBLE is correct, and it also fixes the pre-existing typed-NULL/explicit-CAST paths that were emitting ORA-00902.
  • test_mysql_float_literal_without_cast asserting parse::<f64>().to_bits() == expected.to_bits() over f32::MAX, f64::MAX and subnormals is the right way to test a formatter. {:e} is shortest-round-trip in Rust, and this proves it rather than assuming it.

Findings

# Severity Where Issue
1 High VerticaQuery.ts:17 Two VerticaQuery classes exist; VerticaDriver.dialectClass() returns the driver-package one, which queryClass() prefers over ADAPTERS.vertica. The override never runs, so shipped Vertica keeps types.double = 'DOUBLE' — a type Vertica doesn't have — and every float literal in a pushdown expression now fails at the source where a bare 100 used to work. Regression, and the literal.rs gate can't see it because the template exists with a wrong value.
2 Low testQueries.ts:2832 Runs on all 14 driver suites (unskipped) but validated on six; the float64_null/float32_null columns are source-independent and pass whether or not the cast reaches the source.
3 Low service.rs:930 The template path formats with {:e} on the widened f64; the cast path uses Display on the narrowed f32. Same input, two rules, unexplained — and Display expands 1e300 to 301 characters.
4 Low float-literal-template.test.ts:18 Six hardcoded dialect names where allDialects() exists to make the invariant total; and its scan misses the eight driver-package query classes, which is the blind spot behind finding 1.

Only finding 1 needs to happen before merge. It's a one-line move plus a test that points at the class the runtime actually uses.

Dialect sweep

I re-checked types.double / types.float for every query class, including the eight that live in driver packages (allDialects() doesn't see those):

effective types.double dialects verdict
overridden by this PR or earlier postgres (+redshift, crate, materialize), mssql, bigquery, oracle, presto (+trino, athena)
own float_literal, cast bypassed mysql, mongobi
inherits DOUBLE, valid there clickhouse, snowflake, databricks, hive, sqlite, duckdb, druid, ksql, pinot, questdb, firebolt, dremio, elasticsearch
inherits DOUBLE, invalid vertica (driver-package class) ✗ finding 1

types.float is fine everywhere — base FLOAT is valid in every remaining dialect, including Vertica (where it's an 8-byte synonym, so Float32 literals silently widen; harmless).

Smaller notes (no inline comment)

  • wrapper.rs:3110-3111 — the "Display formats integral floats without a decimal point" comment now sits at the call site while the formatting it describes moved into float_literal_expr, where a second comment covers the same ground. It's still load-bearing where it is (it's why nobody reverts to format!("{f}")), but it's on one of two otherwise-identical arms, and the reason now has two homes.
  • Release note. The decimal→double change is user-visible beyond the bug being fixed. 0.1 * SUM(x) on Postgres/MSSQL/Oracle/MySQL used to compute in exact NUMERIC; it now computes in double. This makes pushdown agree with what DataFusion would have done locally, so it's the defensible direction — but results change for anyone doing decimal math through the SQL API, and at the boundary a high-precision NUMERIC column compared against CAST(10 AS DOUBLE PRECISION) can flip a filter that used to be exact. Worth calling out explicitly in the changelog, not just in the PR description.
  • Member-expression SQL text. Re-affirming the note from the last round: the five changed expectations in mod.rs are strings pushed to Cube as member_expression.sql, so upgrading changes generated SQL for existing queries. Still worth confirming with the pre-aggregation folks whether that text participates in matching or cache keys — if it does, this forces rebuilds.
  • No security or performance concerns. No user input reaches a template name; the casts constant-fold on every target engine; the extra allocation per literal is noise.

Validation

Static review plus source verification — no build or test run in this job. The runtime class-resolution claim in finding 1 was traced through OptsHandler.ts:393-396CompilerApi.ts:303-316QueryBuilder.ts:42, and the driver-side dialectClass() read directly in VerticaDriver.js:40-42; the dialect sweep was read off each adapter and driver package rather than recalled.

CI has produced no signal: all 13 workflow runs on this PR are action_required (community PR pending maintainer approval), so Drivers tests, Build and the cubesql suite have not executed. Your local run of 821 compiler tests is the only evidence they pass, and the eight unvalidated driver suites in finding 2 have never run this test.

· branch [`feature/codex-float-literal-semantics`](https://github.com/cube-js/cube/tree/feature/codex-float-literal-semantics)

Comment thread packages/cubejs-schema-compiler/src/adapter/VerticaQuery.ts
Comment thread rust/cubesql/cubesql/src/transport/service.rs
Comment thread packages/cubejs-testing-drivers/src/tests/testQueries.ts
@MazterQyou

Copy link
Copy Markdown
Member

/bot-deep-review

@claude

claude Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Claude finished @MazterQyou's task in 10m 6s —— View job


Deep review complete — the Vertica fix lands on the right class this time; what's left is where these type names get replayed and what CI actually runs. 2 medium, 1 low (3 inline), 2 prior threads re-affirmed.

Full review

Todos

  • Fetch base and read the updated diff
  • Re-check prior findings against current code (esp. the Vertica high)
  • Review new driver-owned VerticaQuery.js + its test
  • Re-verify runtime class resolution and Tesseract impact
  • Check CI, list/resolve stale threads, dedupe
  • Post findings

Prior round

The high finding is genuinely fixed, and I verified the resolution path rather than taking it on trust: VerticaDriver.dialectClass() is static at VerticaDriver.js:40-42 and returns the driver-local VerticaQuery, which now overrides sqlTemplates() (VerticaQuery.js:27-31). queryClass() prefers that over ADAPTERS.vertica, so DOUBLE PRECISION is what a real Vertica deployment gets. The new assertion goes through dialectClass() instead of the class name, which is the right shape for the test — see the medium below about where it runs.

The three low threads from last round: one you declined with a reason I accept (an existence sweep wouldn't have caught an invalid type name, which was the actual bug), two are still open and still apply — I have not re-posted them:

  • service.rs:930{:e} on the template path vs Display on the cast path, two rules for the same f64.
  • testQueries.ts:2832 — runs on all 14 driver suites, validated on six; the two NULL columns assert nothing about pushdown.

Findings

# Severity Where Issue
1 Medium VerticaQuery.test.js:13 The Vertica regression guard runs in no CI job. The package has no unit script, so lerna run unit (push.yml:137) skips it, and the only script that reaches it — integration:vertica — is commented out of the drivers matrix because the vertica-ce image vanished from Docker Hub. The fix that took two rounds to land is protected by a test only your laptop runs.
2 Medium OracleQuery.ts:284 BINARY_DOUBLE is correct Oracle, but member-expression SQL is source-dialect text that gets replayed against Cube Store when a rollup matches — and Cube Store's DataFusion 46 planner doesn't know BINARY_DOUBLE/BINARY_FLOAT. Rollup matching resolves expression members to underlying members (PreAggregations.ts:849,861), so an expression doesn't block the match. Plausible, not confirmed — I couldn't run Oracle + a rollup.
3 Low literal.rs:93 Nothing exercises the gate's false branch: no dialect deletes these templates and the harness can't remove them, so the fallback mechanism added this round is asserted by nothing. The NaN/infinity half is testable today.

Finding 1 is cheap and I'd do it before merge — a one-line "unit" script. Finding 2 is a question to answer rather than a change to make, but it's worth answering before this ships, because Oracle is one of the dialects you hand-validated and the pre-aggregation path is exactly the one your provider test deliberately avoids.

What I re-verified this round

  • Tesseract is unaffected. cubesqlplanner renders only types/string (planner/sql_templates/plan.rs:314) — no other types/* template is read anywhere in the Rust planner, so the Oracle and Vertica additions don't change Tesseract-generated SQL despite it being the default planner.
  • Driver-package dialects. Vertica was the only one with an invalid effective types.double; the other eight (Databricks, Druid, DuckDB, Firebolt, Ksql, Pinot, Quest) inherit DOUBLE, valid in each. One caveat I'd note without making a finding of it: ksqlDB has no FLOAT type at all, so a Float32 literal would render CAST(x AS FLOAT) there. Float64 (the common case — DataFusion parses bare decimals as Float64) maps to DOUBLE and is fine, and the FLOAT exposure predates this PR via typed NULLs, so it's narrow. Still the one remaining place where the effective template is wrong rather than absent, and the gate can't see it.
  • MongoBI inherits the MySQL float_literal. 1e2 and (NULL + 0e0) against the MongoDB BI connector — the one MySQL-wire target that isn't MySQL — is unverified, and mongobi isn't among the cubejs-testing-drivers fixtures, so no CI job will cover it either.

Smaller notes (no inline comment)

  • The Vertica override now exists in two classes with nothing tying them together, and the split is unfortunate in one specific way: float-literal-template.test.ts asserts the shadowed schema-compiler copy while the driver copy is what ships. You declined inheritance changes, which is fine, but that means the green assertion is on the class that can't break a deployment, and finding 1 is about the other one not running.
  • wrapper.rs:3110 — "Display formats integral floats without a decimal point" now sits at a call site that formats nothing (the formatting moved into float_literal_expr, which carries its own comment), and it's on one of two otherwise-identical arms. Still load-bearing where it is, just no longer describing code beneath it.
  • Release note. Third time raising it, so I'll keep it to one line: on Postgres/MSSQL/Oracle/MySQL, 0.1 * SUM(x) moves from exact NUMERIC to double, and a high-precision NUMERIC column compared against CAST(10 AS DOUBLE PRECISION) can flip a filter that used to be exact. Defensible direction, user-visible change, belongs in the changelog.
  • No security or performance concerns. No user input reaches a template name; casts constant-fold at the source; the extra allocation per literal is noise.

Validation

Static review plus source verification; no build or test run in this job. The Cube Store claim in finding 2 was traced through PreAggregations.transformQueryToCanUseFormcanUsePreAggregationForTransformedQueryFn (ownedDimensions/leafMeasures, not expr: paths) and the Cube Store dependency pins in rust/cubestore/cubestore/Cargo.toml; the CI claim in finding 1 was read off push.yml:137 and push.yml:413-420 plus the package's own scripts block.

CI still gives no signal: all 9 workflow runs on this PR are action_required (community PR pending maintainer approval), so Drivers tests, Build native, Build and the cubesql suite have not executed. Your local run remains the only evidence the Rust tests pass.

· branch [`feature/codex-float-literal-semantics`](https://github.com/cube-js/cube/tree/feature/codex-float-literal-semantics)

@MazterQyou

Copy link
Copy Markdown
Member

/bot-deep-review

@claude

claude Bot commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Claude finished @MazterQyou's task in 17m 36s —— View job


Deep review complete — approach is sound; 3 findings (1 medium, 2 low) + 3 red CI checks to triage

Full review
  • Gather context (PR diff, linked issue SQL API pushdown drops floating-point literal types #11780)
  • Review Rust changes (wrapper.rs, literal.rs, service.rs)
  • Review JS/TS changes (dialect templates, tests)
  • Check dialect coverage for types/float / types/double validity
  • Investigate red CI checks
  • Reconcile prior review threads
  • Post findings

Findings

# Severity Where Concern
1 Medium rust/cubesql/cubesql/src/transport/service.rs:937 NULL float + a float_literal override that doesn't branch on value is none pushes down the bare word none
2 Low packages/cubejs-schema-compiler/test/unit/float-literal-template.test.ts:16 KsqlQuery inherits types.float = 'FLOAT', a type ksqlDB does not have; the 4-dialect list cannot catch it
3 Low packages/cubejs-testing-drivers/src/tests/testQueries.ts:2849 New provider test runs unskipped on all 14 suites × 2 planners, asserting rendered SQL on 8 unvalidated dialects

Each is an inline comment with the concrete failing input.

CI

Check fmt/clippy and Build (JS unit, incl. the two new JS test files) are green. Red on head commit 2354a2c:

  • Unit (Rewrite Engine)cargo insta test --all-features --workspace --unreferenced reject. Since fmt/clippy compiles --all-targets, this is a failing assertion, not a build break.
  • tests (mssql, true) and tests (questdb, true) — Tesseract-mode driver suites; the rest of the Drivers tests run was cancelled behind them.
  • Build windows-2022 24.x with Python fallback at the Tests step — no float literals in packages/cubejs-backend-native/test, so probably unrelated.

I could not read any of those job logs from this runner (the log paths sit outside the workspace and gh run view --log-failed / gh api .../logs are not permitted here), so I can't attribute them. Finding 3 is the first thing to rule out for the two provider suites.

What holds up

  • The fix matches the symptom in SQL API pushdown drops floating-point literal types #11780: CEIL(CAST(EXTRACT(doy ...) AS INTEGER) / CAST(7 AS DOUBLE)) at mod.rs:7830 is exactly the integer-division truncation the issue reports, and CAST(100 AS DOUBLE) restores the percentage arithmetic. It follows the Decimal128 arm's existing shape.
  • Gating on contains_sql_type() + expressions/cast is the right coupling — the gate and float_literal_expr now read the same types/* mapping through sql_type_template(), so they cannot drift.
  • Excluding NaN/±Inf is a strict improvement: those previously rendered as bare NaN/inf identifiers and failed at the source with no fallback.
  • types/nullable on the NULL path is correct and covered — CAST(NULL AS Nullable(FLOAT)) for ClickHouse.
  • The Float32 round trip is exact (f64::from(f32) as f32), and {:e} is Rust's shortest-round-trip exponent form, so the MySQL path preserves the widened Float32 bit pattern. test_mysql_float_literal_without_cast pins that with to_bits() over f32::MAX, f64::MAX and subnormals — good test.
  • Dialect audit of CAST targets: valid in BigQuery (FLOAT64), MSSQL (FLOAT(24|53)), Postgres/Redshift/Crate (REAL/DOUBLE PRECISION), Oracle (BINARY_*, added here), Vertica (added here, in both the compiler-owned and driver-owned class), Presto/Trino/Athena, ClickHouse, Snowflake, Databricks, Hive, SQLite, DuckDB, Druid, Firebolt, Pinot, QuestDB, CubeStore. ksqlDB is the one exception (finding 2).
  • Comments are all within three lines and each carries a reason the code cannot; no comment findings.

One note, not a finding

The cast applies to every float literal, including ones that never lost their type — 100.1, 0.5, 1.1, 0.025 already render with a decimal point under Display. Restricting it to values whose rendering has no decimal point would fix #11780 with a much smaller blast radius: PERCENTILE_CONT(0.5) would stay a bare literal (relevant for the engines flagged unverified in the test_wrapper.rs:4190 thread), MySQL would need no exponent override for fractional values, and pushed-down filter comparisons would not gain a float cast that makes them non-SARGable against a numeric source column. The issue states the broader behaviour is intended ("It applies to all float values, including 0.5") and it has maintainer sign-off, so this is context for that decision rather than a request to change it.

No prior review threads needed resolving — all 24 are already resolved, and none duplicate the findings above.

· `feature/codex-float-literal-semantics`

Comment thread rust/cubesql/cubesql/src/transport/service.rs
Comment thread packages/cubejs-testing-drivers/src/tests/testQueries.ts
Cover the runtime dialect through KsqlDriver.dialectClass(). Scope the float provider test out of QuestDB Tesseract after reproducing its parameter-finalization panic with an integer-only query; retain legacy coverage and all SQL assertions.
@MazterQyou

Copy link
Copy Markdown
Member

/bot-deep-review

@claude

claude Bot commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Claude finished @MazterQyou's task in 19m 37s —— View job


Deep review: 2 findings (1 medium, 1 low) — plus a note on the red Unit (Rewrite Engine) job, which looks like a fork-secrets artifact rather than this PR

Full review
  • Gather context (PR diff, linked issue SQL API pushdown drops floating-point literal types #11780)
  • Review Rust changes (wrapper.rs, literal.rs, service.rs)
  • Review JS/TS dialect template changes (MySQL, Oracle, Vertica ×2, ksqlDB)
  • Review test coverage (Rust unit + gate tests, JS unit, driver fixtures)
  • Audit prior review threads for duplicates / staleness
  • Post findings

Findings

# Severity Location Concern
1 medium rust/cubesql/cubesql/src/compile/rewrite/rules/wrapper/literal.rs:95 DataSource::Unrestricted makes the new gate return true without reading a template, so a dialect lacking types/float (ksqlDB, after this PR) hard-errors instead of falling back to local evaluation
2 low packages/cubejs-testing-drivers/fixtures/questdb.json:236 The new tesseractSkip documents an unchecked-index panic at wrapper.rs:268 with nothing tracking it, so the skip has no removal condition

Both posted inline. No prior threads were re-affirmed or resolved: all 27 review threads on this PR are already resolved, and neither finding duplicates one.

One candidate finding was dropped rather than posted: float_literal_expr ignores data_type on the expressions/float_literal path, so MySQL/MongoBI get double-precision exponent literals for Float32. That is an explicitly documented decision (transport/service.rs:938-940) already settled in two earlier threads, and I have no dialect where the single-precision difference is observable — restating it would not be actionable.

CI

Unit (Rewrite Engine) (job 105509903736) is red on the head commit. Its log isn't reachable from this sandbox, so this is a hypothesis rather than a conclusion: the step runs cargo insta test --all-features --workspace --unreferenced reject, this is a fork PR (davidda/cube), so CUBESQL_TESTING_CUBE_TOKEN / CUBESQL_TESTING_CUBE_URL are empty, PostgresIntegrationTestSuite::before_all returns Skipped (e2e/tests/postgres.rs:59), and the 9 snapshots under e2e/tests/snapshots/ are then never referenced — which --unreferenced reject fails on. That would be PR-independent. Worth confirming against another community PR before spending time on it.

The other red check, unit (26.x, 3.13), failed in its Yarn install step — infrastructure, not the diff.

I could not run the Rust suite or the JS unit tests here (cargo and cargo fetch are blocked in this environment, and the CI log endpoints are unreadable), so everything above is static review plus CI metadata.

What holds up

The core of the change is right, and the parts most likely to be wrong are the parts that were checked hardest:

  • The gate (literal.rs:94-116) and the renderer (service.rs:932-963) now share one type-availability lookup via contains_sql_type/sql_type_template, so "can push" and "can render" cannot drift apart — this was the right fix for the earlier hardcoded template names.
  • Deleting templates.types.float for ksqlDB is safe beyond the literal path: the only other Float32 renderer is Expr::Cast, and transform_cast_expr already gates on contains_sql_type (rules/wrapper/cast.rs:76-80), so a user-written CAST(x AS REAL) falls back locally rather than erroring. generate_typed_null is only reached for types ksqlDB keeps.
  • NULL floats now go through nullable_type() on the cast path, matching generate_typed_null, and the types/nullable case is covered by the ClickHouse-shaped generator in wrapper.rs's new test.
  • Oracle (BINARY_FLOAT/BINARY_DOUBLE) and Vertica (DOUBLE PRECISION) are strict fixes — neither engine has a bare DOUBLE type, so those casts were already wrong for decimal literals and typed NULLs. Fixing both VerticaQuery copies, with the driver-owned one pinned through VerticaDriver.dialectClass(), is the right call given only that copy is live at runtime.
  • Blocking NaN/infinity at the gate replaces SQL that no dialect could parse with a local fallback.
  • Every dialect that keeps the FLOAT/DOUBLE base defaults accepts them as cast targets (BigQuery overrides to FLOAT64, Presto/Trino to REAL, Postgres/Redshift to REAL/DOUBLE PRECISION, MSSQL to FLOAT(24)/FLOAT(53); ClickHouse, Databricks, Druid, DuckDB, Hive, Pinot, QuestDB, Snowflake, SQLite, CubeStore and Crate all take the defaults). Oracle, Vertica and ksqlDB were the three gaps, and all three are closed here.

Rollout note

This is in cubesql's push-down layer, so it applies to both planners equally — Tesseract has no float-literal renderer of its own (types/float/types/double appear only in its test mocks).

Two consequences worth stating in the release notes, both already acknowledged in the PR body:

  • Arithmetic against a source-decimal column changes. SELECT 1.0 * SUM(price) / COUNT(*) previously pushed 1 * SUM(price) and PostgreSQL evaluated it in numeric; it now pushes CAST(1 AS DOUBLE PRECISION) * SUM(price) and evaluates in double precision. That is the intended fix (it is what DataFusion's plan says), but a deployment over money columns will see values move in the last digits.
  • Pushed member-expression SQL changes for any query containing a float literal, so the first run after upgrade misses the query cache.
· branch [`feature/codex-float-literal-semantics`](https://github.com/davidda/cube/tree/feature/codex-float-literal-semantics)

Comment thread rust/cubesql/cubesql/src/compile/rewrite/rules/wrapper/literal.rs Outdated
Comment thread packages/cubejs-testing-drivers/fixtures/questdb.json
Require float support across available generators for unrestricted contexts. Apply the same check to literal scan members, which bypass expression rewrite gates, so missing float types retain local evaluation. Cover both widths with the LIMIT 0 aggregate reproducer and extend the existing gate matrix to unrestricted contexts.
@MazterQyou

Copy link
Copy Markdown
Member

/bot-deep-review

@claude

claude Bot commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Claude finished @MazterQyou's task in 15m 43s —— View job


Deep review — looks good to merge; 3 low-severity findings (0 high, 0 medium, 3 low), all test/comment quality

Review details
  • Fetch base, read the full diff
  • Read issue SQL API pushdown drops floating-point literal types #11780 for the reported symptom
  • Review Rust renderer + gate (wrapper.rs, literal.rs, service.rs, cube_scan_wrapper.rs)
  • Review JS dialect templates (MySQL/Oracle/Vertica/Ksql) and tests
  • Audit every dialect's types.float/types.double for cast validity
  • Review driver integration test + QuestDB fixture skip
  • Check prior review threads (resolve stale / dedupe)
  • Post inline findings + summary

Findings posted inline

# File Concern
1 rust/cubesql/cubesql/src/compile/rewrite/rules/wrapper/literal.rs:312 test_float_literal_gate cannot distinguish the .all(...) added in dcf47b1 from .any(...) — the test tenant has one data source
2 rust/cubesql/cubesql/src/compile/rewrite/rules/wrapper/literal.rs:132 NaN/infinity comment sits over a delegating arm; the is_finite() guard it explains is at :87/:91
3 packages/cubejs-schema-compiler/test/unit/float-literal-template.test.ts:12 Verbatim template-string assertion duplicates the implementation instead of checking the NULL contract the Rust gate relies on

What I verified and found sound

Gate/renderer agreement. SQL generation happens during planning (query_engine.rs:312evaluate_wrapped_sql), and an error there propagates — there is no fallback once a wrapper has formed. So the gate must be exactly as strict as the renderer, and it is: contains_template("expressions/float_literal") || (contains_sql_type(&data_type) && contains_template("expressions/cast")) covers every template float_literal_expr reaches, with nullable_type() self-defaulting when types/nullable is absent (service.rs:1100). DataType::Float32 → types/float and Float64 → types/double line up with sql_type_template (service.rs:1119).

Second gate site. cube_scan_wrapper.rs:188 is the right place for the literal-member path — filtering data_sources_out to empty makes the closure return vec![], so the scan stays unwrapped rather than reaching a renderer that would hard-error. The &egraph[...].data.member_name_to_expr borrow ends before the egraph.add calls below it.

Dialect cast targets. Walked every *Query for types.float/types.double: BigQuery FLOAT64, MSSQL FLOAT(24)/FLOAT(53), Postgres/Redshift/Crate REAL/DOUBLE PRECISION, Presto/Athena/Trino REAL/DOUBLE, plus the four this PR fixes. The FLOAT/DOUBLE defaults the rest inherit are valid cast targets on ClickHouse, Snowflake, Databricks, DuckDB, Druid, Pinot, QuestDB and Firebolt. ksqlDB is the one dialect with no 4-byte float, and delete templates.types.float is the right lever — cast.rs:80 already gates the explicit-cast rule on contains_sql_type, so CAST(x AS REAL) now falls back locally instead of emitting invalid SQL. types/float has no other consumer (nothing in BaseQuery.js or Tesseract's sql_templates/plan.rs reads it).

Pre-aggregations. The Oracle thread's answer generalises: sql-server.ts:241 sets disableExternalPreAggregations: true for every member-expression query, so dialect-specific spellings like FLOAT(53) never reach Cube Store.

Behaviour changes are the intended ones. The two mod.rs expectations worth naming: CAST(EXTRACT(doy ...) AS INTEGER) / CAST(7 AS DOUBLE) (mod.rs:7827) turns what was integer division into float division, and pivot_grouping becomes CAST(0 AS DOUBLE) (mod.rs:7284). Both follow from DataFusion having planned Float64 all along, which is the bug in #11780; the first is a visible result change for anyone relying on the old truncation, as the PR body says.

Gate hardening is a net improvement beyond the reported symptom. Float64(f64::INFINITY) used to render as the bare identifier inf and NaN as NaN; the finite check blocks both from pushdown rather than emitting unparseable SQL.

Notes, not findings

  • The provider test is unverified in CI — the "Drivers tests" run on the head commit was cancelled, and @davidda's local validation covered MySQL 8/5.6, PostgreSQL 16, MSSQL, Oracle, ClickHouse and CrateDB. BigQuery, Snowflake, Athena, Trino, Databricks, Redshift and Pinot have not run SQL API: floating-point literals preserve arithmetic in pushdown. Two of its assertions are the ones I would expect to be dialect-sensitive: CAST(NULL AS [\w ()]+) requiring exactly 2 matches, and Number(rows[0].integer_ratio) requiring 0 (safe only because int_division is overridden per dialect — BaseQuery's / truncates on the remaining engines). Worth a green Drivers run before merge rather than another local sweep.
  • rust/cubesql/cubesql/src/compile/rewrite/rules/wrapper/literal.rs:96 spells crate::transport::SqlTemplates inline where line 14 already imports DataSource, MetaContext from that module.
  • The QuestDB fixture skip follows the established pseudo-comment convention in fixtures/*.json, and the new unit script in cubejs-vertica-driver/package.json is picked up by yarn lerna run unit (push.yml:137).

Review threads

The listing returned 29 of my own threads, all already resolved — nothing unresolved to resolve or re-affirm this round. Four of my findings duplicate resolved threads carrying the author's deferral and are not re-posted: the cast path losing negative zero (-0.0CAST(-0 AS ...)0), the percentile-argument risk on engines requiring a literal rather than a foldable fraction (Snowflake, Databricks, DuckDB, Firebolt), the dropped error context at wrapper.rs:3183, and the provider-validation scope noted above.

· branch [`feature/codex-float-literal-semantics`](https://github.com/cube-js/cube/tree/feature/codex-float-literal-semantics)

Comment thread rust/cubesql/cubesql/src/compile/rewrite/rules/wrapper/literal.rs Outdated
Comment thread rust/cubesql/cubesql/src/compile/rewrite/rules/wrapper/literal.rs Outdated
Exercise supported, unsupported, and unrestricted contexts in the existing float gate matrix, and identify the source in assertion failures. Move the non-finite literal explanation next to the helper checks it describes.
@MazterQyou

Copy link
Copy Markdown
Member

/bot-deep-review

@claude

claude Bot commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

Claude finished @MazterQyou's task in 16m 58s —— View job


Deep review done — 3 findings posted inline (1 medium, 2 low); CI is red on the head commit

Review detail

Todo

  • Gather context (diff, issue SQL API pushdown drops floating-point literal types #11780, changed files)
  • Review Rust renderer + gates (wrapper.rs, literal.rs, service.rs, cube_scan_wrapper.rs)
  • Review JS dialect templates (MySQL/Oracle/Vertica/Ksql) and their CI wiring
  • Review tests (Rust + JS + driver integration)
  • Check CI state on head commit
  • Dedupe against prior review threads
  • Post inline comments + summary

Findings

# Severity Where Concern
1 medium rust/cubesql/cubesql/src/compile/mod.rs:12273 Casting every float literal, not just the integral-valued ones that actually lose their decimal point, retypes ordinary comparisons: ${dim} > 10${dim} > CAST(10 AS DOUBLE PRECISION) coerces a Postgres numeric column to float8, losing exactness and btree index usability
2 low rust/cubesql/cubesql/src/transport/service.rs:949 float_literal_expr has no non-finite guard; inf/NaN would emit as bare SQL identifiers. Unreachable today only because two separate rewrite gates hold the invariant
3 low rust/cubesql/cubesql/src/compile/test/test_wrapper.rs:125 test_float_literal_member_pushdown_fallback never asserts the rendered cast, so a renderer regression on the literal-member path dcf47b1 added passes

No duplicate threads to skip — all 32 prior review threads on this PR are already resolved, and none of mine were left stale.

CI

Build nativeUnit (Rewrite Engine) fails on 2754dc1: job 105865989611. Check fmt/clippy and all the native build matrix jobs pass; Drivers tests was cancelled. I could not read the job log or run cargo test in this environment (both blocked), so I can't say whether it is a test assertion or cargo insta test --unreferenced reject rejecting a snapshot that stopped being referenced. Worth confirming before merge — that job runs with CUBESQL_SQL_PUSH_DOWN=true, which is exactly the configuration this PR changes.

Verified as correct

  • Gate/renderer agreement. The gate checks contains_sql_type(&data_type) + expressions/cast, the renderer calls sql_type(data_type) + cast_expr — same types/float / types/double lookup via sql_type_template, so a gate pass cannot turn into a render-time DataFusionError. types/nullable is correctly not gated, since nullable_type passes the type through when absent.
  • All three literal render paths are gated. Expression literals (transform_literal), literal scan members rendered when a scan is wrapped (cube_scan_wrapper.rs:188), and the ungrouped push-to-cube column→literal path at wrapper.rs:2902 — the last one reads from the same CubeScan member_fields the new filter covers, so it is not a fourth hole.
  • Break(true) strictness is sound and does not regress single-source deployments. Requiring every generator to support the literal in an unrestricted context is stricter than the sibling can_rewrite_template / transform_cast_expr gates, which still return true there. With one data source all() over one entry is the same verdict, so the cost lands only on mixed-source deployments — the right side to err on.
  • ksqlDB fallback is safe, not a hard error. transform_cast_expr already gates on contains_sql_type, so deleting types.float makes CAST(col AS REAL) fall back to local evaluation rather than failing at SQL generation. Mapping it to DOUBLE instead would have kept pushdown but silently stopped CAST(col AS REAL) truncating precision — deleting is the correct trade.
  • New JS tests actually run in CI. yarn lerna run unit picks up the added unit script in cubejs-vertica-driver (jest is in devDependencies, the config has no Docker setup) and the pre-existing one in cubejs-ksql-driver; cubejs-schema-compiler runs dist/test/unit, which allDialects.ts already resolves against.
  • No injection surface. Everything reaching the template is f64 formatted by Display/LowerExp; non-finite is the only shape that escapes digits/./-/e, and it is gated.
  • Side benefit worth keeping. GROUP BY 100 / ORDER BY 1 ordinal ambiguity disappears — an integral float in those positions used to render as a bare integer that MySQL and Postgres read as a column position.
  • Comment discipline. Every added comment is ≤3 lines and load-bearing; the stale wrapper comment from an earlier round is gone.

Residual risk (not re-raised inline)

test_wrapper.rs:4220 now expects APPROX_PERCENTILE(${...}, CAST(0.5 AS DOUBLE)). The prior thread on this is resolved with Postgres verified and Snowflake / the approximate-percentile engines explicitly unverified. Trino/Athena and Databricks reach it through templates.functions.APPROXPERCENTILECONT, and those suites are in cubejs-testing-drivers — a driver-tests run on this branch would settle it, and the one on this commit was cancelled.

· branch `feature/codex-float-literal-semantics`

Comment thread rust/cubesql/cubesql/src/compile/mod.rs
Comment thread rust/cubesql/cubesql/src/transport/service.rs
Comment thread rust/cubesql/cubesql/src/compile/test/test_wrapper.rs Outdated
@MazterQyou

Copy link
Copy Markdown
Member

/bot-deep-review

@claude

claude Bot commented Sep 22, 2026

Copy link
Copy Markdown
Contributor

Claude finished @MazterQyou's task in 22m 37s —— View job


Deep review — PR #11781 — 1 new finding (medium), 5 prior threads re-affirmed; CI is red on 83c90ba

Review details
  • Gather context (PR diff, linked issue SQL API pushdown drops floating-point literal types #11780)
  • Review Rust changes (wrapper renderer, literal gate, SqlTemplates::float_literal_expr, cube-scan-wrap member gate)
  • Review JS dialect template changes (MySQL, Oracle, Vertica, ksqlDB)
  • Review tests + fixtures
  • Check CI on head commit
  • Reconcile with prior review threads
  • Post inline findings + summary

New finding

Severity Where What
Medium rust/cubesql/cubesql/src/compile/rewrite/rules/wrapper/literal.rs:107-113 The DataSource::Unrestricted arm quantifies over every data source in the tenant. This PR also deletes types.float from KsqlQuery (packages/cubejs-ksql-driver/src/KsqlQuery.ts:61), so one ksqlDB data source anywhere in the tenant blocks Float32 literal pushdown in unrestricted contexts for all the other data sources too — a pushdown regression created by the two halves of this PR landing together. Posted inline.

CI

Build native (run 35698870320) is failing on the head commit 83c90ba, in two Rust test jobs:

  • Unit (Rewrite Engine) — step Unit tests (Rewrite Engine) (cargo insta test --all-features --workspace --unreferenced reject)
  • Build windows-2022 24.x with Python fallback — step Tests

Check fmt/clippy passed, so this is a runtime test failure, not a compile or lint error. I could not read the job log (log paths are outside this runner's permitted directories) and Rust builds are not available here, so I cannot name the failing test — please check it before merge. Note the job gate is --unreferenced reject, so an orphaned .snap would also fail it.

What I checked and found clean

  • Renderer. The literal operand string is byte-identical to the old format!("{f}") for both widths — (value as f32).to_string() round-trips exactly through f64::from, and Display for f64 is unchanged. Only the surrounding cast is new, so the pre-existing decimal-literal range limits (e.g. MSSQL's numeric(38) ceiling for f32::MAX) are not a new regression. The NULL path reproduces generate_typed_null exactly, including nullable_type.
  • Gate coverage. A missing type template is not a soft fallback: evaluate_wrapped_sql at rust/cubesql/cubesql/src/compile/query_engine.rs:341 propagates a generation error as a CompilationError, so an ungated float-literal path would be a hard query failure. I traced the three render paths — generate_sql_for_expr's Expr::Literal (gated by transform_literal), literal scan members in generate_sql_for_cube_scan (wrapper.rs:1058) and the push-to-cube column path (wrapper.rs:2902) — and all three are now covered, the latter two by the single wrapper-cube-scan-wrap entry point that cube_scan_wrapper.rs:186-201 filters. No other wrapper rule synthesises a float LiteralExprValue.
  • Borrowck/NLL. Hoisting let Some(members) = … out of the data_sources block in cube_scan_wrapper.rs:155 keeps the immutable e-graph borrow alive only to line 201, before the egraph.add calls — behaviour is unchanged for the None case (both return vec![]).
  • Dialect audit. Walked every types.float/types.double value that a float literal can now reach: BigQuery FLOAT64, Presto/Trino/Athena REAL/DOUBLE, Postgres/Redshift REAL/DOUBLE PRECISION, MSSQL FLOAT(24)/FLOAT(53), Oracle BINARY_FLOAT/BINARY_DOUBLE, Vertica FLOAT/DOUBLE PRECISION, and the base FLOAT/DOUBLE inherited by ClickHouse, Snowflake, Hive, SQLite, Crate, DuckDB, Databricks, Pinot, QuestDB, Druid, Firebolt and CubeStore — all valid cast targets in their dialect. The one gap (ksqlDB has no 32-bit float) is the one this PR closes.
  • Template plumbing. expressions/float_literal is additive: both consumers flatten group/name into a HashMap<String, String> (transport/service.rs, cube_bridge/sql_templates_render.rs), so no dialect struct rejects the new key, and the MySQL template parses under minijinja.
  • Side effects worth calling out as improvements. GROUP BY 0 for a Float64 literal dimension (the Power BI pivot_grouping case, mod.rs:7286) becomes GROUP BY CAST(0 AS DOUBLE), which stops PostgreSQL reading it as an ordinal; and CAST(… AS INTEGER) / 7 (mod.rs:7830) becomes a real float division.
  • Comments. Every comment added is ≤3 lines and load-bearing; none restate the code under it.

I could not run the test suites: node_modules is not installed in this checkout and the Rust toolchain is not available, so nothing below the static review was executed here.

Review threads

The listing returned 35 threads whose first comment is mine. Resolved 24 as no longer live (22 fixed in the current diff, 2 findings withdrawn after davidda refuted them — the Snowflake/percentile constant-expression one and the Oracle Cube Store pre-aggregation one). Re-affirmed 5 that the new findings duplicate: dropped error context at wrapper.rs:3183, the override path's missing type/NULL contract at service.rs:945, the pinned template string at float-literal-template.test.ts:12, the NUMERIC-comparison tradeoff at mod.rs:12273, and the renderer-level guard at service.rs:949. Six others stay open on davidda's deferrals. Note: gh reply-to-thread is not configured in this environment, so the two withdrawn findings were resolved without the explanatory reply the process asks for.

Verdict

The core change is sound and the fix matches the symptom in #11780. Two things gate merge independently of my finding: the red Rust CI, and the semantic decision the PR body already asks for — casting float literals changes comparisons against NUMERIC/DECIMAL columns in PostgreSQL, both at precision boundaries and by making the column's numeric index unusable for the pushed filter. That one is a maintainer call, not something I can settle.

· branch [`feature/codex-float-literal-semantics`](https://github.com/cube-js/cube/tree/feature/codex-float-literal-semantics)

Comment thread rust/cubesql/cubesql/src/compile/rewrite/rules/wrapper/literal.rs
@MazterQyou

Copy link
Copy Markdown
Member

/bot-deep-review

@claude

claude Bot commented Sep 23, 2026

Copy link
Copy Markdown
Contributor

Claude finished @MazterQyou's task in 19m 54s —— View job


Deep review — PR #11781 · 1 medium, 1 low · no blockers

Full review

Findings posted inline

Sev Site Concern
Medium cube_scan_wrapper.rs:193 A literal-only scan has no member names, so data_sources is empty and the new gate resolves DataSource::Unrestricted — requiring every generator to render the float type. The renderer resolves the same scan to a specific source via data_source_for_cube_names(used_cubes) (wrapper.rs:986-992). On a postgres + ksql tenant that costs pushdown of the whole scan even though the literal's actual source can render it. alias_to_cube is already in scope at line 134.
Low literal.rs:98 Fully-qualified crate::transport::SqlTemplates in the closure signature although line 14 already imports from that module.

Review threads

The listing returned 36 threads first-authored by claude, all already resolved — nothing stale to resolve this round, and no "still applies" replies needed.

Three findings I had were dropped as duplicates of threads you already answered and closed, so they are not re-posted:

  • literal.rs:107 — the unrestricted arm quantifying over every data source (PRRT_kwDOCOH19M6kzI0z, declined pending a reproducer). My cube_scan_wrapper.rs:193 comment is the narrower case where the source is known, so it is filed separately rather than folded in.
  • service.rs:949 — no non-finite guard inside float_literal_expr (PRRT_kwDOCOH19M6kj7So, declined as defensive hardening).
  • float-literal-template.test.ts — hand-listed dialects vs. allDialects() (PRRT_kwDOCOH19M6jetam, declined).

What I checked and found clean

Gate coverage. Every production path into generate_sql_for_literal is reachable only through wrapper.rs:2666 (Expr::Literal), and both entries into it are now gated — transform_literal for expressions and the new cube_scan_wrapper filter for literal scan members. This matters because a missing template is not a soft fallback: query_engine.rs:341 propagates generate_sql's error as a compilation failure, so an ungated float literal on a dialect without types/float would fail the query rather than plan locally. The LIMIT 0 member test catches a regression here indirectly — as_physical_plan().unwrap() would panic if the filter were removed.

Dialect spellings. Walked every *Query class in packages/cubejs-schema-compiler/src/adapter plus the driver-owned ones against the FLOAT/DOUBLE defaults at BaseQuery.js:4824-4825. The two genuinely invalid ones are exactly the two the PR fixes: Oracle (DOUBLE is not a type; BINARY_DOUBLE is) and Vertica (needs DOUBLE PRECISION). ClickHouse, CrateDB, Snowflake, Databricks, Hive, Druid, DuckDB, SQLite, QuestDB, Pinot all accept the bare aliases; Athena/Trino, Redshift, Firebolt and BigQuery already override or inherit a valid pair. Vertica's FLOAT is float8, so leaving types.float at the default is right.

Pre-aggregations. The CAST(...) now appearing in member-expression SQL (mod.rs:12273, mod.rs:7618) is source-dialect-specific, which would be wrong if it could reach CubeStore — it can't: sql-server.ts:241 sets disableExternalPreAggregations: true for every SQL API query.

Injection surface. The only values interpolated are f64::to_string() and format!("{v:e}") output, both restricted to [0-9.e+-]. No new unparameterized user data.

Nullability. float_literal_expr routes None through nullable_type, matching generate_typed_null; the ClickHouse Nullable(...) case is covered by the wrapper.rs unit test.

Comments. All new comment blocks are ≤3 lines and load-bearing (the MySQL exponent rationale, the decimal-literal range caveat, the literal-scan-member bypass note). Nothing to trim.

Blast radius worth a maintainer's eye

Beyond the precision/index trade-off already in the PR description, the snapshot at mod.rs:7830 shows the change also flips an existing result: CEIL(CAST(EXTRACT(doy …) AS INTEGER) / 7) was integer division in PostgreSQL and now renders / CAST(7 AS DOUBLE). That is the bug being fixed, but it changes values returned by existing week-of-year style dimensions, not just their precision.

Not verified

Rust tests and the driver suites were not run here — no node_modules and no rust/cubesql/target in this checkout, and a cold cargo test on cubesql does not fit the job budget. The author reports passing runs on MySQL 8.0.22/5.6.51, PostgreSQL 16, MSSQL, Oracle, ClickHouse and CrateDB; BigQuery, Snowflake, Redshift, Athena, Trino, Databricks and Pinot run the new executePg assertions without a fixture skip and are unverified by either of us.

· branch `feature/codex-float-literal-semantics`

Comment on lines +188 to +193
let data_sources_out = data_sources_out
.into_iter()
.filter(|source| {
let data_source = source
.as_deref()
.map_or(DataSource::Unrestricted, DataSource::Specific);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The gate and the renderer disagree on how a literal-only scan resolves its data source, and the disagreement always falls on the strict side.

Member::LiteralMember::name() returns None (analysis.rs:220), so a scan whose only members are literals feeds no names into data_sources_for_member_names and data_sources comes back empty. Line 177 then makes data_sources_out exactly vec![None], and line 193 maps that None to DataSource::Unrestricted — which sends can_push_down_float_literal down its ControlFlow::Break(true) arm, demanding that every generator in meta.data_source_to_sql_generator render the float type.

At render time that same scan resolves a specific source: WrappedSelectNode::data_source_for_cube_scan (wrapper.rs:986-992) falls back to meta.data_source_for_cube_names(node.used_cubes) precisely because "a scan can have no members to resolve from".

Concretely, on a tenant with postgres + ksql (which this PR makes the one dialect without types/float, KsqlQuery.ts:61):

SELECT SUM(v) FROM (SELECT CAST(100 AS REAL) AS v FROM KibanaSampleDataEcommerce LIMIT 0) q

KibanaSampleDataEcommerce is on postgres, whose generator has types/float = REAL and expressions/cast, so the literal renders fine — but ksql's generator fails the all() and the whole CubeScan is refused a wrapper, dropping pushdown for every other member in the scan. test_float_literal_member_pushdown_fallback (test_wrapper.rs:101) only exercises the single-source mock, so it passes either way.

alias_to_cube is already bound at line 134 in this same closure, so the specific source is available without extra lookups — resolving through it (as the renderer does) would keep the gate and the renderer in agreement:

let data_source = match source.as_deref() {
    Some(source) => DataSource::Specific(source),
    // A literal-only scan has no member names; the renderer resolves it from
    // the cubes it scans, so the gate has to as well.
    None => meta
        .data_source_for_cube_names(alias_to_cube.iter().map(|(_, cube)| cube.as_str()))
        .unwrap_or(DataSource::Unrestricted),
};

(The general "unrestricted quantifies over every source" question is the one you already declined on literal.rs:113; this is the narrower case where the source is in fact known.)

ScalarValue::Float32(_) | ScalarValue::Float64(_) => return false,
_ => return true,
};
let supports_templates = |templates: &crate::transport::SqlTemplates| {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

crate::transport::SqlTemplates is spelled out inline while line 14 already pulls DataSource and MetaContext from that module.

Suggested change
let supports_templates = |templates: &crate::transport::SqlTemplates| {
let supports_templates = |templates: &SqlTemplates| {

with SqlTemplates added to the use crate::transport::{DataSource, MetaContext}; list.

This branch was successfully deployed

1 active (outdated) deployment
Preview bade4fec Deployed Sep 10, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

data source driver javascript Pull requests that update Javascript code pr:community Contribution from Cube.js community members. rust Pull requests that update Rust code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants