[patches] [PATCH v2 1/2] sql: make upper lower work over ICU (defult locale)

AKhatskevich avkhatskevich at tarantool.org
Tue Feb 6 15:53:13 MSK 2018


Add support of unicode characters to SQL internal functions `lower` and
`upper`.

This functions work in default locale since now and it is impossible to
change the default locale without recompilation. However it is enough
for most cases.

Closes #2654
---
 src/box/sql/func.c                | 100 ++++++++++++-----------
 test/sql/icu-upper-lower.result   | 165 ++++++++++++++++++++++++++++++++++++++
 test/sql/icu-upper-lower.test.lua |  84 +++++++++++++++++++
 3 files changed, 303 insertions(+), 46 deletions(-)
 create mode 100644 test/sql/icu-upper-lower.result
 create mode 100644 test/sql/icu-upper-lower.test.lua

diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 586a13504..fa4923403 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -35,9 +35,9 @@
  * time functions, are implemented separately.)
  */
 #include "sqliteInt.h"
-#include <stdlib.h>
-#include <assert.h>
 #include "vdbeInt.h"
+#include <unicode/ustring.h>
+#include <unicode/ucasemap.h>
 
 /*
  * Return the collating function associated with a function.
@@ -476,49 +476,49 @@ contextMalloc(sqlite3_context * context, i64 nByte)
 /*
  * Implementation of the upper() and lower() SQL functions.
  */
-static void
-upperFunc(sqlite3_context * context, int argc, sqlite3_value ** argv)
-{
-	char *z1;
-	const char *z2;
-	int i, n;
-	UNUSED_PARAMETER(argc);
-	z2 = (char *)sqlite3_value_text(argv[0]);
-	n = sqlite3_value_bytes(argv[0]);
-	/* Verify that the call to _bytes() does not invalidate the _text() pointer */
-	assert(z2 == (char *)sqlite3_value_text(argv[0]));
-	if (z2) {
-		z1 = contextMalloc(context, ((i64) n) + 1);
-		if (z1) {
-			for (i = 0; i < n; i++) {
-				z1[i] = (char)sqlite3Toupper(z2[i]);
-			}
-			sqlite3_result_text(context, z1, n, sqlite3_free);
-		}
-	}
-}
 
