Repository navigation
TIME (time64[ns]) type unhandled (#505) - #508
peco-engineer-bot[bot] wants to merge 8 commits into
Conversation
Signed-off-by: peco-engineer-bot[bot] <3815206+peco-engineer-bot[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Verdict: 1 Medium
Solid, well-targeted fix for the unhandled time64[ns] TIME type — the new Time32/Time64 arms, container, and unit tests are coherent and release arrays correctly. One medium concern: .In(tvc.location) shifts a zoneless SQL TIME when a non-UTC WithLocation is configured, contradicting the container's stated intent and untested by the UTC-only unit tests.
Addresses: - #4332939800 at internal/rows/arrowbased/columnValues.go:531 Signed-off-by: peco-engineer-bot[bot] <peco-engineer-bot[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Verdict: 1 Medium
Looks good — the TIME fix is correct and well-tested (unit→ns math is sound, the zoneless-UTC rendering is deliberate and matches the sibling date/timestamp containers, and the Time32/Time64 unit cases are covered). One medium: the kernel/SEA backend (internal/arrowscan) still errors on TIME, so the two backends now diverge for the same query despite the repo's cross-backend parity contract. Note also that a scanned TIME carries a spurious 1970-01-01 date (documented in the container comment, and the only reasonable anchor), which callers using .Format/.Date() will observe.
Addresses: - #4333004011 at internal/rows/arrowbased/arrowRows.go:654 Signed-off-by: peco-engineer-bot[bot] <peco-engineer-bot[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Verdict: 1 Low
Looks good — the time32/time64 TIME handling is correct, overflow-safe, lifecycle-consistent with the existing containers, and the Thrift and kernel paths render identically (parity test extended). One low note: a zoneless TIME is epoch-anchored on 1970-01-01, which would surface a spurious date if a TIME is ever rendered nested inside a complex type via marshal() — likely unreachable and consistent with existing nested-TIMESTAMP behavior.
Addresses: - #4333050880 at internal/rows/arrowbased/columnValues.go:518 Signed-off-by: peco-engineer-bot[bot] <peco-engineer-bot[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Verdict: 1 Medium
One cross-backend concern: the fix special-cases nested TIME rendering on the Thrift side (marshalScalar → time-of-day string) but not on the kernel side (internal/arrowscan nested path still emits the full time.Time.String() with the 1970-01-01 epoch date), so a TIME inside a STRUCT/LIST/MAP diverges between the two backends — and no parity test covers the nested case. Top-level handling and the unit tests look correct.
Addresses: - #4333081459 at internal/arrowscan/arrowscan.go:225 Signed-off-by: peco-engineer-bot[bot] <peco-engineer-bot[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Verdict: 1 Medium · 1 Nit
Looks good overall. The Thrift-path fix for time64/time32 is correct and has thorough unit, parity and e2e coverage. One medium gap: the kernel column-type metadata was not updated alongside the new scan arms. Nit: timeUnitToNanos (arrowbased) and timeOfDayToTime (arrowscan) duplicate the unit→ns switch, and the PR description's file list leaves out the arrowscan changes.
Other findings
- ⚪ Nit —
ValueStringswallows the error fromValueand returns"".marshalScalarthen emits it as"", so a nested TIME inside a STRUCT/LIST/MAP that hits the unhandled-array path renders silently as an empty string instead of surfacing an error. Other nested paths, such asmarshal/Value, propagate errors.
Suggested fix: have marshalScalar call Value itself and return the error, or have ValueString return (string, error). The error branch is unreachable in practice today because SetValueArray already validates the type, but returning the error keeps a future regression from turning into silently wrong data.
Addresses: - #4353008500 at internal/arrowscan/arrowscan.go:217 Signed-off-by: peco-engineer-bot[bot] <peco-engineer-bot[bot]@users.noreply.github.com>
Addresses: - #4353072312 at internal/arrowscan/arrowscan.go:493 Signed-off-by: peco-engineer-bot[bot] <peco-engineer-bot[bot]@users.noreply.github.com>
| // Thrift path's timeValueContainer.ValueString (marshalScalar json.Marshals | ||
| // it). Falling through to writeScalarJSON would quote t.String() and leak | ||
| // the 1970-01-01 epoch anchor date, diverging from Thrift. | ||
| return writeScalarJSON(b, formatTimeOfDay(int64(c.Value(row)), col.DataType().(*arrow.Time32Type).Unit)) |
There was a problem hiding this comment.
Add parity cases for TIME map keys.
Pushed fe0a9e5.
There was a problem hiding this comment.
Verdict: 1 Medium · 1 Nit
The core fix looks correct. The new Time32/Time64 container arms fix the unhandled type time64[ns] error and keep the zoneless value in UTC. One medium concern: TIME column metadata now differs between the Thrift and kernel paths, and the parity test that should catch this doesn't cover TIME. Separately, the PR description lists only 4 changed files, but the diff also changes internal/arrowscan/* (kernel scan, nested JSON and map-key rendering, plus coltype) and arrowscan_parity_test.go. Reviewers should look at those kernel-path changes too.
Other findings
- ⚪ Nit — The e2e test checks that the scanned value parses as a time-of-day, but it doesn't check
rows.ColumnTypes()(scan type / database type name) for the TIME column. It also has no nested case (e.g.SELECT named_struct('t', TIME '09:15:00')). Yet this PR adds a lot of nested-TIME rendering (marshalScalar, pluswriteJSONand map keys in arrowscan) based on the assumed server format"09:15:00". One extra live assertion on a nested TIME and on the column metadata would confirm those assumptions against the real server encoding instead of only against synthetic arrays.
| case arrow.TIMESTAMP: | ||
| return ColumnTypeInfo{DatabaseTypeName: "TIMESTAMP", ScanType: scanTypeDateTime} | ||
| case arrow.TIME32, arrow.TIME64: | ||
| // SQL TIME: ScanCellCached returns a time.Time (time-of-day on the Unix |
There was a problem hiding this comment.
🟡 Medium — This makes the kernel path report TIME columns as DatabaseTypeName: "TIME" with ScanType time.Time. The Thrift mapping can't do the same: cli_service has no TTypeId_TIME_TYPE, so over Thrift the server declares a TIME column under some other id. getScanType / GetDBTypeName then give a different name, and probably a different scan type (string or unknown), while timeValueContainer.Value now returns a time.Time.
This causes two problems:
- On the Thrift path,
ColumnTypeScanTypefor a TIME column probably disagrees with the value thatNextactually returns. - The two backends no longer agree on column metadata. This is exactly the drift
TestColumnTypeInfoMatchesThriftMappingexists to catch, but the PR adds notime32/time64case to that test, so the mismatch passes CI.
Suggested fix: check live which TTypeId the server reports for SELECT TIME '…'. Then either align getScanType for that id with ColumnTypeInfoFor, or add a parity case that writes down the intended difference.
There was a problem hiding this comment.
⛔ NEEDS HUMAN DECISION — the bots can't resolve this thread; a maintainer's input is required.
Blocked: the fix depends on which TTypeId the server declares over Thrift for SELECT TIME '…', and only a live warehouse can show that. This job has no warehouse credentials. cli_service has no TIME id, and the Thrift value comes from the Arrow type: time64 goes to timeValueContainer, which returns a time.Time. getScanType/GetDBTypeName go by the TTypeId, so on Thrift they almost certainly disagree with that value. TestColumnTypeInfoMatchesThriftMapping has no time32/time64 case, and TestE2ETimeTypeScan only checks the value, not ColumnTypes(). I didn't add a parity row because the id would be a guess. A human needs to: (1) run SELECT TIME '09:15:00.123456' over Thrift and record the column's PrimitiveEntry.Type, ColumnTypeScanType and ColumnTypeDatabaseTypeName; (2) decide whether to align getScanType/GetDBTypeName with ColumnTypeInfoFor's TIME/time.Time (e.g. by looking at the Arrow type) or keep the difference on purpose; (3) add a {"time64", Time64us, } row (or a documented exception) to TestColumnTypeInfoMatchesThriftMapping, and optionally assert ColumnTypes() in TestE2ETimeTypeScan.
Summary
Automated fix for #505 — TIME (time64[ns]) type unhandled.
Added *arrow.Time32Type/*arrow.Time64Type arms to makeColumnValueContainer plus a new timeValueContainer (and timeUnitToNanos helper) that renders units-since-midnight as a UTC wall-clock time.Time; this resolves the "unhandled type time64[ns]" failure for SQL TIME columns. Unit tests pass and the full
make testsuite +make lintare green. The e2e test (TestE2ETimeTypeScan) was confirmed red against the live warehouse in the prior phase; it only skips here because this runner's bash subprocess scrubs credential-shaped env vars so the warehouse creds aren't reachable from the tool.Root cause & plan
Root cause: On the Thrift Arrow path, a TIME column arrives in the Arrow IPC schema as time64[ns] (microsecond-precision TIME is still carried as time64 nanoseconds). arrowValueContainerMaker.makeColumnValueContainer (internal/rows/arrowbased/arrowRows.go, ~line 591+) switches on the concrete arrow.DataType and has arms for Date32/Timestamp/Decimal/String/List/Map/Struct/Null etc., but NO arm for *arrow.Time32Type or *arrow.Time64Type. The value hits the default arm and returns errArrowRowsUnhandledArrowType(t.String()) -> "databricks: arrow row scanner unhandled type time64[ns]". Because containers are built eagerly in makeColumnValuesContainers when the row scanner is created, this fails the whole result set before any row is returned. DATE and TIMESTAMP already have arms (dateValueContainer / timestampValueContainer); TIME has none.
Files:
internal/rows/arrowbased/arrowRows.go,internal/rows/arrowbased/columnValues.go,driver_e2e_test.goPlanned coverage:
SELECT TIME '09:15:00.123456'must run and rows.Scan must succeed (no 'unhandled type time64[ns]' error), yielding a time-of-day time.Time with Hour()=9, Minute()=15, Second()=0, Nanosecond()=123456000 and UTC location (no date, no location). This is the exact reproducer from the issue and exercises the real server's time64[ns] encoding end-to-end. (time64 (primary, live e2e))Files changed
internal/rows/arrowbased/time_test.godriver_e2e_test.gointernal/rows/arrowbased/columnValues.gointernal/rows/arrowbased/arrowRows.goTest plan
TestTime64ValueContainer (internal/rows/arrowbased/time_test.go)— fails (red) against the original code, passes (green) after the fixTestTime32ValueContainer (internal/rows/arrowbased/time_test.go)— fails (red) against the original code, passes (green) after the fixTestE2ETimeTypeScan (driver_e2e_test.go)— fails (red) against the original code, passes (green) after the fix🤖 Generated by engineer-bot (bug-fix flow) — review before merge.