← All notes

Notes · 2026-08-28

A timestamp that stores NULL and says it worked, and a boolean note that went stale

ArcadeDB stores a space-separated timestamp with a fraction as NULL under a successful INSERT - the exact form psqlodbc binds. Filed by the project as a critical issue. And the NULL BOOLEAN note from August turned out to be fixed before we published it.

Two behaviours sat in this project's ArcadeDB compatibility notes since 2026-08-28, written down but never filed: a timestamp form that silently stored NULL, and a NULL BOOLEAN that read back as false. Both have now been settled, and in opposite directions. One turned out to be worse than the note said and is a critical issue in ArcadeDB's own tracker. The other had already been fixed before the note was ever published, which is the more useful lesson.

The timestamp: success, and no value

A DATETIME_MICROS property fed the string '2024-02-29 13:45:10.123456' stores NULL. The INSERT reports success. Nothing in the result says a value was dropped.

Either half of that literal on its own is fine:

literal='2024-02-29T13:45:10.123456'   stored=2024-02-29T13:45:10.123456
literal='2024-02-29 13:45:10.123456'   stored=>>> NULL <<<
literal='2024-02-29 13:45:10'          stored=2024-02-29T13:45:10

The T separator round-trips to the microsecond, and the space separator without a fraction round-trips too. Only the combination fails, and the combination is exactly what psqlodbc renders a bound SQL_TYPE_TIMESTAMP parameter as. So an application that binds a timestamp parameter — the ordinary thing to do — writes nothing and is told it wrote something.

ArcadeDB filed this themselves on 2026-09-21 as ArcadeData/arcadedb#8090, labelled severity:critical, after this project's notes were shared with them. Their issue names the cause, which our note could not: a blanket catch in Type.convert() logs the parse failure at FINE and returns null, and the insert carries on. It affects every PostgreSQL-wire client that binds a timestamp against a sub-second-precision property, not just this driver.

The literal is not the only way in. Binding the value as a parameter — the ordinary thing an application does, and the reason the form appears at all — loses it the same way, through psqlodbc and out the other side as Arrow:

INSERT INTO RetestT SET lbl = ?, ts = ?   with datetime(2024, 2, 29, 13, 45, 10, 123456)

DATETIME_MICROS column as Arrow:
   bound-param  -> None   <- value lost server-side

Confirmed on the same 26.9.1 image on 2026-09-21. The INSERT succeeds, SQLExecute returns SQL_SUCCESS, and the column is empty. Nothing between the application and the storage engine has a way to notice.

Until it is fixed, the way through is to write timestamps as ISO text with the T, which is what the compatibility entry for ArcadeDB says and what its parameter settings do.

Update, 2026-09-22: fixed upstream the day after it was filed. Luca Garulli opened PR #8096 the same evening the issue was filed and merged it into main at 04:07 UTC on 2026-09-22, closing #8090 with "Fixed in 26.10.1". Two faults had to line up for the silent loss, and both are addressed: no parser accepted the space-and-fraction spelling, so it is now accepted; and a parse failure was being swallowed and turned into NULL, so an unreadable value now fails the write with an error naming the value. Reads stay deliberately lenient — date() and .asDatetime() still return NULL for something they cannot read — which keeps queries from throwing on stored data.

Retested here on the 26.10.1-SNAPSHOT image the day it was pushed (build 382ae09d, sixty commits past the merge, so the fix is genuinely in it), on the same machine and harness that produced the measurements above:

DATETIME_MICROS literals, psqlodbc 16 -> ArcadeDB 26.10.1-SNAPSHOT
   '2024-02-29T13:45:10.123456'    stores  2024-02-29 13:45:10.123456
   '2024-02-29 13:45:10.123456'    stores  2024-02-29 13:45:10.123456   <- was NULL
   '2024-02-29 13:45:10'           stores  2024-02-29 13:45:10
   'not-a-timestamp'               refuses (SQLSTATE 22023)             <- was NULL

INSERT INTO RetestT SET lbl = ?, ts = ?   with datetime(2024, 2, 29, 13, 45, 10, 123456)
   bound-param                     stores  2024-02-29 13:45:10.123456   <- was NULL

Through adbcBridge the column now arrives as Arrow timestamp[us]; on 26.9.1 the same column came back as a string. A second machine ran an independent route the same day — psycopg over libpq 18 in both the text and the binary parameter format, and psqlodbc 18 — and agreed on every case, including that over ODBC the refusal reaches the caller as a failed SQLExecDirect with SQLSTATE 22023: an exception an application can see, which is the whole point of the change.

One narrower shape is still refused after a space separator: a trailing offset given to the second, '2024-02-29 13:45:10.123456+05:30:00'. It is accepted after a T, and +05, +05:30 and Z are accepted after either separator. That is a small gap rather than the defect this note is about, and it loses nothing silently.

The advice above still holds for anyone on a released build: 26.9.1 is the current release, and ISO text with the T works on every version. This line becomes "fixed in 26.10.1" when that release ships.

The boolean: a note that aged badly

The second note said a NULL BOOLEAN had no NULL state over the wire: row 2's bo went in as NULL and read back as False, so the compatibility entry marks that column not_null.

That was observed on 2026-08-28 against arcadedata/arcadedb:latest, which was 26.8.1 at the time. It is not true any more, and it had already stopped being true before we wrote it up: a project contributor filed the same behaviour as #6674 on 2026-08-24 and closed it the same day, and the fix shipped in 26.9.1 on 2026-09-03.

A retest on 2026-09-21 against the official 26.9.1 image (arcadedata/arcadedb:26.9.1), through the route the compatibility entry actually describes — psqlodbc 16 with BoolsAsChar=0, then adbcBridge into Arrow:

BOOLEAN column as Arrow:
  schema: bool
   true-row    -> True
   false-row   -> False
   null-row    -> None   <- SQL NULL
   absent-row  -> None   <- SQL NULL

A NULL property and an absent property both arrive as SQL NULL, and true/false arrive intact. The behaviour is correct.

What that is worth saying out loud

A compatibility matrix is a set of claims with dates on them, and a claim about a fast-moving database is perishable. This one went stale in nine days, between the observation and the write-up, and the only reason it was caught is that the project read the note and pushed back on it rather than accepting it.

Two habits come out of that, and both are now in this project's routine. Every quirk carries the version it was seen on, so a reader can tell what a cell is actually about. And a quirk that is documented but not filed gets re-tested against the current release before it is repeated anywhere, because "documented, not reported" is exactly where a fixed bug goes to live on.

The ArcadeDB compatibility entry keeps its not_null flag for the boolean column for now, because the matrix row still records 26.9; it moves when the row is re-measured on 26.9.1.

Where these are tracked

  • Timestamp: ArcadeData/arcadedb#8090, severity:critical, filed by the project on 2026-09-21 and closed 2026-09-22 by PR #8096; verified here on the 26.10.1-SNAPSHOT build the same day.
  • Boolean: ArcadeData/arcadedb#6674, closed 2026-08-24 by a project contributor, shipped in 26.9.1, retested here on 2026-09-21.

Both rows are in this project's upstream record, and the ArcadeDB entry with its settings is on the ArcadeDB page.

ArcadeDB published their own account of these findings and the integration behind them on 2026-09-21: ArcadeDB in the Apache Arrow Ecosystem, Through Its PostgreSQL Wire Protocol.