Uploaded image for project: 'MariaDB Connector/J'
  1. MariaDB Connector/J
  2. CONJ-1385

getString() returns wrong fractional seconds for DATETIME/TIMESTAMP(1-5), and TIME(1-5) over the binary protocol, when the fraction has leading zeros

    XMLWordPrintable

Details

    • Bug
    • Status: Open (View Workflow)
    • Major
    • Resolution: Unresolved
    • 3.5.10
    • None
    • datetime
    • 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())

      { System.out.println(rs.getString(1)); }

      }

      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).

      Attachments

        Activity

          People

            diego dupin Diego Dupin
            matt.alexander Matthew Alexander
            Votes:
            0 Vote for this issue
            Watchers:
            1 Start watching this issue

            Dates

              Created:
              Updated:

              Git Integration

                Error rendering 'com.xiplink.jira.git.jira_git_plugin:git-issue-webpanel'. Please contact your Jira administrators.