Repository navigation
fix(editor): bind an Oracle DATE and TIMESTAMP edit through TO_DATE, TO_TIMESTAMP, or FROM_TZ - #1818
Merged
cevheri merged 2 commits intoOct 19, 2026
Conversation
…TO_TIMESTAMP, or FROM_TZ The Oracle provider reads DATE and TIMESTAMP as their wall clock (libredb#1131), and TIMESTAMP WITH [LOCAL] TIME ZONE arrives over HTTP as its Date#toISOString text (libredb#1224). The grid opens its editor on those three text forms, and the hook bound an edit of them as a bare :n parameter, which oracledb binds as DB_TYPE_VARCHAR and Oracle reads through the session NLS format. Measured on Oracle AI Database 26ai Free 23.26.3.0.0 (gvenzl/oracle-free:slim, oracledb 26.0.0, TZ=Europe/Istanbul, default NLS_DATE_FORMAT=DD-MON-RR): a bare :1 bind was refused with ORA-01861 on DATE and ORA-01843 on TIMESTAMP, TIMESTAMP WITH TIME ZONE, and TIMESTAMP WITH LOCAL TIME ZONE, while wrapping :n in TO_DATE, TO_TIMESTAMP, or FROM_TZ(TO_TIMESTAMP(:n, ...), UTC) stored every value equal on the server, BC dates included. result-export.ts now exports the masks and regexes (ORACLE_TIMESTAMP_TYPE, ORACLE_ZONED_TIMESTAMP_TYPE, ORACLE_DATE_MASK, ORACLE_BC_DATE_MASK, ORACLE_ZONELESS_TEXT, ISO_INSTANT_TEXT), and use-inline-editing.ts wraps :n through them in oracleBoundEdit / boundEditValue. Any other text shape, NULL, or another column/dialect still binds as :n untouched. Closes libredb#1807
Contributor
There was a problem hiding this comment.
Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.
An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Resolve the oracle.md capability row against libredb#1823 (createTable) and replace the two new em dashes in the inline-editing paragraph.
cevheri
added a commit
that referenced
this pull request
Oct 19, 2026
…placeholder, inline edit and export rules beside its provider (#1850) * test(sql-writing): pin today's literal, identifier and placeholder output per engine * feat(sql-writing): the SqlWritingSpec contract and its shared building blocks * feat(sql-writing): declare each engine's literal, identifier and placeholder rules beside its provider * refactor(sql-writing): quoteLiteral, positionalPlaceholder and quoteIdentifier read the engine's module LITERAL_ESCAPE, BACKSLASH_ESCAPES and the placeholder and identifier switches are gone; each reader asks sqlWritingFor for the engine's spec, and every public signature is unchanged. The search docs and their citation pins name the engine modules instead of the deleted table. * test(sql-writing): one engine's module cannot reach another engine's output * docs(sql-writing): each provider doc names its literal, identifier and placeholder rules Every docs/providers/<id>.md gains a SQL writing subsection that links the engine's module and names its literal, identifier and placeholder building blocks, with the measurements the module's comments carry written out as prose. tests/unit/db/sql-writing/docs.test.ts holds each subsection to the module's functions by name. ADDING_A_PROVIDER.md replaces the LITERAL_ESCAPE and positionalPlaceholder() items with the placement rule, the SQL_WRITING row and the UPDATE_GOLDEN step; ARCHITECTURE.md names src/lib/db/sql-writing/ beside QUERY_DIALECTS. The oxia and s3 doc tests list the new subsection and module row, and the search citation list measures SQL_WRITING. * fix(sql-writing): an empty or null type reads the no-connection spec, as every writer did before The old writers tested `dialect ? ... : "standard"`, so the empty string a published component's `databaseType?: string` can carry got the standard literal. sqlWritingFor tested only undefined and sent it to the unknown-type spec, which doubles the backslash. The new empty-string golden is rendered from origin/main. * test(sql-writing): the contract's types and shared building blocks import nothing but types * docs(sql-writing): point the stale LITERAL_ESCAPE and values.ts escape citations at the engine's module * test(guard): drop the SQL writing debt entries this branch moves into per-engine modules * test(sql-writing): pin today's inline edit output per engine Every golden file gains one line per inline edit case and per key case: the key check request, the executeQuery calls and the toasts of one apply through useInlineEditing. Lines are only appended; src/ is unchanged. The isolation subprocess spreads the real engine module under the sentinel, gives the sentinel an inlineEdit field, and writes its output with an awaited Bun.write, because console.log of this much output into the pipe was cut at 64 KiB. The fixtures import the hook after the sonner mock, since a static import links the real sonner first in a subprocess without preload. * feat(sql-writing): the inline edit contract and its shared type-name sets InlineEditSpec states the key-column type names and the optional bound edit one engine answers for. The instant, float64 and exact decimal sets and the unwrappedType and firstWord helpers are copied into shared.ts with their docblocks; the hook keeps its own copies until it reads the contract. * feat(sql-writing): each engine states the key column kinds and bound edit its inline editor reads SqlWritingSpec gains inlineEdit. Every engine but sqlite and libsql, and both named specs, reads the shared instant, exact decimal and float64 sets. SQLite and libSQL each state empty instant and exact decimal sets and float64 "all" in their own file, with the measurements that were on the hook's dialect sets. MSSQL_DATETIME_TEXT moves into the mssql module and the six Oracle date constants into the oracle module, each with its docblock; the exports and the hook import them from there. The mssql and oracle bound edits are copies of the hook's, which keeps its own until Task 3.4. * refactor(sql-writing): the inline editor reads key column kinds and bound edits from the engine's module keyColumnKind reads instantTypeNames, float64TypeNames and exactDecimalTypeNames from sqlWritingFor(dialect).inlineEdit, and the SET right-hand side comes from the engine's boundEdit, falling back to the bound text. The hook's own type-name sets, the three sqlite/libsql dialect sets, the never-read FLOAT64_ONLY_NAMES, the mssql and oracle bound edit helpers and their engine module imports are gone; unwrappedType and firstWord come from the shared module. Their measured docblocks already live in the shared and engine modules. The guard drops the five pr 3 debt entries, the seam test holds the hook to no export module and no engine module import, and the Oracle provider doc points at the module that now holds the bound edit. No golden moved. * test(sql-writing): one engine's inline edit rules cannot reach another engine's output The isolation mock now gives the inline edit field a sentinel of its own, because the quoted names and placeholders already put SENTINEL into every edit's SQL. For every engine whose committed edits reach their UPDATE, each non-NULL edit in the replaced engine's own rendering must carry the bound edit sentinel, so the hook demonstrably read the mocked inlineEdit. * docs(sql-writing): each provider doc names its inline edit rules Every provider doc's SQL writing subsection now names the module's inlineEdit: the shared INSTANT_TYPE_NAMES, FLOAT64_TYPE_NAMES and EXACT_DECIMAL_TYPE_NAMES on 40 engines, the SQLite affinity facts with their measurements on sqlite and libsql, and the boundEdit of mssql (DATEFORMAT, 2026-10-12) and oracle (NLS, #1818). docs.test.ts holds each subsection to the module. ADDING_A_PROVIDER.md and ARCHITECTURE.md name inlineEdit and its reader. The backlog's EXACT_DECIMAL_TYPE_NAMES citations and the sqlite-provider test comment point at src/lib/db/sql-writing/shared.ts. * docs(backlog): R4 is done, the inline editor reads its engine facts from each SqlWritingSpec The three per-engine sets are inlineEdit fields of each engine's module, their measurements sit in the sqlite and libsql modules and provider docs, and the guard holds no use-inline-editing.ts entry. * test(sql-writing): pin today's SQL export output per engine Every golden gains the SQL INSERT, CREATE TABLE and numeric CSV output of buildResultExport for its key, rendered under Asia/Tokyo so Oracle's local reading of a naive Date is the same on every machine. Only lines are appended: no value or inline edit line moves, and src/ is unchanged. * feat(sql-writing): the export building blocks and the engine-neutral export helpers leaf types.ts gains the export contract types (InferredKind, ScalarLiteral, TypedWriter, ColumnWriter, ExportTypeVocabulary, ExportSpec); SqlWritingSpec is unchanged, so no engine module moves yet. shared.ts gains the default export tables and the type vocabulary patterns, copied with their docblocks and values from result-export.ts, which keeps reading its own copies until its tables become reads of each engine's spec. export-shared.ts is the leaf of engine-neutral export helpers: UnwritableValue, asBytes, binaryText, jsonText, one function per binary literal form, and the parsers the typed-literal grammars share. binary.ts and json.ts re-export what moved by name, and typed-literals.ts imports from the leaf and re-exports UnwritableValue and ScalarLiteral, so no importer changes. shared.test.ts also reads BARE_TYPE_FAMILY, splitArguments and ExportSpec, which have no other reader until the export cutover and would otherwise fail knip. * refactor(sql-writing): split typed-literals.ts by engine Each engine's typed writer moves whole, with its tables, regexes and docblocks, into a module beside its provider: postgres-typed-literals.ts and <id>/typed-literals.ts for clickhouse, trino, duckdb, cassandra, databend, snowflake and spark. The SQL Server writer moves into mssql-sql-writing.ts next to MSSQL_DATETIME_TEXT. Each quoteLiteral call becomes the escape function the engine's own module names as escapeLiteral, and cassandra's quoteIdentifier becomes identifierDoubleQuoted, so no writer imports @/lib/sql/values or @/lib/sql/identifier. typed-literals.ts keeps typedLiteral and TYPED_WRITERS over the nine imported writers; its unused @/lib/non-finite import is gone. No expected value changed. mssqlLiteral is exported for now because TYPED_WRITERS imports it; it becomes module-local when the export spec lands in the module. tests/unit/db/sql-writing/seam.test.ts: ALLOWED gains @/lib/db/sql-writing/export-shared, the engine-neutral export leaf spec 5.3 [T1] lets an engine module import, since mssql-sql-writing.ts now reads normalized from it. Known, left to the orchestrator: the databend and snowflake seam guards report rule 7 for their new typed-literals.ts (same class as sql-writing.ts), and the spark seam guard's isolation test now also names src/lib/export/typed-literals.ts importing spark/typed-literals. * feat(sql-writing): each engine states its export facts beside its provider SqlWritingSpec gains a required export: ExportSpec, and every engine module and both named specs write one, value for value from today's result-export tables: DIALECT_TYPES over STANDARD_TYPES, STANDS_ALONE, DIALECT_BARE_SPELLING, BINARY_LITERAL, NON_FINITE_LITERALS, INSERT_SELECT_DIALECTS, TYPED_WRITERS, DECLARED_TYPE_REWRITE and the snowflake, oracle and cassandra branches. Engines on a default import the shared object by name; no base object is spread. The per-engine comments of those tables, and the sentence of each docblock about an engine, are copied beside the field they explain. result-export.ts keeps reading its own tables, with their comments, until they are deleted. Moved whole with their docblocks: the Oracle date helpers into oracle-sql-writing.ts, whose masks and patterns become module-local; snowflakeVectorDeclaration into snowflake/typed-literals.ts, where snowflakeVectorType becomes module-local; CASSANDRA_PRIMARY_KEY_NOTE into cassandra/sql-writing.ts. result-export.ts imports them from there. mssqlLiteral moves above MSSQL_SQL_WRITING, which now reads it. Em dashes in moved comments became a colon or a comma. census.test.ts pins every export fact per id and the two named specs, and exercises the oracle, snowflake and cassandra hooks against today's output. seam.test.ts lets an engine module import its own typed-literals sibling (spec 5.3) and holds that sibling to the same rule. No expected value changed. * refactor(sql-writing): the SQL export reads the engine's module buildResultExport now asks sqlWritingFor(dialect).export for every type name, literal and statement shape instead of reading tables keyed by type id. Every engine's golden output is unchanged. Three goldens move on purpose, none of them an engine: "constructor" read Object.prototype through the old tables; Object.hasOwn now gives it the unknown-type spec, so its lines equal not-an-engine's. "" and "not-an-engine" threw "TypeError: undefined is not an object (evaluating 'standsAlone.includes')" on every ddl line of a column with a bare declared name; they now get the portable spelling a missing connection already gets. The app passes activeConnection?.type, so only an embedding host with a cast could reach either. * test(sql-writing): one engine's export facts cannot reach another engine's output The seam test now holds each typed writer sibling to the engine module's import rule, checks that only its owner imports it under src/, keeps the export path off the provider tree (spec 5.4), and loads every sibling and export-shared.ts first in a fresh process. The isolation sentinel gains an export field, and the replaced engine's ddl(inferred) line must show the sentinel type. * docs(sql-writing): each provider doc names its export rules Every provider doc's SQL writing subsection gains an export paragraph naming the binary literal form, each export fact by the shared constant it holds or by its field name, every export hook the module defines and its typed-literals sibling; docs.test.ts holds each paragraph to the module. The measurements the deleted result-export.ts tables carried are now prose beside each engine: the stands-alone probe (Postgres 18.4, MySQL 26.7.0, Oracle Free 23ai, SQL Server 2022 CU26, SQLite through bun:sqlite, ClickHouse 26.7.1, Trino 476, Cassandra 5.0.9) and each named absence, the binary literal measurements, the non-finite replays, Cassandra's frozen and primary key refusals, Snowflake's PARSE_JSON and VECTOR refusals, MySQL's bare temporal rounding and Db2's BX form. ADDING_A_PROVIDER.md drops the result-export.ts checklist item, which SQL_WRITING now covers, and states the module import rule with export-shared.ts and the typed-literals sibling. Stale citations of the deleted tables in provider docs, BACKLOG X12, U143 and U144 and two code comments now name where each lives. * test(sql-writing): the export reads the engine's own type vocabulary The isolation sentinel passed the engine's real vocabulary through, which is the shared one for every engine, so an export that imported the shared vocabulary directly passed every test. The sentinel now states a vocabulary that disagrees with the shared one on each fixture type, and the replaced engine's ddl("varchar2") and csv(numeric) lines must show it. Measured: importing any one of BARE_TYPE_FAMILY, FLOAT_TYPE, EXACT_NUMERIC_TYPE or CLICKHOUSE_WRAPPER in result-export.ts fails all 42 cases. * test(sql-writing): spell the sentinel wrapper without an escape oxlint refuses * test(sql-writing): a key outside the union exports what an unknown type exports The prototype keys, a miscased or padded type id and null used to throw or write [object Object] in the export; the registry's Object.hasOwn lookup now sends them to the named specs. Pin that output against the committed not-an-engine export (and null against no connection), so the move is held by a test and not only by registry membership. * docs(sql-writing): each JSON engine's binary literal comment states its own fact Drop the cross-engine count and the pointer to values.ts, which no longer holds that rationale. * docs(sqlite): the REAL row editor measurement is historical SQLite's module declares float64TypeNames "all", so the row editor no longer reads the shared set for it; the comment named shared.ts as its source. * docs(architecture): the result export reads the row's export field * test(seam-guard): the Databend and Snowflake SQL writing modules import their contract's building blocks sql-writing.ts and its typed-literals.ts sibling join migration-ddl.ts in CONTRACT_VALUES, so rule 7 admits @/lib/db/sql-writing/* for those two files only; a planted import of it from another file still fails by name. * test(seam-guard): each InfluxDB SQL writing row is a layer of its own influxdb-sql-writing.ts and influxdb3-sql-writing.ts may import values only from @/lib/db/sql-writing/* and from none of the directory's modules, the shape the migration DDL and SQL grammar layers already have. * test(seam-guard): the etcd module table names sql-writing.ts * test(seam-guard): the SQL writing registry may import the Spark SQL writing row CONTRACT_REGISTRIES admits src/lib/db/sql-writing/index.ts importing spark/sql-writing and nothing else of the directory. * test(sql-writing): parse src/ once for the typed writer sibling importer checks Each of the eight "is imported under src/ by <owner>'s module only" tests re-parsed every file under src/, about 0.6 to 0.9s each on an idle machine and close to the 5000ms per-test timeout under contention. The import index is now built once in beforeAll and every sibling check reads it. Three copies on one core (taskset -c 0) now pass in 8.6s each with no timeout; a planted importer in cassandra/objects.ts still fails the cassandra check. * test(sql-writing): replace engines in separating groups and size the child pool to the machine The isolation test ran one child per engine, eight at once, whatever the core count. It now uses the separating groups and the pool of a quarter of the cores from 3dc6166: 12 children instead of 42, each replacing a group of engines while every engine outside it is held to its golden. Every ordered pair of engines is separated by some group, which a test asserts, and each engine's own sentinel checks run in the first child that replaces it. tests/unit/db/isolation-children.ts is byte-identical to the copy on refactor/engine-contract-7-connection-form, so the two branches merge cleanly. A planted leak (mysql's quoteIdentifier reading postgres's row) fails every group that replaces postgres and holds mysql real. Measured on two cores (taskset -c 0,1): 35.9s before, 11.4s after. * test(sql-writing): hold each typed writer's escape to its engine's own row A typed writer sibling names its engine's escape from shared.ts rather than reading the engine's row, and binaryTextBackslash hard-codes the backslash escape. The census now asserts each literal or identifier helper a sibling imports is the same function as its engine's escapeLiteral or quoteIdentifier, and that bigquery and couchbase, the two engines writing binary as backslash-escaped text, escape their literals with literalBackslash. A planted change of spark's and bigquery's escapeLiteral fails both checks. * docs(engine-contract): name the SQL writing registry and the empty debt list With every engine-contract PR merged, CLAUDE.md lists src/lib/db/sql-writing/ among the per-engine contract registries, docs/ARCHITECTURE.md says the guard's debt list is empty, and the isolation children docblock names the sql-writing isolation test. * docs(adding-a-provider): drop the parser scheme item the connection string registry now holds The scheme is the new engine's ConnectionStringSpec, which the required checklist item already names, and CONNECTION_STRING is total over DatabaseType, so a missing one no longer compiles. * refactor(sql-writing): state each fact the SQLite family shares once, by name SQLite and libSQL held the same inline edit sets and the same standsAlone list, each with a copy of the measured docblock. Each fact now lives once in shared.ts under a name that states it (NO_INSTANT_TYPE_NAMES, NO_EXACT_DECIMAL_TYPE_NAMES, EVERY_TYPE_IS_FLOAT64, VERBATIM_DECLARED_TYPES_STAND_ALONE), with its measurement beside it, and both modules reference it; the sentences that hold for libSQL only stay in its module. The same rule takes the type vocabulary every engine spelled out field by field (STANDARD_TYPE_VOCABULARY) and the quoted non-finite words PostgreSQL and DuckDB both write (QUOTED_WORD_NON_FINITE). No value changes and no golden file moves; the provider docs name the new constants, and docs.test accepts any shared constant a field holds.
cevheri
added a commit
that referenced
this pull request
Oct 20, 2026
… budgets The seam guard's `descriptor readers` rule walked every file of the repository through `spellings`, which collects every identifier as well as every string and computes a line for each. It ran out of bun's five-second default on busy runners: on main at 6dcc67f and 9c983bb, on #1818's macOS job, and on this branch's coverage job. The rule now walks string nodes alone, reads no lines, and builds the module-name set only for a file holding a match; it compares the node's own text as before, so an escaped spelling is still caught. Measured with coverage: 1.20 s to 0.29 s alone, and under four parallel copies beside 16 busy loops the old rule reached 3.66 s while the new file's slowest test took 1.68 s, 12 runs without a failure. The connection-options certificate hook makes RSA keys with openssl and carried bun's default; it now has a 60-second budget, as the seam guard's own parse hook does, and passed the same 12 loaded runs. D153 recorded both and is removed; the three entries that pointed at it now say what they meant without the pointer.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
When a user edits an Oracle
DATE,TIMESTAMP,TIMESTAMP WITH TIME ZONE, orTIMESTAMP WITH LOCAL TIME ZONEcell in the Results Grid (src/hooks/use-inline-editing.ts), the inline editor previously emitted a bare"COL" = :1placeholder and bound the edited cell string directly.Because
OracleProviderreturnsDATEand zonelessTIMESTAMPcolumns as wall-clock strings ("YYYY-MM-DD HH:MM:SS[.ffffff]", #1131 / PR #1225 and #1498 / PR #1612) andTIMESTAMP WITH [LOCAL] TIME ZONEcolumns arrive overPOST /api/db/queryJSON as ISO-8601 UTC strings ("YYYY-MM-DDTHH:MM:SS.sssZ", #1224 / PR #1500),node-oracledbbinds:1asDB_TYPE_VARCHAR. Oracle then parses:1through the session'sNLS_DATE_FORMAT(DD-MON-RR),NLS_TIMESTAMP_FORMAT(DD-MON-RR HH.MI.SSXFF AM), orNLS_TIMESTAMP_TZ_FORMAT(DD-MON-RR HH.MI.SSXFF AM TZR), refusing all three formats (ORA-01861/ORA-01843).Following @kaya-abdullah's notes on #1807 (and the SQL Server
MSSQL_DATETIME_TEXTpattern from #1452 / PR #1639 andtemporalLiteralinsrc/lib/sql/values.ts):src/lib/export/result-export.ts):ORACLE_TIMESTAMP_TYPE,ORACLE_ZONED_TIMESTAMP_TYPE,ORACLE_DATE_MASK,ORACLE_BC_DATE_MASK,ORACLE_ZONELESS_TEXT, andISO_INSTANT_TEXTare exported while keeping the export literal helpers (oracleDateLiteral,oracleTextLiteral,oracleZonedTextLiteral) unchanged.src/hooks/use-inline-editing.ts):oracleBoundEdit/boundEditValuereads the column's declared type from theresult.columnTypesmap (declaredTypeOf(result, c.columnId)) and wraps:nin the matching conversion expression when the edited text still matches the column's form:DATEmatching[-]?YYYY-MM-DD HH:MM:SS→TO_DATE(:n, '[S]YYYY-MM-DD HH24:MI:SS')TIMESTAMP[(p)]matching[-]?YYYY-MM-DD HH:MM:SS[.ffffff]→TO_TIMESTAMP(:n, '[S]YYYY-MM-DD HH24:MI:SS[.FF]')TIMESTAMP[(p)] WITH [LOCAL] TIME ZONEmatchingDate#toISOString(YYYY-MM-DDTHH:MM:SS.sssZ, verified via round-tripnew Date(text).toISOString() === text) →FROM_TZ(TO_TIMESTAMP(:n, 'YYYY-MM-DD HH24:MI:SS.FF3'), 'UTC'), binding"YYYY-MM-DD HH:MM:SS.sss"in:nNULL, an undeclared column, or any other dialect continues to bind as plain:n.Live measurement (Before / After on Oracle 26ai Free)
Measured 2026-10-18 against
gvenzl/oracle-free:slim(Oracle AI Database 26ai Free Release 23.26.3.0.0 - Develop, Learn, and Run for Free),oracledb26.0.0, Bun 1.4.2, processTZ=Europe/Istanbul.Session NLS defaults read from
NLS_SESSION_PARAMETERS:NLS_DATE_FORMAT:'DD-MON-RR'NLS_TIMESTAMP_FORMAT:'DD-MON-RR HH.MI.SSXFF AM'NLS_TIMESTAMP_TZ_FORMAT:'DD-MON-RR HH.MI.SSXFF AM TZR'Source table read through
OracleProvider.query()and passed throughJSON.parse(JSON.stringify(read))asPOST /api/db/queryreturns it (columnTypesmap:{"ID":"NUMBER","D":"DATE","TS":"TIMESTAMP","TTZ":"TIMESTAMP WITH TIME ZONE","TLTZ":"TIMESTAMP WITH LOCAL TIME ZONE"}):columnTypes[col])main(UPDATE¶ms)mainresult on OracleUPDATE¶ms)D(DATE)"2026-09-02 09:15:00"SET "D" = :1 WHERE "ID" = :2["2026-09-02 09:15:00", 1]ORA-01861: literal does not match format stringSET "D" = TO_DATE(:1, 'YYYY-MM-DD HH24:MI:SS') WHERE "ID" = :2["2026-09-02 09:15:00", 1]D_EQ = EQUAL("2026-09-02 09:15:00")TS(TIMESTAMP)"2026-09-02 10:30:00.345"SET "TS" = :1 WHERE "ID" = :2["2026-09-02 10:30:00.345", 1]ORA-01843: An invalid month was specified.SET "TS" = TO_TIMESTAMP(:1, 'YYYY-MM-DD HH24:MI:SS.FF') WHERE "ID" = :2["2026-09-02 10:30:00.345", 1]TS_EQ = EQUAL("2026-09-02 10:30:00.345")TTZ(TIMESTAMP WITH TIME ZONE)"2026-08-25T17:11:12.345Z"SET "TTZ" = :1 WHERE "ID" = :2["2026-08-25T17:11:12.345Z", 1]ORA-01843: An invalid month was specified.SET "TTZ" = FROM_TZ(TO_TIMESTAMP(:1, 'YYYY-MM-DD HH24:MI:SS.FF3'), 'UTC') WHERE "ID" = :2["2026-08-25 17:11:12.345", 1]TTZ_EQ = EQUAL("2026-08-25T17:11:12.345Z")TLTZ(TIMESTAMP WITH LOCAL TIME ZONE)"1999-01-01T18:04:05.006Z"SET "TLTZ" = :1 WHERE "ID" = :2["1999-01-01T18:04:05.006Z", 1]ORA-01843: An invalid month was specified.SET "TLTZ" = FROM_TZ(TO_TIMESTAMP(:1, 'YYYY-MM-DD HH24:MI:SS.FF3'), 'UTC') WHERE "ID" = :2["1999-01-01 18:04:05.006", 1]TLTZ_EQ = EQUAL("1999-01-01T18:04:05.006Z")D(DATE, BC)"-0044-03-15 00:00:00"SET "D" = :1 WHERE "ID" = :2["-0044-03-15 00:00:00", 1]ORA-01861/ORA-01843SET "D" = TO_DATE(:1, 'SYYYY-MM-DD HH24:MI:SS') WHERE "ID" = :2["-0044-03-15 00:00:00", 1]D_EQ = EQUAL("-0044-03-15 00:00:00")TS(TIMESTAMP, BC)"-0044-03-15 10:30:00.5"SET "TS" = :1 WHERE "ID" = :2["-0044-03-15 10:30:00.5", 1]ORA-01843SET "TS" = TO_TIMESTAMP(:1, 'SYYYY-MM-DD HH24:MI:SS.FF') WHERE "ID" = :2["-0044-03-15 10:30:00.5", 1]TS_EQ = EQUAL("-0044-03-15 10:30:00.5")Verbatim run log against
gvenzl/oracle-free:slim:tests/live/oracle-zoneless-values.tsalso passed unchanged against the same container (replayed id=1..4: D EQUAL, TS EQUAL, TTZ EQUAL, TLTZ EQUAL).Type of Change
Related Issue
Closes #1807
(I also opened issue #1807 reporting this bug; builds on my earlier Oracle and SQL Server temporal PRs #1225, #1500, and #1639, plus issue #1498.)
Changes Made
src/lib/export/result-export.ts: Export the Oracle temporal type regexes (ORACLE_TIMESTAMP_TYPE,ORACLE_ZONED_TIMESTAMP_TYPE), format masks (ORACLE_DATE_MASK,ORACLE_BC_DATE_MASK), and text regexes (ORACLE_ZONELESS_TEXT,ISO_INSTANT_TEXT). The export literal helpers remain unchanged.src/hooks/use-inline-editing.ts: AddoracleBoundEditandboundEditValueusing the shared masks and regexes fromresult-export.tsso edits on OracleDATE,TIMESTAMP, andTIMESTAMP WITH [LOCAL] TIME ZONEcolumns bind:nthroughTO_DATE,TO_TIMESTAMP, orFROM_TZ(TO_TIMESTAMP(:n, 'YYYY-MM-DD HH24:MI:SS.FF3'), 'UTC').tests/hooks/use-inline-editing.test.ts: Add 20 unit tests covering all 4 Oracle temporal column types (DATE,TIMESTAMP,TIMESTAMP WITH TIME ZONE,TIMESTAMP WITH LOCAL TIME ZONE), BC years (SYYYY), whole-second vs fractional-secondTIMESTAMP, parameterized and lowercase padded declarations, fallback to plain:1(fractionalDATE, bare date without time, session NLS literal, numeric offset, impossible calendar date2026-02-30, out-of-range month2026-13-01, six-digit signed year,VARCHAR2, undeclared column, absentcolumnTypes), non-Oracle dialects, andNULL.docs/providers/oracle.md: Update §5.3, §5.5, and §10 (supportsInlineRowEdit) with the inline-editing behavior and live measurement on 26ai Free.Testing
Tested locally with
bun run devTested Docker build (
docker build -t libredb-studio .)Added/updated unit tests
Tested with multiple database types (if applicable)
bun test --coverage result-export use-inline-editing: 394 pass, 0 fail (1,120 expect() calls), 100.00% function and 100.00% line coverage on bothsrc/hooks/use-inline-editing.tsandsrc/lib/export/result-export.ts.bun run format,bun run only:check,bun run lint:oxc,bunx eslint src/lib/export/result-export.ts src/hooks/use-inline-editing.ts tests/hooks/use-inline-editing.test.ts(0 errors, 0 new warnings),bun run typecheck,bun run knip,bun run readme:check,bun run channels:showcase:check,bun run distribution:check,bun run security:check, andbun run chart:checkall pass.Live verification on
gvenzl/oracle-free:slim(Oracle AI Database 26ai Free Release 23.26.3.0.0): both the before/after inline-editUPDATEprobe above andtests/live/oracle-zoneless-values.tspass.Test Environment
mainat15411230)gvenzl/oracle-free:slim),oracledb26.0.0Checklist
Additional Notes
WHEREclause primary-key parameters remain plain:nplaceholders becauseuseInlineEditingrequires anid/*_idprimary key column, whereas theSETclause can target any editable column in the result set.