cordova-sqlite-evmax-build-free 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGES.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # Changes
2
2
 
3
- # cordova-sqlite-evmax-build-free 0.1.0
3
+ # cordova-sqlite-evmax-build-free 0.2.0
4
+
5
+ - add ICU support for Android & iOS using built-in platform ICU on each platform (requires Android 12+)
4
6
 
5
7
  ## cordova-sqlite-evmax-feat-android-db-location 0.0.3-dev
6
8
 
package/README.md CHANGED
@@ -54,7 +54,11 @@ under consideration:
54
54
 
55
55
  ## About this plugin version
56
56
 
57
- Super-premium enterprise version with additional performance and stability improvements for Android, iOS, ~~and macOS,~~ including workarounds for super-large INSERT transactions and SELECT results on Android - with limited extra features (missing pre-populated database support), using the `before_plugin_install` hook to fetch the sqlite3 component dependencies from <https://github.com/brodybits/cordova-sqlite-evmax-free-dependencies-dev>.
57
+ Super-premium enterprise version with additional performance and stability improvements for Android, iOS, ~~and macOS,~~ including workarounds for super-large INSERT transactions and SELECT results on Android - with limited extra features (missing pre-populated database support)
58
+
59
+ with ICU support for Android & iOS using built-in platform ICU on each platform, using Android JAR built from: https://github.com/brody4hire/android-sqlite-evmax-ndk-driver-free/tree/evmax-icu-support-2026-01
60
+
61
+ NOTE that this should work for UPPER, LOWER, & LIKE functions. REGEXP & `icu_load_collation` cannot be supported consistently by built-in platform ICU on all platforms.
58
62
 
59
63
  <!-- FUTURE TBD critical bug notices for this plugin version -->
60
64
 
