A timestamp that stores NULL and says it worked, and a boolean note that went stale
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 the26.10.1-SNAPSHOTbuild 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.