Uploaded image for project: 'MariaDB Server'
  1. MariaDB Server
  2. MDEV-40673

CONCAT silently returns the wrong string, dropping its first argument's value, when that argument is JSON_TYPE() or GET_FORMAT()

    XMLWordPrintable

Details

    • Bug
    • Status: Open (View Workflow)
    • Major
    • Resolution: Unresolved
    • 10.6, 10.11, 11.4, 11.8, 12.3, 13.0
    • None
    • Data types, JSON
    • None

    Description

      CONCAT() returns a string with its first argument's value missing
      entirely when that argument is JSON_TYPE() or GET_FORMAT(). No error
      and no warning is raised, so the wrong answer is silent.

      SELECT JSON_TYPE('{"a":1}');                    -- OBJECT
      SELECT CONCAT(JSON_TYPE('{"a":1}'), 'X');       -- X          <-- wrong
      SELECT GET_FORMAT(DATE, 'USA');                 -- %m.%d.%Y
      SELECT CONCAT(GET_FORMAT(DATE, 'USA'), 'X');    -- X          <-- wrong
      

      Only the first argument position is affected. The same functions
      anywhere else in the argument list answer correctly, which is what
      makes the bug easy to miss:

      SELECT CONCAT('P', JSON_TYPE('{"a":1}'));       -- POBJECT    <-- right
      SELECT CONCAT(JSON_TYPE('{"a":1}'),
                    JSON_TYPE('[1]'));                -- ARRAY      <-- wrong
      

      It is not limited to literals

      The same happens over ordinary table data, so this reaches real
      queries and not only hand-written examples:

      CREATE TABLE t (j JSON);
      INSERT INTO t VALUES ('{"a":1}'), ('[1,2]');
      SELECT CONCAT(JSON_TYPE(j), '.') AS c FROM t;
      

      +------+
      | c    |
      +------+
      | .    |
      | .    |
      +------+
      

      Expected OBJECT. and ARRAY. respectively.

      How far it goes

      Affected, every line below giving the wrong answer:

      SELECT CONCAT(JSON_TYPE('{"a":1}'), 'A', 'B');     -- AB
      SELECT LENGTH(CONCAT(JSON_TYPE('{"a":1}'), 'X'));  -- 1
      SELECT CONCAT(JSON_TYPE('{"a":1}'), '');           -- empty string
       
      SET sql_mode='ORACLE';
      SELECT JSON_TYPE('{"a":1}') || 'X';                -- X
      SET sql_mode=DEFAULT;
      

      Appending an empty string is enough to lose the value, which is the
      smallest form of the bug. LENGTH() shows the bytes are genuinely gone
      rather than merely not displayed. The Oracle-mode concatenation
      operator is a separate implementation and is affected as well.

      Not affected:

      SELECT CONCAT_WS('-', JSON_TYPE('{"a":1}'), 'X');  -- OBJECT-X
      SELECT CONCAT(ENCRYPT('abc', 'ab'), 'X');          -- abFZSxKKdq5s6X
      

      CONCAT_WS accumulates differently. ENCRYPT finishes the same way as
      the two affected functions but then copies the bytes into a buffer of
      its own before returning. Either affected function on its own, outside
      CONCAT, answers correctly.

      Where it comes from

      Both affected functions finish by pointing the output buffer they were
      handed at bytes they do not own, and returning that same buffer:

        /* Item_func_json_type::val_str() */
        str->set(type, strlen(type), &my_charset_utf8mb3_general_ci);
        return str;
      

      String::set() re-points the buffer at a static string, so afterwards
      its allocated length is 0 while its length is 6.

      Item_func_concat::val_str() then sees that the value it got back is
      the very buffer it passed in, and therefore skips the copy_or_move()
      call that would have given its accumulator bytes of its own. Appending
      the next argument reaches Item_func_concat::realloc_result(), which
      tests the allocated length, finds 0, and calls alloc() rather than
      realloc(). Binary_string::real_alloc() sets str_length to 0 - its
      declaration says so outright, "Empties old string" - so the six bytes
      are dropped and the append lands at offset 0.

      The test for an allocated length of 0 reads as "therefore empty",
      which holds for CONCAT's own fresh buffer and does not hold once an
      argument has handed the buffer back pointing at borrowed bytes.

      Affected versions

      Verified by execution:

      • 10.11.19
      • 13.1.0

      Verified by inspection, all three code sites above being unchanged:
      10.5, 10.6, 10.11, 11.4, 11.8 and main. 12.x was not checked
      separately and is listed on the strength of lying between two that
      were.

      How to repeat

      SELECT JSON_TYPE('{"a":1}');
      SELECT CONCAT(JSON_TYPE('{"a":1}'), 'X');
      SELECT CONCAT('P', JSON_TYPE('{"a":1}'));
       
      SELECT GET_FORMAT(DATE, 'USA');
      SELECT CONCAT(GET_FORMAT(DATE, 'USA'), 'X');
       
      CREATE TABLE t (j JSON);
      INSERT INTO t VALUES ('{"a":1}'), ('[1,2]');
      SELECT CONCAT(JSON_TYPE(j), '.') AS c FROM t;
      DROP TABLE t;
      

      Expected, in order: OBJECTX, POBJECT, %m.%d.%YX, and OBJECT. / ARRAY.
      from the table.

      Got, in order: X, POBJECT, X, and . / . from the table.

      Attachments

        Issue Links

          Activity

            People

              danblack Daniel Black
              arcivanov Arcadiy Ivanov
              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.