Skip to content

fix(sql): coerceToDate accepts a Number as epoch milliseconds (story 20.5) - #266

Merged
fupelaqu merged 1 commit into
mainfrom
feature/20.5
Sep 2, 2026
Merged

fix(sql): coerceToDate accepts a Number as epoch milliseconds (story 20.5)#266
fupelaqu merged 1 commit into
mainfrom
feature/20.5

Conversation

@fupelaqu

@fupelaqu fupelaqu commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Story 20.5 — elasticsql half: coerceToDate accepts epoch-millis Number (+ ELASTIC_* doc mirrors)

Closes #265

Companion of softclient4es-arrow#168 (date columns) and softclient4es-arrow#169 (env vars). The arrow-side halves (Arrow Date64 UTC-midnight normalisation, the date32[day] readValue arm, the ES_*ELASTIC_* startup alias, and the baked image-ENV removal) ride the arrow repo's feature/20.5 branch, whose PR opens once elasticsql 0.22.0 is published (it will carry Closes #168 + Closes #169).

What changed

  • sql/.../type/ValueCoercion.scalacoerceToDate gains case n: Number => new java.sql.Date(n.longValue()) (epoch milliseconds), placed after the String arm exactly like its long-standing twin in coerceToTimestamp (:203). The scaladoc records why, and why coerceToTime deliberately does not get the arm (a bare number in a TIME position is ambiguous — epoch millis vs millis-of-day — and SQLTypes.Time is unreachable from an ES mapping).
  • sql/src/test/.../ValueCoercionSpec.scala (new) — 8 tests: the Number arm (all shapes incl. BigDecimal, pinned deliberately), every pre-existing conversion, the rejection catch-all, the coerceToTime negative control, SQLTypes("date")SQLTypes.Date, and the UTC-calendar-date invariant (zone-independent assertion).
  • documentation/client/arrow_flight_sql.md + adbc_driver.md — the ES_HOST/ES_PORT/ES_USER/ES_PASSWORD/ES_API_KEY/ES_BEARER spellings (read by nothing — arrow#169) become the canonical ELASTIC_* family; the user = config key (binds to nothing) becomes username =; credential env names are the real ELASTIC_CREDENTIALS_* (never the REPL-only ELASTIC_USERNAME/ELASTIC_PASSWORD). The .mdx twins are updated in the sibling softclient4es-web PR.

Red-first evidence (spec AC 14)

Pre-fix run of the new spec fails with the issue's exact message on exactly the 3 Number-arm tests:

java.sql.SQLException: Cannot convert java.lang.Long to DATE
  at app.softnetwork.elastic.sql.type.ValueCoercion$.coerceToDate(ValueCoercion.scala:158)

Post-fix: 8/8 green.

Verification

  • sbt "+ sql/test"556/556 green on BOTH cross legs (2.12.20 + 2.13.16)
  • sbt "+ core/compile" — green (downstream consumer of sql)
  • sbt scalafmtAll headerCheck — clean
  • Completeness grep (spec E-2) over documentation/: zero ES_* survivors — the only pattern hits are SOFTCLIENT4ES_API_KEY/SOFTCLIENT4ES_LICENSE_KEY in federation_operator_guide.md (a different, correct env family; substring false-positives of the ES_API_KEY pattern)

Blast radius (spec D-3, re-verified)

Three live coerceToDate consumers (arrow ArrowTypeMapping.writeValue, jdbc ElasticResultSet.getDate, jdbc TypeMapping.coerceValue); nothing inside elasticsql calls it; no test anywhere pinned the old Number throw. ElasticStreamingResultSet is dead code (Story 20.1 finding).

Release-note lines (for the 0.22.0 notes)

  • Internal: ValueCoercion.coerceToDate (module softclient4es-sql) now accepts a Number as epoch milliseconds, symmetrically with coerceToTimestamp; coerceToTime deliberately unchanged.
  • Via the arrow train: JDBC ResultSet.getDate on an epoch-millis-stored date column now returns the date instead of throwing.

🤖 Generated with Claude Code

…20.5)

An Elasticsearch date field whose _source stores the value numerically
(legal under ES's default strict_date_optional_time||epoch_millis) reaches
the coercion layer as a java.lang.Long and fell to coerceToDate's catch-all:
'Cannot convert java.lang.Long to DATE'. That took down every cross-index
JOIN leg projecting such a column (softclient4es-arrow#168's Superset
'schema probe' failure) and JDBC ResultSet.getDate with it.

- ValueCoercion.coerceToDate: new 'case n: Number => new java.sql.Date(n.longValue())'
  after the String arm, symmetric with coerceToTimestamp's long-standing arm;
  scaladoc records the deliberate NON-extension to coerceToTime (a bare number
  in a TIME position is ambiguous, and SQLTypes.Time is unreachable from an
  ES mapping).
- New ValueCoercionSpec: RED-first on the exact pre-fix message; Number shapes
  (Long/Integer/BigDecimal/Double), pre-existing conversions, the coerceToTime
  negative control, and the UTC-calendar-date invariant.
- documentation/client/{arrow_flight_sql,adbc_driver}.md: ES_* env spellings
  (read by nothing, softclient4es-arrow#169) corrected to the canonical
  ELASTIC_* family, plus 'user' -> 'username' and the real credential env
  names (ELASTIC_CREDENTIALS_*).

Both cross legs green (sql/test 556+556), + core/compile green, scalafmtAll +
headerCheck clean.

Issue #265 (companion of softclient4es-arrow#168/#169)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@fupelaqu
fupelaqu marked this pull request as ready for review September 2, 2026 15:46
@fupelaqu
fupelaqu merged commit 74b52db into main Sep 2, 2026
6 of 8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

coerceToDate has no Number arm — numeric epoch-millis date values throw on every DATE surface

1 participant