Details
-
Bug
-
Status: Open (View Workflow)
-
Major
-
Resolution: Unresolved
-
3.5.10
-
None
-
None
Description
SUMMARY (title field):
getString() returns wrong fractional seconds for DATETIME/TIMESTAMP(1-5), and TIME(1-5) over the binary protocol, when the fraction has leading zeros
AFFECTS VERSION:
3.5.3 (the same code is in 3.4.1, 3.5.2 and 3.5.10; other versions not checked)
ENVIRONMENT:
Connector/J 3.5.3, reproduced against both MySQL 8.0.32 (Docker mysql:8.0.32) and MariaDB 10.10 (Docker mariadb:10.10-rc). The results were identical on both servers.
DESCRIPTION:
ResultSet.getString() on a DATETIME
or TIMESTAMP
column with 1 <= n <= 5 returns a wrong value when the fractional part has leading zeros. The fraction comes back as a larger value than what is stored. For example, a stored .029 comes back as .29000, which reads as .290. DATETIME(6) and columns without a fraction are not affected.
STEPS TO REPRODUCE
Create and fill a table:
CREATE TABLE t (id INT, d3 DATETIME(3), ts3 TIMESTAMP(3) NULL, t3 TIME(3));
INSERT INTO t VALUES
(1, '2026-10-09 12:00:00.001', '2026-10-09 12:00:00.001', '12:00:00.001'),
(2, '2026-10-09 12:00:00.029', '2026-10-09 12:00:00.029', '12:00:00.029'),
(3, '2026-10-09 12:00:00.099', '2026-10-09 12:00:00.099', '12:00:00.099'),
(4, '2026-10-09 12:00:00.100', '2026-10-09 12:00:00.100', '12:00:00.100');
Read it back:
try (var rs = conn.createStatement().executeQuery("SELECT d3 FROM t ORDER BY id")) {
while (rs.next())
}
EXPECTED
2026-10-09 12:00:00.001
2026-10-09 12:00:00.029
2026-10-09 12:00:00.099
2026-10-09 12:00:00.100
ACTUAL
2026-10-09 12:00:00.1000
2026-10-09 12:00:00.29000
2026-10-09 12:00:00.99000
2026-10-09 12:00:00.100000
Read as fractions of a second, the first three come back as .100, .290 and .990, ten times too large. Values of .100 and above are correct.
WHICH CASES ARE AFFECTED
Run against a real server, on both MySQL and MariaDB:
DATETIME(3): text protocol wrong, binary protocol wrong
TIMESTAMP(3): text protocol wrong, binary protocol wrong
TIME(3): text protocol correct, binary protocol wrong
DATETIME(6): text protocol correct, binary protocol correct
"Binary protocol" means useServerPrepStmts=true. Using CAST(col AS CHAR) in the SELECT returns the correct values in both protocols, which is how we work around it.
ROOT CAUSE (from reading the source)
In TimestampColumn.decodeStringText and decodeStringBinary, the fraction is formatted as:
String.format(Locale.US, "%0" + this.decimals + "d", modifiedLdt.getNano() / 1000)
getNano() / 1000 is the fraction in microseconds, which can have up to six digits. The width is the column's decimals. For .029 at precision 3, that formats 29000 with %03d. A width is a minimum, so the result is 29000 and no zero padding happens. The output is .29000.
TimeColumn.formatWithMicroseconds has the same pattern in the binary path (padZeros(microseconds, getDecimals())). The text path for TIME returns the server's string unchanged, which is why it is correct there.
The DateTimeException fallback inside decodeStringBinary gets this right. It divides the microseconds by 10^(6 - decimals) before padding to decimals digits.
SUGGESTED FIX (untested)
Scale the microseconds down to decimals digits before padding, as the fallback path already does, for example fill(micros / 10^(6 - decimals), decimals). Apply the same change in TimeColumn.
IMPACT
Any application that reads getString() on these columns can silently get corrupted fractional seconds. There is no error, and the wrong value is a plausible timestamp. We found it in a data replication pipeline where affected values were written to a destination database with the wrong milliseconds.
RELATED
MariaDB Connector/C had the same bug, CONC-168 (https://jira.mariadb.org/browse/CONC-168), fixed in 2.2.3 and 3.0.1. Its example was .001 coming out as .1000 and .010 as .10000 for a DATETIME(3).