@@ -252,7 +256,7 @@ See the [Sample section](#sample) for a sample with a more detailed explanation
252
256
  - The **macOS** platform version (**"osx" platform**) is not tested in a release build and should be considered pre-alpha with known issues:
253
257
  - `cordova prepare osx` is needed before building and running from Xcode
254
258
  - known issue between `cordova-osx` and Cordova CLI `10.0.0`: <https://github.com/apache/cordova-osx/issues/106>
255
- - Android versions supported: _minimum 5.1, see also: <https://cordova.apache.org/docs/en/latest/guide/platforms/android/>_
259
+ - Android versions supported: _minimum 12, see also: <https://cordova.apache.org/docs/en/latest/guide/platforms/android/>_
256
260
  - iOS versions supported: 8.x / 9.x / 10.x / 11.x / 12.x (see [deviations section](#deviations) below for differences in case of WKWebView)
257
261
  - FTS3, FTS4, and R-Tree are fully tested and supported for all target platforms in this version branch.
258
262
  - Default `PRAGMA journal_mode` setting (*tested*):
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cordova-sqlite-evmax-build-free",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Cordova/PhoneGap sqlite storage - free evmax enterprise version with premium stability and performance improvements including workaround for super-large INSERT transactions & SELECT results on Android (version with external sqlite3 dependencies)",
5
5
  "cordova": {
6
6
  "id": "cordova-sqlite-evmax-build-free",
package/plugin.xml CHANGED
@@ -2,7 +2,7 @@
2
2
  <plugin xmlns="http://www.phonegap.com/ns/plugins/1.0"
3
3
  xmlns:android="http://schemas.android.com/apk/res/android"
4
4
  id="cordova-sqlite-evmax-build-free"
5
- version="0.1.0">
5
+ version="0.2.0">
6
6
 
7
7
  <name>Cordova sqlite storage - free evmax common version branch with premium stability performance improvements including workaround for super-large INSERT transactions and SELECT results on Android (with external sqlite3 dependencies)</name>
8
8
 
@@ -67,6 +67,9 @@
67
67
  <header-file src="src/deps/common/sqlite3.h" />
68
68
  <source-file src="src/deps/common/sqlite3.c"
69
69
  compiler-flags="-w -DSQLITE_THREADSAFE=1 -DSQLITE_DEFAULT_SYNCHRONOUS=3 -DSQLITE_LOCKING_STYLE=1 -DHAVE_USLEEP=1 -DSQLITE_DEFAULT_MEMSTATUS=0 -DSQLITE_OMIT_DECLTYPE -DSQLITE_OMIT_DEPRECATED -DSQLITE_OMIT_PROGRESS_CALLBACK -DSQLITE_OMIT_SHARED_CACHE -DSQLITE_TEMP_STORE=2 -DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_ENABLE_FTS3 -DSQLITE_ENABLE_FTS3_PARENTHESIS -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_FTS5 -DSQLITE_ENABLE_RTREE -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_MATH_FUNCTIONS -DSQLITE_DEFAULT_PAGE_SIZE=4096 -DSQLITE_DEFAULT_CACHE_SIZE=-2000" />
70
+
71
+ <header-file src="src/icu/sqliteicu.h" />
72
+ <source-file src="src/icu/icu.c" />
70
73
  </platform>
71
74
 
72
75
  <!-- macOS (osx) -->
@@ -1094,6 +1094,7 @@ var mytests = function() {
1094
1094
  // - plugin with androidDatabaseImplementation: 2 on
1095
1095
  // Android 4.4 & newer
1096
1096
  if ((isWebSql && isChromeBrowser) ||
1097
+ !isWebSql || // PLUGIN WITH BUILT-IN ICU SUPPORT
1097
1098
  (isAndroid && ((isWebSql && isAndroid && !(/Android 4.[1-3]/.test(navigator.userAgent))) || (isImpl2 && /Android [5-9]/.test(navigator.userAgent)))))
1098
1099
  expect(resultRow1.myresult).toBe('AÉ');
1099
1100
  else
@@ -1112,6 +1113,7 @@ var mytests = function() {
1112
1113
  // - plugin with androidDatabaseImplementation: 2 on
1113
1114
  // Android 4.4 & newer
1114
1115
  if ((isWebSql && isChromeBrowser) ||
1116
+ !isWebSql || // PLUGIN WITH BUILT-IN ICU SUPPORT
1115
1117
  (isAndroid && ((isWebSql && isAndroid && !(/Android 4.[1-3]/.test(navigator.userAgent))) || (isImpl2 && /Android [5-9]/.test(navigator.userAgent)))))
1116
1118
  expect(resultRow2.myresult).toBe('BÉ');
1117
1119
  else
@@ -1150,6 +1152,7 @@ var mytests = function() {
1150
1152
  // - plugin with androidDatabaseImplementation: 2 on
1151
1153
  // Android 4.4 & newer
1152
1154
  if ((isWebSql && isChromeBrowser) ||
1155
+ !isWebSql || // PLUGIN WITH BUILT-IN ICU SUPPORT
1153
1156
  (isAndroid && ((isWebSql && isAndroid && !(/Android 4.[1-3]/.test(navigator.userAgent))) || (isImpl2 && /Android [5-9]/.test(navigator.userAgent)))))
1154
1157
  expect(resultRow1.myresult).toBe('aé');
1155
1158
  else
@@ -1168,6 +1171,7 @@ var mytests = function() {
1168
1171
  // - plugin with androidDatabaseImplementation: 2 on
1169
1172
  // Android 4.4 & newer
1170
1173
  if ((isWebSql && isChromeBrowser) ||
1174
+ !isWebSql || // PLUGIN WITH BUILT-IN ICU SUPPORT
1171
1175
  (isAndroid && ((isWebSql && isAndroid && !(/Android 4.[1-3]/.test(navigator.userAgent))) || (isImpl2 && /Android [5-9]/.test(navigator.userAgent)))))
1172
1176
  expect(resultRow2.myresult).toBe('bé');
1173
1177
  else
@@ -1206,6 +1210,7 @@ var mytests = function() {
1206
1210
  // - plugin with androidDatabaseImplementation: 2 on
1207
1211
  // Android 4.4 & newer
1208
1212
  if ((isWebSql && isChromeBrowser) ||
1213
+ !isWebSql || // PLUGIN WITH BUILT-IN ICU SUPPORT
1209
1214
  (isAndroid && ((isWebSql && isAndroid && !(/Android 4.[1-3]/.test(navigator.userAgent))) || (isImpl2 && /Android [5-9]/.test(navigator.userAgent)))))
1210
1215
  expect(resultRow1.myresult).toBe('STRASSE');
1211
1216
  else
@@ -1224,6 +1229,7 @@ var mytests = function() {
1224
1229
  // - plugin with androidDatabaseImplementation: 2 on
1225
1230
  // Android 4.4 & newer
1226
1231
  if ((isWebSql && isChromeBrowser) ||
1232
+ !isWebSql || // PLUGIN WITH BUILT-IN ICU SUPPORT
1227
1233
  (isAndroid && ((isWebSql && isAndroid && !(/Android 4.[1-3]/.test(navigator.userAgent))) || (isImpl2 && /Android [5-9]/.test(navigator.userAgent)))))
1228
1234
  expect(resultRow2.myresult).toBe('STRASSE');
1229
1235
  else
@@ -1434,6 +1440,7 @@ var mytests = function() {
1434
1440
  // - plugin with androidDatabaseImplementation: 2 on
1435
1441
  // Android 4.4 & newer
1436
1442
  if ((isWebSql && isChromeBrowser) ||
1443
+ !isWebSql || // PLUGIN WITH BUILT-IN ICU SUPPORT
1437
1444
  (isAndroid && ((isWebSql && isAndroid && !(/Android 4.[1-3]/.test(navigator.userAgent))) || (isImpl2 && /Android [5-9]/.test(navigator.userAgent)))))
1438
1445
  expect(resultRow1.myresult).toBe('straße');
1439
1446
  else
@@ -1452,6 +1459,7 @@ var mytests = function() {
1452
1459
  // - plugin with androidDatabaseImplementation: 2 on
1453
1460
  // Android 4.4 & newer
1454
1461
  if ((isWebSql && isChromeBrowser) ||
1462
+ !isWebSql || // PLUGIN WITH BUILT-IN ICU SUPPORT
1455
1463
  (isAndroid && ((isWebSql && isAndroid && !(/Android 4.[1-3]/.test(navigator.userAgent))) || (isImpl2 && /Android [5-9]/.test(navigator.userAgent)))))
1456
1464
  expect(resultRow2.myresult).toBe('straße');
1457
1465
  else
@@ -1597,6 +1605,7 @@ var mytests = function() {
1597
1605
  });
1598
1606
  }, MYTIMEOUT);
1599
1607
 
1608
+ /* ** NOT SUPPORTING OR TESTING THIS CASE WITH BUILT-IN ICU LIB:
1600
1609
  it(suiteName + "SELECT LOWER(X'41EDA080EDBCB1') - result column value is '\\uED41\u80A0\\uBCED' ('\uED41\u80A0\uBCED') on Android 4.1-4.3 (WebKit) Web SQL & Windows (UTF-16le), 'a\uD800\uDF31' (non-standard encoding) on Android with default Android NDK provider on all Android versions & androidDatabaseProvider: 'system' on Android 4.x, MISSING on iOS/macOS plugin, 'a\\uFFFD\\uFFFD' ('a\uFFFD\uFFFD') on Android with androidDatabaseProvider: 'system' on Android post-4.x & (WebKit) Web SQL (Android post-4.3/iOS/Browser)", function(done) {
1601
1610
  // ref:
1602
1611
  // - litehelpers/Cordova-sqlite-evcore-extbuild-free#44
@@ -1630,6 +1639,7 @@ var mytests = function() {
1630
1639
  (isWebSql) ? done() : db.close(done, done);
1631
1640
  });
1632
1641
  }, MYTIMEOUT);
1642
+ ** NOT SUPPORTING OR TESTING THIS CASE WITH BUILT-IN ICU LIB */
1633
1643
 
1634
1644
  it(suiteName + 'Inline emoji string manipulation test: SELECT UPPER("a\\uD83D\\uDE03.") [\\u1F603 SMILING FACE (MOUTH OPEN)] - ENCODING ISSUE NOW FIXED on default Android SQLite3 NDK [evplus] implementation for Android post-5.x', function(done) {
1635
1645
  // ref:
@@ -1777,6 +1787,7 @@ var mytests = function() {
1777
1787
  });
1778
1788
  }, MYTIMEOUT);
1779
1789
 
1790
+ /* ** NOT SUPPORTING OR TESTING THIS CASE WITH BUILT-IN ICU LIB:
1780
1791
  it(suiteName + "SELECT LOWER(X'41EDA0BDEDB88321') - result column value is '\\uED41\\uBDA0\\uB8ED\\u2183' ('\uED41\uBDA0\uB8ED\u2183') on Android 4.1-4.3 (WebKit) Web SQL & Windows (UTF-16le), 'a\\uD83D\\uDE03!' ('a\uD83D\uDE03!') [non-standard encoding] on Android with default Android NDK provider on all Android versions & androidDatabaseProvider: 'system' on Android 4.x, MISSING on iOS/macOS plugin, '\\uED41\\uBDA0\\uB8ED\\u2183' ('\uED41\uBDA0\uB8ED\u2183') on Android with androidDatabaseProvider: 'system' on Android post-4.x & (WebKit) Web SQL (Android post-4.3/iOS/Browser)", function(done) {
1781
1792
  // ref:
1782
1793
  // - litehelpers/Cordova-sqlite-evcore-extbuild-free#44
@@ -1822,6 +1833,7 @@ var mytests = function() {
1822
1833
  done.fail();
1823
1834
  });
1824
1835
  }, MYTIMEOUT);
1836
+ ** NOT SUPPORTING OR TESTING THIS CASE WITH BUILT-IN ICU LIB */
1825
1837
 
1826
1838
  // NOTE: the next 3 tests show that for iOS/macOS/Android:
1827
1839
  // - UNICODE \u2028 line separator from JavaScript to native (Objective-C/Java) is working OK
@@ -2030,6 +2042,7 @@ var mytests = function() {
2030
2042
  // - plugin with androidDatabaseImplementation: 2 on
2031
2043
  // Android 4.4 & newer
2032
2044
  if ((isWebSql && isChromeBrowser) ||
2045
+ !isWebSql || // PLUGIN WITH BUILT-IN ICU SUPPORT
2033
2046
  (isAndroid && ((isWebSql && !(/Android 4.[1-3]/.test(navigator.userAgent))) || (isImpl2 && /Android [5-9]/.test(navigator.userAgent)))))
2034
2047
  expect(rs.rows.item(0).upper_result).toBe('TEST ¢ É €');
2035
2048
  else
@@ -2062,6 +2075,7 @@ var mytests = function() {
2062
2075
  // - plugin with androidDatabaseImplementation: 2 on
2063
2076
  // Android 4.4 & newer
2064
2077
  if ((isWebSql && isChromeBrowser) ||
2078
+ !isWebSql || // PLUGIN WITH BUILT-IN ICU SUPPORT
2065
2079
  (isAndroid && ((isWebSql && !(/Android 4.[1-3]/.test(navigator.userAgent))) || (isImpl2 && /Android [5-9]/.test(navigator.userAgent)))))
2066
2080
  expect(rs.rows.item(0).upper_result).toBe('TEST ¢ É €');
2067
2081
  else
package/src/icu/icu.c ADDED
@@ -0,0 +1,596 @@
1
+ /*
2
+ ** 2007 May 6
3
+ **
4
+ ** The author disclaims copyright to this source code. In place of
5
+ ** a legal notice, here is a blessing:
6
+ **
7
+ ** May you do good and not evil.
8
+ ** May you find forgiveness for yourself and forgive others.
9
+ ** May you share freely, never taking more than you give.
10
+ **
11
+ *************************************************************************
12
+ ** $Id: icu.c,v 1.7 2007/12/13 21:54:11 drh Exp $
13
+ **
14
+ ** This file implements an integration between the ICU library
15
+ ** ("International Components for Unicode", an open-source library
16
+ ** for handling unicode data) and SQLite. The integration uses
17
+ ** ICU to provide the following to SQLite:
18
+ **
19
+ ** * An implementation of the SQL regexp() function (and hence REGEXP
20
+ ** operator) using the ICU uregex_XX() APIs.
21
+ **
22
+ ** * Implementations of the SQL scalar upper() and lower() functions
23
+ ** for case mapping.
24
+ **
25
+ ** * Integration of ICU and SQLite collation sequences.
26
+ **
27
+ ** * An implementation of the LIKE operator that uses ICU to
28
+ ** provide case-independent matching.
29
+ */
30
+
31
+ #if !defined(SQLITE_CORE) \
32
+ || defined(SQLITE_ENABLE_ICU) \
33
+ || defined(SQLITE_ENABLE_ICU_COLLATIONS)
34
+
35
+ /* Include ICU headers */
36
+ #include <unicode/utypes.h>
37
+ #include <unicode/uregex.h>
38
+ #include <unicode/ustring.h>
39
+ #if 0 // AVOID BUILD ISSUE WITH COLLATION
40
+ #include <unicode/ucol.h>
41
+ #endif // AVOID BUILD ISSUE WITH COLLATION
42
+
43
+ #include <assert.h>
44
+
45
+ #ifndef SQLITE_CORE
46
+ #include "sqlite3ext.h"
47
+ SQLITE_EXTENSION_INIT1
48
+ #else
49
+ #include "sqlite3.h"
50
+ #endif
51
+
52
+ /*
53
+ ** This function is called when an ICU function called from within
54
+ ** the implementation of an SQL scalar function returns an error.
55
+ **
56
+ ** The scalar function context passed as the first argument is
57
+ ** loaded with an error message based on the following two args.
58
+ */
59
+ static void icuFunctionError(
60
+ sqlite3_context *pCtx, /* SQLite scalar function context */
61
+ const char *zName, /* Name of ICU function that failed */
62
+ UErrorCode e /* Error code returned by ICU function */
63
+ ){
64
+ char zBuf[128];
65
+ sqlite3_snprintf(128, zBuf, "ICU error: %s(): %s", zName, u_errorName(e));
66
+ zBuf[127] = '\0';
67
+ sqlite3_result_error(pCtx, zBuf, -1);
68
+ }
69
+
70
+ #if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_ICU)
71
+
72
+ /*
73
+ ** Maximum length (in bytes) of the pattern in a LIKE or GLOB
74
+ ** operator.
75
+ */
76
+ #ifndef SQLITE_MAX_LIKE_PATTERN_LENGTH
77
+ # define SQLITE_MAX_LIKE_PATTERN_LENGTH 50000
78
+ #endif
79
+
80
+ /*
81
+ ** Version of sqlite3_free() that is always a function, never a macro.
82
+ */
83
+ static void xFree(void *p){
84
+ sqlite3_free(p);
85
+ }
86
+
87
+ /*
88
+ ** This lookup table is used to help decode the first byte of
89
+ ** a multi-byte UTF8 character. It is copied here from SQLite source
90
+ ** code file utf8.c.
91
+ */
92
+ static const unsigned char icuUtf8Trans1[] = {
93
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
94
+ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
95
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
96
+ 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
97
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
98
+ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
99
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
100
+ 0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x00, 0x00,
101
+ };
102
+
103
+ #define SQLITE_ICU_READ_UTF8(zIn, c) \
104
+ c = *(zIn++); \
105
+ if( c>=0xc0 ){ \
106
+ c = icuUtf8Trans1[c-0xc0]; \
107
+ while( (*zIn & 0xc0)==0x80 ){ \
108
+ c = (c<<6) + (0x3f & *(zIn++)); \
109
+ } \
110
+ }
111
+
112
+ #define SQLITE_ICU_SKIP_UTF8(zIn) \
113
+ assert( *zIn ); \
114
+ if( *(zIn++)>=0xc0 ){ \
115
+ while( (*zIn & 0xc0)==0x80 ){zIn++;} \
116
+ }
117
+
118
+
119
+ /*
120
+ ** Compare two UTF-8 strings for equality where the first string is
121
+ ** a "LIKE" expression. Return true (1) if they are the same and
122
+ ** false (0) if they are different.
123
+ */
124
+ static int icuLikeCompare(
125
+ const uint8_t *zPattern, /* LIKE pattern */
126
+ const uint8_t *zString, /* The UTF-8 string to compare against */
127
+ const UChar32 uEsc /* The escape character */
128
+ ){
129
+ static const uint32_t MATCH_ONE = (uint32_t)'_';
130
+ static const uint32_t MATCH_ALL = (uint32_t)'%';
131
+
132
+ int prevEscape = 0; /* True if the previous character was uEsc */
133
+
134
+ while( 1 ){
135
+
136
+ /* Read (and consume) the next character from the input pattern. */
137
+ uint32_t uPattern;
138
+ SQLITE_ICU_READ_UTF8(zPattern, uPattern);
139
+ if( uPattern==0 ) break;
140
+
141
+ /* There are now 4 possibilities:
142
+ **
143
+ ** 1. uPattern is an unescaped match-all character "%",
144
+ ** 2. uPattern is an unescaped match-one character "_",
145
+ ** 3. uPattern is an unescaped escape character, or
146
+ ** 4. uPattern is to be handled as an ordinary character
147
+ */
148
+ if( uPattern==MATCH_ALL && !prevEscape && uPattern!=(uint32_t)uEsc ){
149
+ /* Case 1. */
150
+ uint8_t c;
151
+
152
+ /* Skip any MATCH_ALL or MATCH_ONE characters that follow a
153
+ ** MATCH_ALL. For each MATCH_ONE, skip one character in the
154
+ ** test string.
155
+ */
156
+ while( (c=*zPattern) == MATCH_ALL || c == MATCH_ONE ){
157
+ if( c==MATCH_ONE ){
158
+ if( *zString==0 ) return 0;
159
+ SQLITE_ICU_SKIP_UTF8(zString);
160
+ }
161
+ zPattern++;
162
+ }
163
+
164
+ if( *zPattern==0 ) return 1;
165
+
166
+ while( *zString ){
167
+ if( icuLikeCompare(zPattern, zString, uEsc) ){
168
+ return 1;
169
+ }
170
+ SQLITE_ICU_SKIP_UTF8(zString);
171
+ }
172
+ return 0;
173
+
174
+ }else if( uPattern==MATCH_ONE && !prevEscape && uPattern!=(uint32_t)uEsc ){
175
+ /* Case 2. */
176
+ if( *zString==0 ) return 0;
177
+ SQLITE_ICU_SKIP_UTF8(zString);
178
+
179
+ }else if( uPattern==(uint32_t)uEsc && !prevEscape ){
180
+ /* Case 3. */
181
+ prevEscape = 1;
182
+
183
+ }else{
184
+ /* Case 4. */
185
+ uint32_t uString;
186
+ SQLITE_ICU_READ_UTF8(zString, uString);
187
+ uString = (uint32_t)u_foldCase((UChar32)uString, U_FOLD_CASE_DEFAULT);
188
+ uPattern = (uint32_t)u_foldCase((UChar32)uPattern, U_FOLD_CASE_DEFAULT);
189
+ if( uString!=uPattern ){
190
+ return 0;
191
+ }
192
+ prevEscape = 0;
193
+ }
194
+ }
195
+
196
+ return *zString==0;
197
+ }
198
+
199
+ /*
200
+ ** Implementation of the like() SQL function. This function implements
201
+ ** the build-in LIKE operator. The first argument to the function is the
202
+ ** pattern and the second argument is the string. So, the SQL statements:
203
+ **
204
+ ** A LIKE B
205
+ **
206
+ ** is implemented as like(B, A). If there is an escape character E,
207
+ **
208
+ ** A LIKE B ESCAPE E
209
+ **
210
+ ** is mapped to like(B, A, E).
211
+ */
212
+ static void icuLikeFunc(
213
+ sqlite3_context *context,
214
+ int argc,
215
+ sqlite3_value **argv
216
+ ){
217
+ const unsigned char *zA = sqlite3_value_text(argv[0]);
218
+ const unsigned char *zB = sqlite3_value_text(argv[1]);
219
+ UChar32 uEsc = 0;
220
+
221
+ /* Limit the length of the LIKE or GLOB pattern to avoid problems
222
+ ** of deep recursion and N*N behavior in patternCompare().
223
+ */
224
+ if( sqlite3_value_bytes(argv[0])>SQLITE_MAX_LIKE_PATTERN_LENGTH ){
225
+ sqlite3_result_error(context, "LIKE or GLOB pattern too complex", -1);
226
+ return;
227
+ }
228
+
229
+
230
+ if( argc==3 ){
231
+ /* The escape character string must consist of a single UTF-8 character.
232
+ ** Otherwise, return an error.
233
+ */
234
+ int nE= sqlite3_value_bytes(argv[2]);
235
+ const unsigned char *zE = sqlite3_value_text(argv[2]);
236
+ int i = 0;
237
+ if( zE==0 ) return;
238
+ U8_NEXT(zE, i, nE, uEsc);
239
+ if( i!=nE){
240
+ sqlite3_result_error(context,
241
+ "ESCAPE expression must be a single character", -1);
242
+ return;
243
+ }
244
+ }
245
+
246
+ if( zA && zB ){
247
+ sqlite3_result_int(context, icuLikeCompare(zA, zB, uEsc));
248
+ }
249
+ }
250
+
251
+ /*
252
+ ** Function to delete compiled regexp objects. Registered as
253
+ ** a destructor function with sqlite3_set_auxdata().
254
+ */
255
+ static void icuRegexpDelete(void *p){
256
+ URegularExpression *pExpr = (URegularExpression *)p;
257
+ uregex_close(pExpr);
258
+ }
259
+
260
+ /*
261
+ ** Implementation of SQLite REGEXP operator. This scalar function takes
262
+ ** two arguments. The first is a regular expression pattern to compile
263
+ ** the second is a string to match against that pattern. If either
264
+ ** argument is an SQL NULL, then NULL Is returned. Otherwise, the result
265
+ ** is 1 if the string matches the pattern, or 0 otherwise.
266
+ **
267
+ ** SQLite maps the regexp() function to the regexp() operator such
268
+ ** that the following two are equivalent:
269
+ **
270
+ ** zString REGEXP zPattern
271
+ ** regexp(zPattern, zString)
272
+ **
273
+ ** Uses the following ICU regexp APIs:
274
+ **
275
+ ** uregex_open()
276
+ ** uregex_matches()
277
+ ** uregex_close()
278
+ */
279
+ static void icuRegexpFunc(sqlite3_context *p, int nArg, sqlite3_value **apArg){
280
+ UErrorCode status = U_ZERO_ERROR;
281
+ URegularExpression *pExpr;
282
+ UBool res;
283
+ const UChar *zString = sqlite3_value_text16(apArg[1]);
284
+
285
+ (void)nArg; /* Unused parameter */
286
+
287
+ /* If the left hand side of the regexp operator is NULL,
288
+ ** then the result is also NULL.
289
+ */
290
+ if( !zString ){
291
+ return;
292
+ }
293
+
294
+ pExpr = sqlite3_get_auxdata(p, 0);
295
+ if( !pExpr ){
296
+ const UChar *zPattern = sqlite3_value_text16(apArg[0]);
297
+ if( !zPattern ){
298
+ return;
299
+ }
300
+ pExpr = uregex_open(zPattern, -1, 0, 0, &status);
301
+
302
+ if( U_SUCCESS(status) ){
303
+ sqlite3_set_auxdata(p, 0, pExpr, icuRegexpDelete);
304
+ pExpr = sqlite3_get_auxdata(p, 0);
305
+ }
306
+ if( !pExpr ){
307
+ icuFunctionError(p, "uregex_open", status);
308
+ return;
309
+ }
310
+ }
311
+
312
+ /* Configure the text that the regular expression operates on. */
313
+ uregex_setText(pExpr, zString, -1, &status);
314
+ if( !U_SUCCESS(status) ){
315
+ icuFunctionError(p, "uregex_setText", status);
316
+ return;
317
+ }
318
+
319
+ /* Attempt the match */
320
+ res = uregex_matches(pExpr, 0, &status);
321
+ if( !U_SUCCESS(status) ){
322
+ icuFunctionError(p, "uregex_matches", status);
323
+ return;
324
+ }
325
+
326
+ /* Set the text that the regular expression operates on to a NULL
327
+ ** pointer. This is not really necessary, but it is tidier than
328
+ ** leaving the regular expression object configured with an invalid
329
+ ** pointer after this function returns.
330
+ */
331
+ uregex_setText(pExpr, 0, 0, &status);
332
+
333
+ /* Return 1 or 0. */
334
+ sqlite3_result_int(p, res ? 1 : 0);
335
+ }
336
+
337
+ /*
338
+ ** Implementations of scalar functions for case mapping - upper() and
339
+ ** lower(). Function upper() converts its input to upper-case (ABC).
340
+ ** Function lower() converts to lower-case (abc).
341
+ **
342
+ ** ICU provides two types of case mapping, "general" case mapping and
343
+ ** "language specific". Refer to ICU documentation for the differences
344
+ ** between the two.
345
+ **
346
+ ** To utilise "general" case mapping, the upper() or lower() scalar
347
+ ** functions are invoked with one argument:
348
+ **
349
+ ** upper('ABC') -> 'abc'
350
+ ** lower('abc') -> 'ABC'
351
+ **
352
+ ** To access ICU "language specific" case mapping, upper() or lower()
353
+ ** should be invoked with two arguments. The second argument is the name
354
+ ** of the locale to use. Passing an empty string ("") or SQL NULL value
355
+ ** as the second argument is the same as invoking the 1 argument version
356
+ ** of upper() or lower().
357
+ **
358
+ ** lower('I', 'en_us') -> 'i'
359
+ ** lower('I', 'tr_tr') -> '\u131' (small dotless i)
360
+ **
361
+ ** http://www.icu-project.org/userguide/posix.html#case_mappings
362
+ */
363
+ static void icuCaseFunc16(sqlite3_context *p, int nArg, sqlite3_value **apArg){
364
+ const UChar *zInput; /* Pointer to input string */
365
+ UChar *zOutput = 0; /* Pointer to output buffer */
366
+ int nInput; /* Size of utf-16 input string in bytes */
367
+ int nOut; /* Size of output buffer in bytes */
368
+ int cnt;
369
+ int bToUpper; /* True for toupper(), false for tolower() */
370
+ UErrorCode status;
371
+ const char *zLocale = 0;
372
+
373
+ assert(nArg==1 || nArg==2);
374
+ bToUpper = (sqlite3_user_data(p)!=0);
375
+ if( nArg==2 ){
376
+ zLocale = (const char *)sqlite3_value_text(apArg[1]);
377
+ }
378
+
379
+ zInput = sqlite3_value_text16(apArg[0]);
380
+ if( !zInput ){
381
+ return;
382
+ }
383
+ nOut = nInput = sqlite3_value_bytes16(apArg[0]);
384
+ if( nOut==0 ){
385
+ sqlite3_result_text16(p, "", 0, SQLITE_STATIC);
386
+ return;
387
+ }
388
+
389
+ for(cnt=0; cnt<2; cnt++){
390
+ UChar *zNew = sqlite3_realloc(zOutput, nOut);
391
+ if( zNew==0 ){
392
+ sqlite3_free(zOutput);
393
+ sqlite3_result_error_nomem(p);
394
+ return;
395
+ }
396
+ zOutput = zNew;
397
+ status = U_ZERO_ERROR;
398
+ if( bToUpper ){
399
+ nOut = 2*u_strToUpper(zOutput,nOut/2,zInput,nInput/2,zLocale,&status);
400
+ }else{
401
+ nOut = 2*u_strToLower(zOutput,nOut/2,zInput,nInput/2,zLocale,&status);
402
+ }
403
+
404
+ if( U_SUCCESS(status) ){
405
+ sqlite3_result_text16(p, zOutput, nOut, xFree);
406
+ }else if( status==U_BUFFER_OVERFLOW_ERROR ){
407
+ assert( cnt==0 );
408
+ continue;
409
+ }else{
410
+ icuFunctionError(p, bToUpper ? "u_strToUpper" : "u_strToLower", status);
411
+ }
412
+ return;
413
+ }
414
+ assert( 0 ); /* Unreachable */
415
+ }
416
+
417
+ #endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_ICU) */
418
+
419
+ #if 0 // AVOID BUILD ISSUE WITH COLLATION
420
+ /*
421
+ ** Collation sequence destructor function. The pCtx argument points to
422
+ ** a UCollator structure previously allocated using ucol_open().
423
+ */
424
+ static void icuCollationDel(void *pCtx){
425
+ UCollator *p = (UCollator *)pCtx;
426
+ ucol_close(p);
427
+ }
428
+
429
+ /*
430
+ ** Collation sequence comparison function. The pCtx argument points to
431
+ ** a UCollator structure previously allocated using ucol_open().
432
+ */
433
+ static int icuCollationColl(
434
+ void *pCtx,
435
+ int nLeft,
436
+ const void *zLeft,
437
+ int nRight,
438
+ const void *zRight
439
+ ){
440
+ UCollationResult res;
441
+ UCollator *p = (UCollator *)pCtx;
442
+ res = ucol_strcoll(p, (UChar *)zLeft, nLeft/2, (UChar *)zRight, nRight/2);
443
+ switch( res ){
444
+ case UCOL_LESS: return -1;
445
+ case UCOL_GREATER: return +1;
446
+ case UCOL_EQUAL: return 0;
447
+ }
448
+ assert(!"Unexpected return value from ucol_strcoll()");
449
+ return 0;
450
+ }
451
+
452
+ /*
453
+ ** Implementation of the scalar function icu_load_collation().
454
+ **
455
+ ** This scalar function is used to add ICU collation based collation
456
+ ** types to an SQLite database connection. It is intended to be called
457
+ ** as follows:
458
+ **
459
+ ** SELECT icu_load_collation(<locale>, <collation-name>);
460
+ **
461
+ ** Where <locale> is a string containing an ICU locale identifier (i.e.
462
+ ** "en_AU", "tr_TR" etc.) and <collation-name> is the name of the
463
+ ** collation sequence to create.
464
+ */
465
+ static void icuLoadCollation(
466
+ sqlite3_context *p,
467
+ int nArg,
468
+ sqlite3_value **apArg
469
+ ){
470
+ sqlite3 *db = (sqlite3 *)sqlite3_user_data(p);
471
+ UErrorCode status = U_ZERO_ERROR;
472
+ const char *zLocale; /* Locale identifier - (eg. "jp_JP") */
473
+ const char *zName; /* SQL Collation sequence name (eg. "japanese") */
474
+ UCollator *pUCollator; /* ICU library collation object */
475
+ int rc; /* Return code from sqlite3_create_collation_x() */
476
+
477
+ assert(nArg==2 || nArg==3);
478
+ (void)nArg; /* Unused parameter */
479
+ zLocale = (const char *)sqlite3_value_text(apArg[0]);
480
+ zName = (const char *)sqlite3_value_text(apArg[1]);
481
+
482
+ if( !zLocale || !zName ){
483
+ return;
484
+ }
485
+
486
+ pUCollator = ucol_open(zLocale, &status);
487
+ if( !U_SUCCESS(status) ){
488
+ icuFunctionError(p, "ucol_open", status);
489
+ return;
490
+ }
491
+ assert(p);
492
+ if(nArg==3){
493
+ const char *zOption = (const char*)sqlite3_value_text(apArg[2]);
494
+ static const struct {
495
+ const char *zName;
496
+ UColAttributeValue val;
497
+ } aStrength[] = {
498
+ { "PRIMARY", UCOL_PRIMARY },
499
+ { "SECONDARY", UCOL_SECONDARY },
500
+ { "TERTIARY", UCOL_TERTIARY },
501
+ { "DEFAULT", UCOL_DEFAULT_STRENGTH },
502
+ { "QUARTERNARY", UCOL_QUATERNARY },
503
+ { "IDENTICAL", UCOL_IDENTICAL },
504
+ };
505
+ unsigned int i;
506
+ for(i=0; i<sizeof(aStrength)/sizeof(aStrength[0]); i++){
507
+ if( sqlite3_stricmp(zOption,aStrength[i].zName)==0 ){
508
+ ucol_setStrength(pUCollator, aStrength[i].val);
509
+ break;
510
+ }
511
+ }
512
+ if( i>=sizeof(aStrength)/sizeof(aStrength[0]) ){
513
+ sqlite3_str *pStr = sqlite3_str_new(sqlite3_context_db_handle(p));
514
+ sqlite3_str_appendf(pStr,
515
+ "unknown collation strength \"%s\" - should be one of:",
516
+ zOption);
517
+ for(i=0; i<sizeof(aStrength)/sizeof(aStrength[0]); i++){
518
+ sqlite3_str_appendf(pStr, " %s", aStrength[i].zName);
519
+ }
520
+ sqlite3_result_error(p, sqlite3_str_value(pStr), -1);
521
+ sqlite3_free(sqlite3_str_finish(pStr));
522
+ return;
523
+ }
524
+ }
525
+ rc = sqlite3_create_collation_v2(db, zName, SQLITE_UTF16, (void *)pUCollator,
526
+ icuCollationColl, icuCollationDel
527
+ );
528
+ if( rc!=SQLITE_OK ){
529
+ ucol_close(pUCollator);
530
+ sqlite3_result_error(p, "Error registering collation function", -1);
531
+ }
532
+ }
533
+ #endif // AVOID BUILD ISSUE WITH COLLATION
534
+
535
+ /*
536
+ ** Register the ICU extension functions with database db.
537
+ */
538
+ int sqlite3IcuInit(sqlite3 *db){
539
+ # define SQLITEICU_EXTRAFLAGS (SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS)
540
+ static const struct IcuScalar {
541
+ const char *zName; /* Function name */
542
+ unsigned char nArg; /* Number of arguments */
543
+ unsigned int enc; /* Optimal text encoding */
544
+ unsigned char iContext; /* sqlite3_user_data() context */
545
+ void (*xFunc)(sqlite3_context*,int,sqlite3_value**);
546
+ } scalars[] = {
547
+ #if 0 // AVOID BUILD ISSUE WITH COLLATION
548
+ {"icu_load_collation",2,SQLITE_UTF8|SQLITE_DIRECTONLY,1, icuLoadCollation},
549
+ {"icu_load_collation",3,SQLITE_UTF8|SQLITE_DIRECTONLY,1, icuLoadCollation},
550
+ #endif // AVOID BUILD ISSUE WITH COLLATION
551
+ #if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_ICU)
552
+ #if 0 // AVOID INCONSISTENCY WITH ANDROID PLATFORM - CLEANUP TODO REMOVE OTHER REGEXP FUNCTIONS FROM THIS BUILD
553
+ {"regexp", 2, SQLITE_ANY|SQLITEICU_EXTRAFLAGS, 0, icuRegexpFunc},
554
+ #endif
555
+ {"lower", 1, SQLITE_UTF16|SQLITEICU_EXTRAFLAGS, 0, icuCaseFunc16},
556
+ {"lower", 2, SQLITE_UTF16|SQLITEICU_EXTRAFLAGS, 0, icuCaseFunc16},
557
+ {"upper", 1, SQLITE_UTF16|SQLITEICU_EXTRAFLAGS, 1, icuCaseFunc16},
558
+ {"upper", 2, SQLITE_UTF16|SQLITEICU_EXTRAFLAGS, 1, icuCaseFunc16},
559
+ {"lower", 1, SQLITE_UTF8|SQLITEICU_EXTRAFLAGS, 0, icuCaseFunc16},
560
+ {"lower", 2, SQLITE_UTF8|SQLITEICU_EXTRAFLAGS, 0, icuCaseFunc16},
561
+ {"upper", 1, SQLITE_UTF8|SQLITEICU_EXTRAFLAGS, 1, icuCaseFunc16},
562
+ {"upper", 2, SQLITE_UTF8|SQLITEICU_EXTRAFLAGS, 1, icuCaseFunc16},
563
+ {"like", 2, SQLITE_UTF8|SQLITEICU_EXTRAFLAGS, 0, icuLikeFunc},
564
+ {"like", 3, SQLITE_UTF8|SQLITEICU_EXTRAFLAGS, 0, icuLikeFunc},
565
+ #endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_ICU) */
566
+ };
567
+ int rc = SQLITE_OK;
568
+ int i;
569
+
570
+ for(i=0; rc==SQLITE_OK && i<(int)(sizeof(scalars)/sizeof(scalars[0])); i++){
571
+ const struct IcuScalar *p = &scalars[i];
572
+ rc = sqlite3_create_function(
573
+ db, p->zName, p->nArg, p->enc,
574
+ p->iContext ? (void*)db : (void*)0,
575
+ p->xFunc, 0, 0
576
+ );
577
+ }
578
+
579
+ return rc;
580
+ }
581
+
582
+ #ifndef SQLITE_CORE
583
+ #ifdef _WIN32
584
+ __declspec(dllexport)
585
+ #endif
586
+ int sqlite3_icu_init(
587
+ sqlite3 *db,
588
+ char **pzErrMsg,
589
+ const sqlite3_api_routines *pApi
590
+ ){
591
+ SQLITE_EXTENSION_INIT2(pApi)
592
+ return sqlite3IcuInit(db);
593
+ }
594
+ #endif
595
+
596
+ #endif
@@ -0,0 +1,26 @@
1
+ /*
2
+ ** 2008 May 26
3
+ **
4
+ ** The author disclaims copyright to this source code. In place of
5
+ ** a legal notice, here is a blessing:
6
+ **
7
+ ** May you do good and not evil.
8
+ ** May you find forgiveness for yourself and forgive others.
9
+ ** May you share freely, never taking more than you give.
10
+ **
11
+ ******************************************************************************
12
+ **
13
+ ** This header file is used by programs that want to link against the
14
+ ** ICU extension. All it does is declare the sqlite3IcuInit() interface.
15
+ */
16
+ #include "sqlite3.h"
17
+
18
+ #ifdef __cplusplus
19
+ extern "C" {
20
+ #endif /* __cplusplus */
21
+
22
+ int sqlite3IcuInit(sqlite3 *db);
23
+
24
+ #ifdef __cplusplus
25
+ } /* extern "C" */
26
+ #endif /* __cplusplus */
@@ -14,6 +14,8 @@
14
14
 
15
15
  #import "sqlite3_base64.h"
16
16
 
17
+ #import "sqliteicu.h"
18
+
17
19
  // Defines Macro to only log lines when in DEBUG mode
18
20
  #ifdef DEBUG
19
21
  # define DLog(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__);
@@ -191,6 +193,8 @@
191
193
 
192
194
  sqlite3_base64_init(db);
193
195
 
196
+ sqlite3IcuInit(db);
197
+
194
198
  // for SQLCipher version:
195
199
  // NSString *dbkey = [options objectForKey:@"key"];
196
200
  // const char *key = NULL;