55// case conversion. Registered as an auto-extension, overriding SQLite's
66// ASCII-only built-in lower()/upper() on every connection.
77//
8- // Scope: context-free case mapping (the common case). It does not implement
9- // context-sensitive rules such as Greek final sigma; JavaScript's toLowerCase
10- // applies those, so a word-final Σ can differ. The zqlite ILIKE parity test
11- // guards the cases Zero relies on.
8+ // This matches JavaScript's default (locale-independent) case conversion for
9+ // all input: per-code-point full mappings plus the one context-sensitive rule
10+ // that algorithm applies — Greek final sigma (see LowerSigma). Locale-specific
11+ // rules (Turkish dotless i, Lithuanian) are not applied, and neither does
12+ // String.prototype.toLowerCase, so the two stay consistent.
1213
1314#include " unicode_case_data.h"
1415
@@ -71,7 +72,47 @@ static const ZeroCaseMap* Lookup(const ZeroCaseMap* map, int len, unsigned int c
7172 return NULL ;
7273}
7374
74- static void Apply (sqlite3_context* ctx, sqlite3_value* arg, const ZeroCaseMap* map, int len) {
75+ // Whether `cp` falls in one of the sorted, non-overlapping [lo, hi] ranges.
76+ static int InRanges (const ZeroRange* r, int len, unsigned int cp) {
77+ int lo = 0 , hi = len - 1 ;
78+ while (lo <= hi) {
79+ int mid = (lo + hi) >> 1 ;
80+ if (cp < r[mid].lo ) hi = mid - 1 ;
81+ else if (cp > r[mid].hi ) lo = mid + 1 ;
82+ else return 1 ;
83+ }
84+ return 0 ;
85+ }
86+
87+ static int IsCased (unsigned int cp) {
88+ return InRanges (kZeroCased , kZeroCasedLen , cp);
89+ }
90+ static int IsCaseIgnorable (unsigned int cp) {
91+ return InRanges (kZeroCaseIgnorable , kZeroCaseIgnorableLen , cp);
92+ }
93+
94+ static const unsigned int kCapitalSigma = 0x3A3u ; // Σ
95+ static const unsigned int kSmallSigma = 0x3C3u ; // σ
96+ static const unsigned int kFinalSigma = 0x3C2u ; // ς
97+
98+ // Lowercasing Σ is the one context-sensitive rule in the default (locale-
99+ // independent) algorithm that JS toLowerCase applies: Σ -> ς when it is preceded
100+ // by a cased letter (ignoring case-ignorable chars) and not followed by one;
101+ // otherwise Σ -> σ. `prevCased` is whether the last non-ignorable input char was
102+ // cased; `in`/`n`/`after` scan the input following the Σ.
103+ static unsigned int LowerSigma (const unsigned char * in, int n, int after, int prevCased) {
104+ int followedByCased = 0 ;
105+ int j = after;
106+ while (j < n) {
107+ unsigned int c = Utf8Decode (in, n, &j);
108+ if (IsCaseIgnorable (c)) continue ;
109+ followedByCased = IsCased (c);
110+ break ;
111+ }
112+ return (prevCased && !followedByCased) ? kFinalSigma : kSmallSigma ;
113+ }
114+
115+ static void Apply (sqlite3_context* ctx, sqlite3_value* arg, const ZeroCaseMap* map, int len, int lower) {
75116 if (sqlite3_value_type (arg) == SQLITE_NULL ) {
76117 sqlite3_result_null (ctx);
77118 return ;
@@ -92,6 +133,7 @@ static void Apply(sqlite3_context* ctx, sqlite3_value* arg, const ZeroCaseMap* m
92133 }
93134 int outn = 0 ;
94135 int i = 0 ;
136+ int prevCased = 0 ; // was the last non-case-ignorable input char cased?
95137 while (i < n) {
96138 unsigned int cp = Utf8Decode (in, n, &i);
97139 // Reserve room for up to 3 mapped code points (4 bytes each).
@@ -105,24 +147,30 @@ static void Apply(sqlite3_context* ctx, sqlite3_value* arg, const ZeroCaseMap* m
105147 }
106148 out = grown;
107149 }
108- const ZeroCaseMap* m = Lookup (map, len, cp);
109- if (m) {
110- for (int k = 0 ; k < m->n ; k++) outn += Utf8Encode (m->to [k], out + outn);
150+ if (lower && cp == kCapitalSigma ) {
151+ outn += Utf8Encode (LowerSigma (in, n, i, prevCased), out + outn);
111152 } else {
112- outn += Utf8Encode (cp, out + outn);
153+ const ZeroCaseMap* m = Lookup (map, len, cp);
154+ if (m) {
155+ for (int k = 0 ; k < m->n ; k++) outn += Utf8Encode (m->to [k], out + outn);
156+ } else {
157+ outn += Utf8Encode (cp, out + outn);
158+ }
113159 }
160+ // Context for final-sigma is evaluated on the original input.
161+ if (!IsCaseIgnorable (cp)) prevCased = IsCased (cp);
114162 }
115163 sqlite3_result_text (ctx, out, outn, sqlite3_free);
116164}
117165
118166static void LowerFunc (sqlite3_context* ctx, int argc, sqlite3_value** argv) {
119167 (void )argc;
120- Apply (ctx, argv[0 ], kZeroLowerMap , kZeroLowerMapLen );
168+ Apply (ctx, argv[0 ], kZeroLowerMap , kZeroLowerMapLen , 1 );
121169}
122170
123171static void UpperFunc (sqlite3_context* ctx, int argc, sqlite3_value** argv) {
124172 (void )argc;
125- Apply (ctx, argv[0 ], kZeroUpperMap , kZeroUpperMapLen );
173+ Apply (ctx, argv[0 ], kZeroUpperMap , kZeroUpperMapLen , 0 );
126174}
127175
128176} // namespace UnicodeCase
0 commit comments