MariaDB currently ignores trailing spaces when comparing values of the CHAR, VARCHAR or TEXT data types.
For example, these three scripts:
ignore trailing spaces in the column value and in the string constant and return two rows as a result.
This is correct. According to the standard, the behavior of trailing space comparison does not depend on the data type. It depends only on the collation, and namely on its PAD attribute, which can be PAD SPACE or NO PAD.
The SQL standard says:
The comparison of two character string expressions depends on the collation used for the comparison (see Subclause 9.13, “Collation determination”). When values of unequal length are compared, if the collation for the comparison has the NO PAD characteristic and the shorter value is equal to some prefix of the longer value, then the shorter value is considered less than the longer value. If the collation for the comparison has the PAD SPACE characteristic, for the purposes of the comparison, the shorter value is effectively extended to the length of the longer by concatenation of <space>s on the right.
MariaDB currently has PAD SPACE collations only, so trailing spaces are always ignored for the CHAR, VARCHAR and TEXT data types.
In certain cases it would be nice to take trailing spaces into account.
Under terms of this task, we want to add NO PAD variants for all default collations and for all _bin collations.
Note: We eventually want to add NO PAD variants for all other collations. But this will be done separately. This task is only about adding NO PAD variants for default and _bin collations.
New collation names will have the nopad substring. The list of all new desired collations can be extracted using this SQL query:
Suppose we need to add utf8_general_nopad_ci, which will be based on utf8_general_ci but will have the NO PAD attribute.
utf8_general_ci is implemented in strings/ctype-utf8.c.
Adding utf8_general_nopad_ci can be done in these three steps:
1. Add a new collation handler
Copy the collation handler from the existing my_collation_utf8_general_ci_handler which looks like this:
and then replace these three virtual functions to new similar functions that will not ignore trailing spaces:
- my_strnncollsp_utf8_general_ci - this is used for BTREE indexes
- my_hash_sort_utf8 - this is used for HASH indexes
- my_strnxfrm_unicode - this is used for filesort (non-indexed ORDER BY)
All other functions can be reused from the existing PAD SPACE collation.
So the new handler will look about like this:
2. Add a new collation definition by copying it from my_charset_utf8_general_ci
The new definition will look like this:
Notice, it looks very similar to the definition of my_charset_utf8_general_ci but
- has a new distinct ID: 333 (the exact IDs for all new collations will be chosen later)
- has a new distinct name: utf8_general_nopad_ci
- has an extra MY_CS_NOPAD flag
- does not have MY_CS_PRIMARY in flags
- uses the new collation handler my_collation_utf8_general_nopad_ci_handler instead of my_collation_utf8_general_ci_handler
3. Add a collation initialization code
Open mysys/charset-def.c and add an initialization line like this:
Collations for the other Unicode and Asian character sets and for latin1 are to be added using about the same steps (some minor details may differ though).
Collations for 8-bit character sets are to be done in a different way, see section 4.
The task will include tests for the MariaDB "mtr" test infrastructure in the mysql-test source directory.
Tests will cover all new collations, in all parts of SQL queries involving comparison, including:
- Unique indexes
- UNION DISTINCT
- DISTINCT (with and without indexes)
- ORDER BY (with and without indexes)
- GROUP BY (with and without indexes)
- Mixing NO PAD and PAD collations
- Aggregate functions
- MIIN(), MAX()
- SQL functions involving comparison
- LEAST(), GREATEST()
- CASE WHEN a = b THEN ...
- CASE a WHEN a0 THEN ...
Tests should cover MyISAM, HEAP and InnoDB tables.
To simplify adding test, we'll create a shared include file, say mysql-test/include/ctype_pad.inc,
which will be then included in all mysql-test/t/ctype_xxx.test files for individual character sets. See ctype_regex.inc as an example of such shared file.
As collations are quite stand-alone pieces of code and do not affect each other, it's easier to implement new collations one by one, consequently, in separate commits.
Every commit can include collations xxx_nopad_ci and xxx_bin for a single character set and should consist of:
- implementation, as described in "Implementation details" (for Unicode and Asian character sets and for latin1) and in section 4 (for 8-bit character sets)
- Tests, as described in "Testing"
Unlike all other 8-bit character sets, latin1 has a special implementation in ctype-latin1.c, so it's easier to have it in a separate step.
This step will start with introducing new shared handlers:
and use these handlers to actually add new collations for latin1.
Note, the handlers added at this step will be reused for all other 8-bit character sets at the next step.
8-bit collations are defined in ctype-extra.c.
Unlike all other ctype-xxxx.c files, this file is not manually written. It's generated from collation definition files sql/share/charsets/*.xml.
Whenever we modify the collation definition files, we do the following procedure to regenerate ctype-extra.c:
New 8-bit collations should also be generated from the collation definition files.
We'll need the following changes:
The collation definition loader (implemented in ctype.c) should be extended to handle a new flag "nopad", so Index.xml can look like this (notice a new line highlighted):
New _ci collations should use exactly the same collating weights with their PAD SPACE counterparts.
For example, a collating weight table for greek_general_ci resides in greek.xml and looks like this:
It consists of 256 weights, one weight per code. The first weight corresponds the character with the code 0x00, the last weight corresponds to the character with the code 0xFF.
Notice, the characters 0x41 and 0x61 (Latin letters 'a' and 'A') have the same weight of 0x41. This makes the collation case insensitive.
The easiest way would be just to copy-and-paste the weight table definition and use the copy with a new name greek_general_nopad_ci.
But it would be nice to avoid duplication of definitions. We'll extend ctype.c to understand references to other collations instead of explicit weight table definitions, so a new block for the "greek" character set can look like this:
Notice, the "map" attribute has another collation name rather than a full weight table definition.
At this sub-step we'll use the changes made in the collation definition loader mentioned in 4a and 4b, and do the following:
- Edit Index.xml:
- Add new _nopad_bin collations to all 8-bit character sets, as described in the example for "greek" in 4a (one collation per character set)
- Add new _nopad_ci collations to all 8-bit character sets, as described in the example for "greek" in 4b (one collation per character set)
- Extend this block in conf_to_src.c:
to check the MY_CS_NOPAD flag and print my_collation_8bit_nopad_bin_handler and my_collation_8bit_simple_nopad_ci_handler (which we added at step 3) when MY_CS_NOPAD is set.
- Regenerate ctype-extra.c, as described in the beginning of the section 4.
- Add tests for the new 8bit collations.