bun-sqlite-for-rxdb 1.0.1
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/.serena/project.yml +84 -0
- package/CHANGELOG.md +300 -0
- package/LICENSE +21 -0
- package/README.md +87 -0
- package/ROADMAP.md +532 -0
- package/benchmarks/benchmark.ts +145 -0
- package/benchmarks/case-insensitive-10runs.ts +156 -0
- package/benchmarks/fts5-1m-scale.ts +126 -0
- package/benchmarks/fts5-before-after.ts +104 -0
- package/benchmarks/indexed-benchmark.ts +141 -0
- package/benchmarks/new-operators-benchmark.ts +140 -0
- package/benchmarks/query-builder-benchmark.ts +88 -0
- package/benchmarks/query-builder-consistency.ts +109 -0
- package/benchmarks/raw-better-sqlite3-10m.ts +85 -0
- package/benchmarks/raw-better-sqlite3.ts +86 -0
- package/benchmarks/raw-bun-sqlite-10m.ts +85 -0
- package/benchmarks/raw-bun-sqlite.ts +86 -0
- package/benchmarks/regex-10runs-all.ts +216 -0
- package/benchmarks/regex-comparison-benchmark.ts +161 -0
- package/benchmarks/regex-real-comparison.ts +213 -0
- package/benchmarks/run-10x.sh +19 -0
- package/benchmarks/smart-regex-benchmark.ts +148 -0
- package/benchmarks/sql-vs-mingo-benchmark.ts +210 -0
- package/benchmarks/sql-vs-mingo-comparison.ts +175 -0
- package/benchmarks/text-vs-jsonb.ts +167 -0
- package/benchmarks/wal-benchmark.ts +112 -0
- package/docs/architectural-patterns.md +1336 -0
- package/docs/id1-testsuite-journey.md +839 -0
- package/docs/official-test-suite-setup.md +393 -0
- package/nul +0 -0
- package/package.json +44 -0
- package/src/changestream.test.ts +182 -0
- package/src/cleanup.test.ts +110 -0
- package/src/collection-isolation.test.ts +74 -0
- package/src/connection-pool.test.ts +102 -0
- package/src/connection-pool.ts +38 -0
- package/src/findDocumentsById.test.ts +122 -0
- package/src/index.ts +2 -0
- package/src/instance.ts +382 -0
- package/src/multi-instance-events.test.ts +204 -0
- package/src/query/and-operator.test.ts +39 -0
- package/src/query/builder.test.ts +96 -0
- package/src/query/builder.ts +154 -0
- package/src/query/elemMatch-operator.test.ts +24 -0
- package/src/query/exists-operator.test.ts +28 -0
- package/src/query/in-operators.test.ts +54 -0
- package/src/query/mod-operator.test.ts +22 -0
- package/src/query/nested-query.test.ts +198 -0
- package/src/query/not-operators.test.ts +49 -0
- package/src/query/operators.test.ts +70 -0
- package/src/query/operators.ts +185 -0
- package/src/query/or-operator.test.ts +68 -0
- package/src/query/regex-escaping-regression.test.ts +43 -0
- package/src/query/regex-operator.test.ts +44 -0
- package/src/query/schema-mapper.ts +27 -0
- package/src/query/size-operator.test.ts +22 -0
- package/src/query/smart-regex.ts +52 -0
- package/src/query/type-operator.test.ts +37 -0
- package/src/query-cache.test.ts +286 -0
- package/src/rxdb-helpers.test.ts +348 -0
- package/src/rxdb-helpers.ts +262 -0
- package/src/schema-version-isolation.test.ts +126 -0
- package/src/statement-manager.ts +69 -0
- package/src/storage.test.ts +589 -0
- package/src/storage.ts +21 -0
- package/src/types.ts +14 -0
- package/test/rxdb-test-suite.ts +27 -0
- package/tsconfig.json +31 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { Database } from "bun:sqlite";
|
|
2
|
+
|
|
3
|
+
interface BenchmarkRow {
|
|
4
|
+
data: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
async function benchmarkTextVsJsonb() {
|
|
8
|
+
console.log('🏴☠️ TEXT vs JSONB: Storage Format Comparison (1M scale)\n');
|
|
9
|
+
|
|
10
|
+
const dbText = new Database(":memory:");
|
|
11
|
+
const dbJsonb = new Database(":memory:");
|
|
12
|
+
|
|
13
|
+
const versionRow = dbText.query('SELECT sqlite_version() as version').get() as { version: string };
|
|
14
|
+
console.log(`SQLite version: ${versionRow.version}\n`);
|
|
15
|
+
|
|
16
|
+
dbText.run(`CREATE TABLE users (id TEXT PRIMARY KEY, data TEXT)`);
|
|
17
|
+
dbText.run(`CREATE INDEX idx_age ON users(json_extract(data, '$.age'))`);
|
|
18
|
+
dbText.run(`CREATE INDEX idx_status ON users(json_extract(data, '$.status'))`);
|
|
19
|
+
|
|
20
|
+
dbJsonb.run(`CREATE TABLE users (id TEXT PRIMARY KEY, data BLOB)`);
|
|
21
|
+
dbJsonb.run(`CREATE INDEX idx_age ON users(json_extract(data, '$.age'))`);
|
|
22
|
+
dbJsonb.run(`CREATE INDEX idx_status ON users(json_extract(data, '$.status'))`);
|
|
23
|
+
|
|
24
|
+
console.log('📝 Inserting 1,000,000 documents...');
|
|
25
|
+
|
|
26
|
+
const textInsertStmt = dbText.prepare('INSERT INTO users (id, data) VALUES (?, ?)');
|
|
27
|
+
const textInsertMany = dbText.transaction((docs: Array<{ id: string; data: any }>) => {
|
|
28
|
+
for (const doc of docs) {
|
|
29
|
+
textInsertStmt.run(doc.id, JSON.stringify(doc.data));
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const jsonbInsertStmt = dbJsonb.prepare('INSERT INTO users (id, data) VALUES (?, jsonb(?))');
|
|
34
|
+
const jsonbInsertMany = dbJsonb.transaction((docs: Array<{ id: string; data: any }>) => {
|
|
35
|
+
for (const doc of docs) {
|
|
36
|
+
jsonbInsertStmt.run(doc.id, JSON.stringify(doc.data));
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const batchSize = 10000;
|
|
41
|
+
for (let batch = 0; batch < 100; batch++) {
|
|
42
|
+
const docs: Array<{ id: string; data: any }> = [];
|
|
43
|
+
for (let i = 0; i < batchSize; i++) {
|
|
44
|
+
const idx = batch * batchSize + i;
|
|
45
|
+
docs.push({
|
|
46
|
+
id: `user${idx}`,
|
|
47
|
+
data: {
|
|
48
|
+
name: `User ${idx}`,
|
|
49
|
+
age: 18 + (idx % 50),
|
|
50
|
+
email: `user${idx}@example.com`,
|
|
51
|
+
status: idx % 2 === 0 ? 'active' : 'inactive',
|
|
52
|
+
bio: `Biography for user ${idx} with interests in technology, music, and travel.`
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
textInsertMany(docs);
|
|
57
|
+
jsonbInsertMany(docs);
|
|
58
|
+
|
|
59
|
+
if ((batch + 1) % 10 === 0) {
|
|
60
|
+
console.log(` Inserted ${(batch + 1) * batchSize} documents...`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
console.log('✅ Inserted 1,000,000 documents\n');
|
|
64
|
+
|
|
65
|
+
console.log('='.repeat(60));
|
|
66
|
+
console.log('Test 1: Simple query (age > 50) - 15 runs');
|
|
67
|
+
console.log('='.repeat(60));
|
|
68
|
+
|
|
69
|
+
const textTimes1: number[] = [];
|
|
70
|
+
const jsonbTimes1: number[] = [];
|
|
71
|
+
|
|
72
|
+
for (let run = 1; run <= 15; run++) {
|
|
73
|
+
const textStart = performance.now();
|
|
74
|
+
dbText.query(`SELECT * FROM users WHERE json_extract(data, '$.age') > ?`).all(50);
|
|
75
|
+
const textEnd = performance.now();
|
|
76
|
+
textTimes1.push(textEnd - textStart);
|
|
77
|
+
|
|
78
|
+
const jsonbStart = performance.now();
|
|
79
|
+
dbJsonb.query(`SELECT * FROM users WHERE json_extract(data, '$.age') > ?`).all(50);
|
|
80
|
+
const jsonbEnd = performance.now();
|
|
81
|
+
jsonbTimes1.push(jsonbEnd - jsonbStart);
|
|
82
|
+
|
|
83
|
+
console.log(`Run ${run}: TEXT=${(textEnd - textStart).toFixed(2)}ms, JSONB=${(jsonbEnd - jsonbStart).toFixed(2)}ms`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const textAvg1 = textTimes1.reduce((a, b) => a + b, 0) / textTimes1.length;
|
|
87
|
+
const jsonbAvg1 = jsonbTimes1.reduce((a, b) => a + b, 0) / jsonbTimes1.length;
|
|
88
|
+
|
|
89
|
+
console.log(`\nTEXT average: ${textAvg1.toFixed(2)}ms`);
|
|
90
|
+
console.log(`JSONB average: ${jsonbAvg1.toFixed(2)}ms`);
|
|
91
|
+
console.log(`Speedup: ${(textAvg1 / jsonbAvg1).toFixed(2)}x\n`);
|
|
92
|
+
|
|
93
|
+
console.log('='.repeat(60));
|
|
94
|
+
console.log('Test 2: Complex query (age > 30 AND status = active) - 15 runs');
|
|
95
|
+
console.log('='.repeat(60));
|
|
96
|
+
|
|
97
|
+
const textTimes2: number[] = [];
|
|
98
|
+
const jsonbTimes2: number[] = [];
|
|
99
|
+
|
|
100
|
+
for (let run = 1; run <= 15; run++) {
|
|
101
|
+
const textStart = performance.now();
|
|
102
|
+
dbText.query(`SELECT * FROM users WHERE json_extract(data, '$.age') > ? AND json_extract(data, '$.status') = ?`).all(30, 'active');
|
|
103
|
+
const textEnd = performance.now();
|
|
104
|
+
textTimes2.push(textEnd - textStart);
|
|
105
|
+
|
|
106
|
+
const jsonbStart = performance.now();
|
|
107
|
+
dbJsonb.query(`SELECT * FROM users WHERE json_extract(data, '$.age') > ? AND json_extract(data, '$.status') = ?`).all(30, 'active');
|
|
108
|
+
const jsonbEnd = performance.now();
|
|
109
|
+
jsonbTimes2.push(jsonbEnd - jsonbStart);
|
|
110
|
+
|
|
111
|
+
console.log(`Run ${run}: TEXT=${(textEnd - textStart).toFixed(2)}ms, JSONB=${(jsonbEnd - jsonbStart).toFixed(2)}ms`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const textAvg2 = textTimes2.reduce((a, b) => a + b, 0) / textTimes2.length;
|
|
115
|
+
const jsonbAvg2 = jsonbTimes2.reduce((a, b) => a + b, 0) / jsonbTimes2.length;
|
|
116
|
+
|
|
117
|
+
console.log(`\nTEXT average: ${textAvg2.toFixed(2)}ms`);
|
|
118
|
+
console.log(`JSONB average: ${jsonbAvg2.toFixed(2)}ms`);
|
|
119
|
+
console.log(`Speedup: ${(textAvg2 / jsonbAvg2).toFixed(2)}x\n`);
|
|
120
|
+
|
|
121
|
+
console.log('='.repeat(60));
|
|
122
|
+
console.log('Test 3: Read and parse (SELECT + JSON.parse) - 15 runs');
|
|
123
|
+
console.log('='.repeat(60));
|
|
124
|
+
|
|
125
|
+
const textTimes3: number[] = [];
|
|
126
|
+
const jsonbTimes3: number[] = [];
|
|
127
|
+
|
|
128
|
+
for (let run = 1; run <= 15; run++) {
|
|
129
|
+
const textStart = performance.now();
|
|
130
|
+
const textRows = dbText.query(`SELECT data FROM users LIMIT 1000`).all() as BenchmarkRow[];
|
|
131
|
+
for (const row of textRows) {
|
|
132
|
+
JSON.parse(row.data);
|
|
133
|
+
}
|
|
134
|
+
const textEnd = performance.now();
|
|
135
|
+
textTimes3.push(textEnd - textStart);
|
|
136
|
+
|
|
137
|
+
const jsonbStart = performance.now();
|
|
138
|
+
const jsonbRows = dbJsonb.query(`SELECT json(data) as data FROM users LIMIT 1000`).all() as BenchmarkRow[];
|
|
139
|
+
for (const row of jsonbRows) {
|
|
140
|
+
JSON.parse(row.data);
|
|
141
|
+
}
|
|
142
|
+
const jsonbEnd = performance.now();
|
|
143
|
+
jsonbTimes3.push(jsonbEnd - jsonbStart);
|
|
144
|
+
|
|
145
|
+
console.log(`Run ${run}: TEXT=${(textEnd - textStart).toFixed(2)}ms, JSONB=${(jsonbEnd - jsonbStart).toFixed(2)}ms`);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const textAvg3 = textTimes3.reduce((a, b) => a + b, 0) / textTimes3.length;
|
|
149
|
+
const jsonbAvg3 = jsonbTimes3.reduce((a, b) => a + b, 0) / jsonbTimes3.length;
|
|
150
|
+
|
|
151
|
+
console.log(`\nTEXT average: ${textAvg3.toFixed(2)}ms`);
|
|
152
|
+
console.log(`JSONB average: ${jsonbAvg3.toFixed(2)}ms`);
|
|
153
|
+
console.log(`Speedup: ${(textAvg3 / jsonbAvg3).toFixed(2)}x\n`);
|
|
154
|
+
|
|
155
|
+
console.log('='.repeat(60));
|
|
156
|
+
console.log('📊 FINAL RESULTS (1M documents, 15 runs each)');
|
|
157
|
+
console.log('='.repeat(60));
|
|
158
|
+
console.log(`Simple query: TEXT=${textAvg1.toFixed(2)}ms, JSONB=${jsonbAvg1.toFixed(2)}ms (${(textAvg1 / jsonbAvg1).toFixed(2)}x)`);
|
|
159
|
+
console.log(`Complex query: TEXT=${textAvg2.toFixed(2)}ms, JSONB=${jsonbAvg2.toFixed(2)}ms (${(textAvg2 / jsonbAvg2).toFixed(2)}x)`);
|
|
160
|
+
console.log(`Read + parse: TEXT=${textAvg3.toFixed(2)}ms, JSONB=${jsonbAvg3.toFixed(2)}ms (${(textAvg3 / jsonbAvg3).toFixed(2)}x)`);
|
|
161
|
+
console.log('='.repeat(60) + '\n');
|
|
162
|
+
|
|
163
|
+
dbText.close();
|
|
164
|
+
dbJsonb.close();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
benchmarkTextVsJsonb().catch(console.error);
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { Database } from 'bun:sqlite';
|
|
2
|
+
|
|
3
|
+
console.log('🏴☠️ WAL Mode Performance Benchmark\n');
|
|
4
|
+
|
|
5
|
+
const NUM_DOCS = 1000;
|
|
6
|
+
const NUM_RUNS = 5;
|
|
7
|
+
|
|
8
|
+
interface TestDoc {
|
|
9
|
+
id: string;
|
|
10
|
+
name: string;
|
|
11
|
+
age: number;
|
|
12
|
+
status: string;
|
|
13
|
+
timestamp: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function createDatabase(name: string, enableWAL: boolean): Database {
|
|
17
|
+
const db = new Database(name);
|
|
18
|
+
|
|
19
|
+
if (enableWAL) {
|
|
20
|
+
db.run('PRAGMA journal_mode = WAL');
|
|
21
|
+
} else {
|
|
22
|
+
db.run('PRAGMA journal_mode = DELETE');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
db.run(`
|
|
26
|
+
CREATE TABLE IF NOT EXISTS documents (
|
|
27
|
+
id TEXT PRIMARY KEY NOT NULL,
|
|
28
|
+
data TEXT NOT NULL,
|
|
29
|
+
deleted INTEGER NOT NULL DEFAULT 0,
|
|
30
|
+
rev TEXT NOT NULL,
|
|
31
|
+
mtime_ms REAL NOT NULL
|
|
32
|
+
)
|
|
33
|
+
`);
|
|
34
|
+
|
|
35
|
+
return db;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function generateDocs(count: number): TestDoc[] {
|
|
39
|
+
return Array.from({ length: count }, (_, i) => ({
|
|
40
|
+
id: `doc-${i}`,
|
|
41
|
+
name: `User ${i}`,
|
|
42
|
+
age: 20 + (i % 50),
|
|
43
|
+
status: i % 3 === 0 ? 'active' : i % 3 === 1 ? 'inactive' : 'pending',
|
|
44
|
+
timestamp: Date.now() + i
|
|
45
|
+
}));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function benchmarkWrites(db: Database, docs: TestDoc[]): number {
|
|
49
|
+
const start = performance.now();
|
|
50
|
+
|
|
51
|
+
const stmt = db.prepare(`
|
|
52
|
+
INSERT INTO documents (id, data, deleted, rev, mtime_ms)
|
|
53
|
+
VALUES (?, ?, ?, ?, ?)
|
|
54
|
+
`);
|
|
55
|
+
|
|
56
|
+
for (const doc of docs) {
|
|
57
|
+
const data = JSON.stringify(doc);
|
|
58
|
+
stmt.run(doc.id, data, 0, '1-abc', doc.timestamp);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const end = performance.now();
|
|
62
|
+
return end - start;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function runBenchmark() {
|
|
66
|
+
const docs = generateDocs(NUM_DOCS);
|
|
67
|
+
const walTimes: number[] = [];
|
|
68
|
+
const noWalTimes: number[] = [];
|
|
69
|
+
|
|
70
|
+
console.log(`📊 Testing ${NUM_DOCS} document inserts, ${NUM_RUNS} runs each\n`);
|
|
71
|
+
|
|
72
|
+
console.log('⏱️ Testing WITHOUT WAL (DELETE journal mode)...');
|
|
73
|
+
for (let i = 0; i < NUM_RUNS; i++) {
|
|
74
|
+
const db = createDatabase(':memory:', false);
|
|
75
|
+
const time = benchmarkWrites(db, docs);
|
|
76
|
+
noWalTimes.push(time);
|
|
77
|
+
db.close();
|
|
78
|
+
console.log(` Run ${i + 1}: ${time.toFixed(2)}ms`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const avgNoWal = noWalTimes.reduce((a, b) => a + b, 0) / NUM_RUNS;
|
|
82
|
+
console.log(` Average: ${avgNoWal.toFixed(2)}ms\n`);
|
|
83
|
+
|
|
84
|
+
console.log('⏱️ Testing WITH WAL mode...');
|
|
85
|
+
for (let i = 0; i < NUM_RUNS; i++) {
|
|
86
|
+
const db = createDatabase(':memory:', true);
|
|
87
|
+
const time = benchmarkWrites(db, docs);
|
|
88
|
+
walTimes.push(time);
|
|
89
|
+
db.close();
|
|
90
|
+
console.log(` Run ${i + 1}: ${time.toFixed(2)}ms`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const avgWal = walTimes.reduce((a, b) => a + b, 0) / NUM_RUNS;
|
|
94
|
+
console.log(` Average: ${avgWal.toFixed(2)}ms\n`);
|
|
95
|
+
|
|
96
|
+
const speedup = avgNoWal / avgWal;
|
|
97
|
+
console.log('📈 Results:');
|
|
98
|
+
console.log(` WITHOUT WAL: ${avgNoWal.toFixed(2)}ms`);
|
|
99
|
+
console.log(` WITH WAL: ${avgWal.toFixed(2)}ms`);
|
|
100
|
+
console.log(` Speedup: ${speedup.toFixed(2)}x faster with WAL\n`);
|
|
101
|
+
|
|
102
|
+
if (speedup >= 3.0) {
|
|
103
|
+
console.log('✅ WAL mode delivers 3-6x speedup as claimed!\n');
|
|
104
|
+
} else if (speedup >= 1.5) {
|
|
105
|
+
console.log('⚠️ WAL mode is faster, but less than 3x speedup (in-memory DB limitation)\n');
|
|
106
|
+
console.log('💡 Note: WAL benefits are more pronounced with file-based databases\n');
|
|
107
|
+
} else {
|
|
108
|
+
console.log('❌ WAL mode speedup not significant\n');
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
runBenchmark();
|