-static void
-lowerFunc(sqlite3_context * context, int argc, sqlite3_value ** argv)
-{
-	char *z1;
-	const char *z2;
-	int i, n;
-	UNUSED_PARAMETER(argc);
-	z2 = (char *)sqlite3_value_text(argv[0]);
-	n = sqlite3_value_bytes(argv[0]);
-	/* Verify that the call to _bytes() does not invalidate the _text() pointer */
-	assert(z2 == (char *)sqlite3_value_text(argv[0]));
-	if (z2) {
-		z1 = contextMalloc(context, ((i64) n) + 1);
-		if (z1) {
-			for (i = 0; i < n; i++) {
-				z1[i] = sqlite3Tolower(z2[i]);
-			}
-			sqlite3_result_text(context, z1, n, sqlite3_free);
-		}
-	}
-}
+static UCaseMap *pUCaseMap;
+
+#define ICU_CASE_CONVERT(case_type)                                            \
+static void                                                                    \
+case_type##ICUFunc(sqlite3_context *context, int argc, sqlite3_value **argv)   \
+{                                                                              \
+	char *z1;                                                              \
+	const char *z2;                                                        \
+	int n;                                                                 \
+	UNUSED_PARAMETER(argc);                                                \
+	z2 = (char *)sqlite3_value_text(argv[0]);                              \
+	n = sqlite3_value_bytes(argv[0]);                                      \
+	/*                                                                     \
+	 * Verify that the call to _bytes()                                    \
+	 * does not invalidate the _text() pointer.                            \
+	 */                                                                    \
+	assert(z2 == (char *)sqlite3_value_text(argv[0]));                     \
+	if (!z2)                                                               \
+		return;                                                        \
+	z1 = contextMalloc(context, ((i64) n) + 1);                            \
+	if (!z1) {                                                             \
+		sqlite3_result_error_nomem(context);                           \
+		return;                                                        \
+	}                                                                      \
+	UErrorCode status = U_ZERO_ERROR;                                      \
+	int len = ucasemap_utf8To##case_type(pUCaseMap, z1, n, z2, n, &status);\
+	if (len > n) {                                                         \
+		status = U_ZERO_ERROR;                                         \
+		sqlite3_free(z1);                                              \
+		z1 = contextMalloc(context, ((i64) len) + 1);                  \
+		if (!z1) {                                                     \
+			sqlite3_result_error_nomem(context);                   \
+			return;                                                \
+		}                                                              \
+		ucasemap_utf8To##case_type(pUCaseMap, z1, len, z2, n, &status);\
+	}                                                                      \
+	sqlite3_result_text(context, z1, len, sqlite3_free);                   \
+}                                                                              \
+
+ICU_CASE_CONVERT(Lower);
+ICU_CASE_CONVERT(Upper);
+
 
 /*
  * Some functions like COALESCE() and IFNULL() and UNLIKELY() are implemented
@@ -1831,6 +1831,14 @@ sqlite3IsLikeFunction(sqlite3 * db, Expr * pExpr, int *pIsNocase, char *aWc)
 void
 sqlite3RegisterBuiltinFunctions(void)
 {
+	/*
+	 * Initialize default case map for UPPER/LOWER functions
+	 * This structure is not freed at db exit, but that is ok.
+	 */
+	UErrorCode status = U_ZERO_ERROR;
+
+	pUCaseMap = ucasemap_open(NULL, 0, &status);
+	assert(pUCaseMap);
 	/*
 	 * The following array holds FuncDef structures for all of the functions
 	 * defined in this file.
@@ -1876,8 +1884,8 @@ sqlite3RegisterBuiltinFunctions(void)
 		FUNCTION(round, 1, 0, 0, roundFunc),
 		FUNCTION(round, 2, 0, 0, roundFunc),
 #endif
-		FUNCTION(upper, 1, 0, 0, upperFunc),
-		FUNCTION(lower, 1, 0, 0, lowerFunc),
+		FUNCTION(upper, 1, 0, 0, UpperICUFunc),
+		FUNCTION(lower, 1, 0, 0, LowerICUFunc),
 		FUNCTION(hex, 1, 0, 0, hexFunc),
 		FUNCTION2(ifnull, 2, 0, 0, noopFunc, SQLITE_FUNC_COALESCE),
 		VFUNCTION(random, 0, 0, 0, randomFunc),
diff --git a/test/sql/icu-upper-lower.result b/test/sql/icu-upper-lower.result
new file mode 100644
index 000000000..9989ed239
--- /dev/null
+++ b/test/sql/icu-upper-lower.result
@@ -0,0 +1,165 @@
+test_run = require('test_run').new()
+---
+...
+test_run:cmd("setopt delimiter ';'")
+---
+- true
+...
+upper_lower_test = function (str)
+    return box.sql.execute(string.format("select lower('%s'), upper('%s')", str, str))
+end;
+---
+...
+-- Some pangrams
+-- Azerbaijanian
+upper_lower_test([[
+    Zəfər, jaketini də, papağını da götür, bu axşam hava çox soyuq olacaq.
+]]);
+---
+- - ['     zəfər, jaketini də, papağını da götür, bu axşam hava çox soyuq olacaq. ',
+    '     ZƏFƏR, JAKETINI DƏ, PAPAĞINI DA GÖTÜR, BU AXŞAM HAVA ÇOX SOYUQ OLACAQ. ']
+...
+upper_lower_test([[
+    The quick brown fox jumps over the lazy dog.
+]]);
+---
+- - ['     the quick brown fox jumps over the lazy dog. ', '     THE QUICK BROWN FOX
+      JUMPS OVER THE LAZY DOG. ']
+...
+-- English
+upper_lower_test([[
+    The quick brown fox jumps over the lazy dog.
+]]);
+---
+- - ['     the quick brown fox jumps over the lazy dog. ', '     THE QUICK BROWN FOX
+      JUMPS OVER THE LAZY DOG. ']
+...
+-- Armenian
+upper_lower_test([[
+    Բել դղյակի ձախ ժամն օֆ ազգությանը ցպահանջ չճշտած վնաս էր եւ փառք
+]]);
+---
+- - ['     բել դղյակի ձախ ժամն օֆ ազգությանը ցպահանջ չճշտած վնաս էր եւ փառք ', '     ԲԵԼ
+      ԴՂՅԱԿԻ ՁԱԽ ԺԱՄՆ ՕՖ ԱԶԳՈՒԹՅԱՆԸ ՑՊԱՀԱՆՋ ՉՃՇՏԱԾ ՎՆԱՍ ԷՐ ԵՒ ՓԱՌՔ ']
+...
+-- Belarussian
+upper_lower_test([[
+    У Іўі худы жвавы чорт у зялёнай камізэльцы пабег пад’есці фаршу з юшкай
+]]);
+---
+- - ['     у іўі худы жвавы чорт у зялёнай камізэльцы пабег пад’есці фаршу з юшкай ',
+    '     У ІЎІ ХУДЫ ЖВАВЫ ЧОРТ У ЗЯЛЁНАЙ КАМІЗЭЛЬЦЫ ПАБЕГ ПАД’ЕСЦІ ФАРШУ З ЮШКАЙ ']
+...
+-- Greek
+upper_lower_test([[
+    Τάχιστη αλώπηξ βαφής ψημένη γη, δρασκελίζει υπέρ νωθρού κυνός
+]]);
+---
+- - ['     τάχιστη αλώπηξ βαφής ψημένη γη, δρασκελίζει υπέρ νωθρού κυνός ', '     ΤΆΧΙΣΤΗ
+      ΑΛΏΠΗΞ ΒΑΦΉΣ ΨΗΜΈΝΗ ΓΗ, ΔΡΑΣΚΕΛΊΖΕΙ ΥΠΈΡ ΝΩΘΡΟΎ ΚΥΝΌΣ ']
+...
+-- Irish
+upper_lower_test([[
+    Chuaigh bé mhórshách le dlúthspád fíorfhinn trí hata mo dhea-phorcáin bhig
+]]);
+---
+- - ['     chuaigh bé mhórshách le dlúthspád fíorfhinn trí hata mo dhea-phorcáin bhig ',
+    '     CHUAIGH BÉ MHÓRSHÁCH LE DLÚTHSPÁD FÍORFHINN TRÍ HATA MO DHEA-PHORCÁIN BHIG ']
+...
+-- Spain
+upper_lower_test([[
+    Quiere la boca exhausta vid, kiwi, piña y fugaz jamón
+]]);
+---
+- - ['     quiere la boca exhausta vid, kiwi, piña y fugaz jamón ', '     QUIERE LA
+      BOCA EXHAUSTA VID, KIWI, PIÑA Y FUGAZ JAMÓN ']
+...
+-- Korean
+upper_lower_test([[
+    키스의 고유조건은 입술끼리 만나야 하고 특별한 기술은 필요치 않다
+]]);
+---
+- - ['     키스의 고유조건은 입술끼리 만나야 하고 특별한 기술은 필요치 않다 ', '     키스의 고유조건은 입술끼리 만나야 하고 특별한
+      기술은 필요치 않다 ']
+...
+-- Latvian
+upper_lower_test([[
+    Glāžšķūņa rūķīši dzērumā čiepj Baha koncertflīģeļu vākus
+]]);
+---
+- - ['     glāžšķūņa rūķīši dzērumā čiepj baha koncertflīģeļu vākus ', '     GLĀŽŠĶŪŅA
+      RŪĶĪŠI DZĒRUMĀ ČIEPJ BAHA KONCERTFLĪĢEĻU VĀKUS ']
+...
+-- German
+upper_lower_test([[
+    Zwölf große Boxkämpfer jagen Viktor quer über den Sylter Deich
+]]);
+---
+- - ['     zwölf große boxkämpfer jagen viktor quer über den sylter deich ', '     ZWÖLF
+      GROSSE BOXKÄMPFER JAGEN VIKTOR QUER ÜBER DEN SYLTER DEICH ']
+...
+-- Polish
+upper_lower_test([[
+    Pchnąć w tę łódź jeża lub ośm skrzyń fig.
+]]);
+---
+- - ['     pchnąć w tę łódź jeża lub ośm skrzyń fig. ', '     PCHNĄĆ W TĘ ŁÓDŹ JEŻA
+      LUB OŚM SKRZYŃ FIG. ']
+...
+-- Ukrainian
+upper_lower_test([[
+    Чуєш їх, доцю, га? Кумедна ж ти, прощайся без ґольфів!
+]]);
+---
+- - ['     чуєш їх, доцю, га? кумедна ж ти, прощайся без ґольфів! ', '     ЧУЄШ ЇХ,
+      ДОЦЮ, ГА? КУМЕДНА Ж ТИ, ПРОЩАЙСЯ БЕЗ ҐОЛЬФІВ! ']
+...
+-- Czech
+upper_lower_test([[
+    Příliš žluťoučký kůň úpěl ďábelské ódy
+]]);
+---
+- - ['     příliš žluťoučký kůň úpěl ďábelské ódy ', '     PŘÍLIŠ ŽLUŤOUČKÝ KŮŇ ÚPĚL
+      ĎÁBELSKÉ ÓDY ']
+...
+-- Esperanto
+upper_lower_test([[
+    Laŭ Ludoviko Zamenhof bongustas freŝa ĉeĥa manĝaĵo kun spicoj
+]]);
+---
+- - ['     laŭ ludoviko zamenhof bongustas freŝa ĉeĥa manĝaĵo kun spicoj ', '     LAŬ
+      LUDOVIKO ZAMENHOF BONGUSTAS FREŜA ĈEĤA MANĜAĴO KUN SPICOJ ']
+...
+-- Japanese
+upper_lower_test([[
+    いろはにほへと ちりぬるを わかよたれそ つねならむ うゐのおくやま けふこえて あさきゆめみし ゑひもせす
+]]);
+---
+- - ['     いろはにほへと ちりぬるを わかよたれそ つねならむ うゐのおくやま けふこえて あさきゆめみし ゑひもせす ', '     いろはにほへと
+      ちりぬるを わかよたれそ つねならむ うゐのおくやま けふこえて あさきゆめみし ゑひもせす ']
+...
+-- Turkish
+upper_lower_test([[
+    Pijamalı hasta yağız şoföre çabucak güvendi. EXTRA: İ
+]]);
+---
+- - ['     pijamalı hasta yağız şoföre çabucak güvendi. extra: i̇ ', '     PIJAMALI
+      HASTA YAĞIZ ŞOFÖRE ÇABUCAK GÜVENDI. EXTRA: İ ']
+...
+test_run:cmd("setopt delimiter ''");
+---
+- true
+...
+-- Bad test cases
+box.sql.execute("select upper('1', 2)")
+---
+- error: wrong number of arguments to function UPPER()
+...
+box.sql.execute("select upper(\"1\")")
+---
+- error: 'no such column: 1'
+...
+box.sql.execute("select upper()")
+---
+- error: wrong number of arguments to function UPPER()
+...
diff --git a/test/sql/icu-upper-lower.test.lua b/test/sql/icu-upper-lower.test.lua
new file mode 100644
index 000000000..bb1a5189b
--- /dev/null
+++ b/test/sql/icu-upper-lower.test.lua
@@ -0,0 +1,84 @@
+test_run = require('test_run').new()
+
+test_run:cmd("setopt delimiter ';'")
+
+upper_lower_test = function (str)
+    return box.sql.execute(string.format("select lower('%s'), upper('%s')", str, str))
+end;
+
+-- Some pangrams
+-- Azerbaijanian
+upper_lower_test([[
+    Zəfər, jaketini də, papağını da götür, bu axşam hava çox soyuq olacaq.
+]]);
+upper_lower_test([[
+    The quick brown fox jumps over the lazy dog.
+]]);
+-- English
+upper_lower_test([[
+    The quick brown fox jumps over the lazy dog.
+]]);
+-- Armenian
+upper_lower_test([[
+    Բել դղյակի ձախ ժամն օֆ ազգությանը ցպահանջ չճշտած վնաս էր եւ փառք
+]]);
+-- Belarussian
+upper_lower_test([[
+    У Іўі худы жвавы чорт у зялёнай камізэльцы пабег пад’есці фаршу з юшкай
+]]);
+-- Greek
+upper_lower_test([[
+    Τάχιστη αλώπηξ βαφής ψημένη γη, δρασκελίζει υπέρ νωθρού κυνός
+]]);
+-- Irish
+upper_lower_test([[
+    Chuaigh bé mhórshách le dlúthspád fíorfhinn trí hata mo dhea-phorcáin bhig
+]]);
+-- Spain
+upper_lower_test([[
+    Quiere la boca exhausta vid, kiwi, piña y fugaz jamón
+]]);
+-- Korean
+upper_lower_test([[
+    키스의 고유조건은 입술끼리 만나야 하고 특별한 기술은 필요치 않다
+]]);
+-- Latvian
+upper_lower_test([[
+    Glāžšķūņa rūķīši dzērumā čiepj Baha koncertflīģeļu vākus
+]]);
+-- German
+upper_lower_test([[
+    Zwölf große Boxkämpfer jagen Viktor quer über den Sylter Deich
+]]);
+-- Polish
+upper_lower_test([[
+    Pchnąć w tę łódź jeża lub ośm skrzyń fig.
+]]);
+-- Ukrainian
+upper_lower_test([[
+    Чуєш їх, доцю, га? Кумедна ж ти, прощайся без ґольфів!
+]]);
+-- Czech
+upper_lower_test([[
+    Příliš žluťoučký kůň úpěl ďábelské ódy
+]]);
+-- Esperanto
+upper_lower_test([[
+    Laŭ Ludoviko Zamenhof bongustas freŝa ĉeĥa manĝaĵo kun spicoj
+]]);
+-- Japanese
+upper_lower_test([[
+    いろはにほへと ちりぬるを わかよたれそ つねならむ うゐのおくやま けふこえて あさきゆめみし ゑひもせす
+]]);
+-- Turkish
+upper_lower_test([[
+    Pijamalı hasta yağız şoföre çabucak güvendi. EXTRA: İ
+]]);
+
+test_run:cmd("setopt delimiter ''");
+
+-- Bad test cases
+box.sql.execute("select upper('1', 2)")
+box.sql.execute("select upper(\"1\")")
+box.sql.execute("select upper()")
+
-- 
2.14.1




More information about the Tarantool-patches mailing list