Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 0 additions & 13 deletions binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,6 @@
},
},
'conditions': [
# ICU is statically linked into the SQLite static library on
# non-Windows; the final .node must resolve its ICU symbols. (See
# deps/sqlite3.gyp for why Windows is excluded.)
['OS != "win"', {
'libraries': ['<!@(node deps/icu.js libs)'],
}],
['OS=="linux"', {
'ldflags': [
'-Wl,-Bsymbolic',
Expand Down Expand Up @@ -75,13 +69,6 @@
'defines': ['HAVE_EDITLINE=1'],
'libraries': ['-ledit', '-lncurses'],
}],
# Unicode-aware LIKE/upper()/lower() via the bundled ICU extension,
# statically linked. Excluded on Windows (see deps/sqlite3.gyp).
['OS != "win"', {
'defines': ['SQLITE_ENABLE_ICU'],
'include_dirs': ['<!@(node deps/icu.js include)'],
'libraries': ['<!@(node deps/icu.js libs)'],
}],
],
'configurations': {
'Debug': {
Expand Down
8 changes: 0 additions & 8 deletions deps/download.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,6 @@
CHECKIN="0e862bc9ed7aa9ae"

# Defines below are sorted alphabetically.
#
# Note: SQLITE_ENABLE_ICU is intentionally NOT listed here. These defines are
# applied unconditionally on every platform (they become defines.gypi and are
# passed to every compile), but ICU is only available on non-Windows builds.
# It is therefore defined conditionally (OS != "win") in deps/sqlite3.gyp
# instead. The ICU extension code already ships in the amalgamation guarded by
# #ifdef SQLITE_ENABLE_ICU, so it does not need to be set when generating
# sqlite3.c here.
DEFINES="
HAVE_INT16_T=1
HAVE_INT32_T=1
Expand Down
96 changes: 96 additions & 0 deletions deps/gen-unicode-case.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Generates src/util/unicode_case_data.h — the case-mapping tables used by the
// driver's Unicode-aware lower()/upper() SQL functions.
//
// The tables are generated from THIS Node's String.prototype.toLowerCase /
// toUpperCase and the Cased / Case_Ignorable Unicode property escapes. That
// makes the C implementation match JavaScript's case conversion by construction
// (including the Greek final-sigma rule) — exactly the consistency the
// client-side IVM matcher (toLowerCase) and the zqlite replica rely on. Case
// data is stable across Unicode versions, so this only needs regenerating on a
// deliberate Unicode bump. Run it with the active Node LTS (currently 24,
// Unicode 17) so the snapshot tracks a stable, widely-deployed version;
// test/52.unicode-case.js then exhaustively checks the functions against JS on
// any runtime whose Unicode matches.
//
// Usage: node deps/gen-unicode-case.mjs > src/util/unicode_case_data.h

const MAX = 0x10ffff;
const isSurrogate = cp => cp >= 0xd800 && cp <= 0xdfff;

function mappings(method) {
const rows = [];
for (let cp = 0; cp <= MAX; cp++) {
if (isSurrogate(cp)) continue; // lone surrogates
const ch = String.fromCodePoint(cp);
const mapped = ch[method]();
if (mapped === ch) continue;
const to = Array.from(mapped, c => c.codePointAt(0));
if (to.length > 3) throw new Error(`mapping for U+${cp.toString(16)} > 3 cps`);
rows.push([cp, to]);
}
return rows;
}

// Collapses the code points matching `re` into sorted [lo, hi] ranges. Used for
// the Cased / Case_Ignorable properties needed by the final-sigma rule.
function propertyRanges(re) {
const ranges = [];
let start = -1;
for (let cp = 0; cp <= MAX; cp++) {
const match = !isSurrogate(cp) && re.test(String.fromCodePoint(cp));
if (match && start < 0) start = cp;
else if (!match && start >= 0) {
ranges.push([start, cp - 1]);
start = -1;
}
}
if (start >= 0) ranges.push([start, MAX]);
return ranges;
}

function emitRanges(name, ranges) {
const lines = ranges.map(
([lo, hi]) => ` {0x${lo.toString(16)}u, 0x${hi.toString(16)}u},`,
);
return (
`static const ZeroRange ${name}[] = {\n${lines.join('\n')}\n};\n` +
`static const int ${name}Len = ${ranges.length};\n`
);
}

function emit(name, rows) {
const lines = rows.map(([from, to]) => {
const t = [...to, 0, 0, 0].slice(0, 3).map(c => `0x${c.toString(16)}u`);
return ` {0x${from.toString(16)}u, {${t.join(', ')}}, ${to.length}},`;
});
return (
`static const ZeroCaseMap ${name}[] = {\n${lines.join('\n')}\n};\n` +
`static const int ${name}Len = ${rows.length};\n`
);
}

const lower = mappings('toLowerCase');
const upper = mappings('toUpperCase');
const cased = propertyRanges(/\p{Cased}/u);
const caseIgnorable = propertyRanges(/\p{Case_Ignorable}/u);

process.stdout.write(
`// AUTO-GENERATED by deps/gen-unicode-case.mjs — DO NOT EDIT.\n` +
`// Source: Node ${process.versions.node} (Unicode ${process.versions.unicode}).\n` +
`// Case maps: one source code point to 1-3 target code points, matching\n` +
`// JavaScript String.prototype.toLowerCase/toUpperCase. Cased / CaseIgnorable\n` +
`// are property ranges used by the Greek final-sigma rule.\n` +
`#ifndef ZERO_UNICODE_CASE_DATA_H\n#define ZERO_UNICODE_CASE_DATA_H\n\n` +
`typedef struct ZeroCaseMap {\n` +
` unsigned int from;\n unsigned int to[3];\n unsigned char n;\n` +
`} ZeroCaseMap;\n\n` +
`typedef struct ZeroRange {\n unsigned int lo;\n unsigned int hi;\n} ZeroRange;\n\n` +
emit('kZeroLowerMap', lower) +
`\n` +
emit('kZeroUpperMap', upper) +
`\n` +
emitRanges('kZeroCased', cased) +
`\n` +
emitRanges('kZeroCaseIgnorable', caseIgnorable) +
`\n#endif /* ZERO_UNICODE_CASE_DATA_H */\n`,
);
174 changes: 0 additions & 174 deletions deps/icu.js

This file was deleted.

9 changes: 0 additions & 9 deletions deps/sqlite3.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,6 @@
'SQLITE_ENABLE_COLUMN_METADATA',
],
}],
# Unicode-aware LIKE/upper()/lower() via SQLite's bundled ICU extension,
# statically linked so the prebuilt binaries stay self-contained.
# Not enabled on Windows yet: static ICU there means building it from
# source (vcpkg), which is impractically slow in CI. Windows therefore
# keeps SQLite's ASCII-only LIKE for now.
['OS != "win"', {
'defines': ['SQLITE_ENABLE_ICU'],
'include_dirs': ['<!@(node icu.js include)'],
}],
],
'configurations': {
'Debug': {
Expand Down
9 changes: 9 additions & 0 deletions src/better_sqlite3.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class Backup;
#include "objects/statement.cpp"
#include "objects/database.cpp"
#include "objects/statement-iterator.cpp"
#include "util/unicode_case.cpp"

NODE_MODULE_INIT(/* exports, context */) {
#if defined(NODE_MODULE_VERSION) && NODE_MODULE_VERSION >= 140
Expand All @@ -55,6 +56,14 @@ NODE_MODULE_INIT(/* exports, context */) {
v8::HandleScope scope(isolate);
Addon::ConfigureURI();

// Register Unicode-aware lower()/upper() on every connection (replaces ICU).
// sqlite3_auto_extension is process-global, so guard against repeated
// NODE_MODULE_INIT calls (e.g. worker threads) registering it more than once.
static std::once_flag unicode_case_once;
std::call_once(unicode_case_once, []() {
sqlite3_auto_extension((void (*)(void))zeroRegisterUnicodeCase);
});

// Initialize addon instance.
Addon* addon = new Addon(isolate);
v8::Local<v8::External> data = v8::External::New(isolate, addon);
Expand Down
Loading
Loading