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,88 @@
|
|
|
1
|
+
import { buildWhereClause } from '../src/query/builder';
|
|
2
|
+
import type { RxJsonSchema, MangoQuerySelector, RxDocumentData } from 'rxdb';
|
|
3
|
+
|
|
4
|
+
console.log("🏴☠️ Query Builder Performance Benchmark\n");
|
|
5
|
+
|
|
6
|
+
type TestDoc = {
|
|
7
|
+
id: string;
|
|
8
|
+
name: string;
|
|
9
|
+
age: number;
|
|
10
|
+
email: string;
|
|
11
|
+
status: string;
|
|
12
|
+
_deleted: boolean;
|
|
13
|
+
_attachments: {};
|
|
14
|
+
_rev: string;
|
|
15
|
+
_meta: { lwt: number };
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const schema: RxJsonSchema<RxDocumentData<TestDoc>> = {
|
|
19
|
+
version: 0,
|
|
20
|
+
primaryKey: 'id',
|
|
21
|
+
type: 'object',
|
|
22
|
+
properties: {
|
|
23
|
+
id: { type: 'string', maxLength: 100 },
|
|
24
|
+
name: { type: 'string' },
|
|
25
|
+
age: { type: 'number' },
|
|
26
|
+
email: { type: 'string' },
|
|
27
|
+
status: { type: 'string' },
|
|
28
|
+
_deleted: { type: 'boolean' },
|
|
29
|
+
_attachments: { type: 'object' },
|
|
30
|
+
_rev: { type: 'string' },
|
|
31
|
+
_meta: {
|
|
32
|
+
type: 'object',
|
|
33
|
+
properties: {
|
|
34
|
+
lwt: { type: 'number' }
|
|
35
|
+
},
|
|
36
|
+
required: ['lwt']
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
required: ['id', 'name', 'age', '_deleted', '_rev', '_meta']
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const queries: MangoQuerySelector<RxDocumentData<TestDoc>>[] = [
|
|
43
|
+
{ age: { $gt: 30 }, status: { $eq: 'active' } },
|
|
44
|
+
{ age: { $gte: 25, $lte: 50 } },
|
|
45
|
+
{ $or: [{ age: { $lt: 20 } }, { age: { $gt: 60 } }] },
|
|
46
|
+
{ $and: [{ status: { $eq: 'active' } }, { age: { $gte: 18 } }] },
|
|
47
|
+
{ name: { $regex: '^User' } },
|
|
48
|
+
{ age: { $in: [25, 30, 35, 40] } },
|
|
49
|
+
{ status: { $ne: 'deleted' } },
|
|
50
|
+
{ email: { $exists: true } }
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
console.log("📊 Test: Build 100,000 queries (no cache)");
|
|
54
|
+
const iterations = 100_000;
|
|
55
|
+
|
|
56
|
+
const start = performance.now();
|
|
57
|
+
for (let i = 0; i < iterations; i++) {
|
|
58
|
+
const query = queries[i % queries.length];
|
|
59
|
+
buildWhereClause(query, schema);
|
|
60
|
+
}
|
|
61
|
+
const end = performance.now();
|
|
62
|
+
const totalTime = end - start;
|
|
63
|
+
const avgTime = totalTime / iterations;
|
|
64
|
+
|
|
65
|
+
console.log(` Total time: ${totalTime.toFixed(2)}ms`);
|
|
66
|
+
console.log(` Average per query: ${(avgTime * 1000).toFixed(2)}µs`);
|
|
67
|
+
console.log(` Throughput: ${(iterations / (totalTime / 1000)).toFixed(0)} queries/sec\n`);
|
|
68
|
+
|
|
69
|
+
console.log("📊 Analysis:");
|
|
70
|
+
console.log(` If caching saves 50% → ${(avgTime * 0.5 * 1000).toFixed(2)}µs saved per query`);
|
|
71
|
+
console.log(` For 1M queries → ${(totalTime * 0.5 / 1000).toFixed(2)}s saved`);
|
|
72
|
+
console.log(` Cache overhead: ~${(avgTime * 0.1 * 1000).toFixed(2)}µs (Map lookup)`);
|
|
73
|
+
console.log(` Net benefit: ${((avgTime * 0.5 - avgTime * 0.1) * 1000).toFixed(2)}µs per query\n`);
|
|
74
|
+
|
|
75
|
+
console.log("✅ Conclusion:");
|
|
76
|
+
if (avgTime < 0.01) {
|
|
77
|
+
console.log(" Query builder is VERY FAST (<10µs)");
|
|
78
|
+
console.log(" Caching overhead likely > benefit");
|
|
79
|
+
console.log(" Recommendation: SKIP caching (YAGNI)");
|
|
80
|
+
} else if (avgTime < 0.1) {
|
|
81
|
+
console.log(" Query builder is FAST (<100µs)");
|
|
82
|
+
console.log(" Caching might help for repeated queries");
|
|
83
|
+
console.log(" Recommendation: Implement simple cache, measure impact");
|
|
84
|
+
} else {
|
|
85
|
+
console.log(" Query builder is SLOW (>100µs)");
|
|
86
|
+
console.log(" Caching will definitely help!");
|
|
87
|
+
console.log(" Recommendation: Implement cache immediately");
|
|
88
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { buildWhereClause } from '../src/query/builder';
|
|
2
|
+
import type { RxJsonSchema, MangoQuerySelector, RxDocumentData } from 'rxdb';
|
|
3
|
+
|
|
4
|
+
console.log("🏴☠️ Query Builder Consistency Test (15 runs)\n");
|
|
5
|
+
|
|
6
|
+
type TestDoc = {
|
|
7
|
+
id: string;
|
|
8
|
+
name: string;
|
|
9
|
+
age: number;
|
|
10
|
+
email: string;
|
|
11
|
+
status: string;
|
|
12
|
+
_deleted: boolean;
|
|
13
|
+
_attachments: {};
|
|
14
|
+
_rev: string;
|
|
15
|
+
_meta: { lwt: number };
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const schema: RxJsonSchema<RxDocumentData<TestDoc>> = {
|
|
19
|
+
version: 0,
|
|
20
|
+
primaryKey: 'id',
|
|
21
|
+
type: 'object',
|
|
22
|
+
properties: {
|
|
23
|
+
id: { type: 'string', maxLength: 100 },
|
|
24
|
+
name: { type: 'string' },
|
|
25
|
+
age: { type: 'number' },
|
|
26
|
+
email: { type: 'string' },
|
|
27
|
+
status: { type: 'string' },
|
|
28
|
+
_deleted: { type: 'boolean' },
|
|
29
|
+
_attachments: { type: 'object' },
|
|
30
|
+
_rev: { type: 'string' },
|
|
31
|
+
_meta: {
|
|
32
|
+
type: 'object',
|
|
33
|
+
properties: {
|
|
34
|
+
lwt: { type: 'number' }
|
|
35
|
+
},
|
|
36
|
+
required: ['lwt']
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
required: ['id', 'name', 'age', '_deleted', '_rev', '_meta']
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const queries: MangoQuerySelector<RxDocumentData<TestDoc>>[] = [
|
|
43
|
+
{ age: { $gt: 30 }, status: { $eq: 'active' } },
|
|
44
|
+
{ age: { $gte: 25, $lte: 50 } },
|
|
45
|
+
{ $or: [{ age: { $lt: 20 } }, { age: { $gt: 60 } }] },
|
|
46
|
+
{ $and: [{ status: { $eq: 'active' } }, { age: { $gte: 18 } }] },
|
|
47
|
+
{ name: { $regex: '^User' } },
|
|
48
|
+
{ age: { $in: [25, 30, 35, 40] } },
|
|
49
|
+
{ status: { $ne: 'deleted' } },
|
|
50
|
+
{ email: { $exists: true } }
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
const iterations = 100_000;
|
|
54
|
+
const runs = 15;
|
|
55
|
+
const times: number[] = [];
|
|
56
|
+
|
|
57
|
+
console.log(`Running ${runs} iterations of ${iterations.toLocaleString()} queries each...\n`);
|
|
58
|
+
|
|
59
|
+
for (let run = 0; run < runs; run++) {
|
|
60
|
+
const start = performance.now();
|
|
61
|
+
for (let i = 0; i < iterations; i++) {
|
|
62
|
+
const query = queries[i % queries.length];
|
|
63
|
+
buildWhereClause(query, schema);
|
|
64
|
+
}
|
|
65
|
+
const end = performance.now();
|
|
66
|
+
const time = end - start;
|
|
67
|
+
times.push(time);
|
|
68
|
+
|
|
69
|
+
const avgPerQuery = (time / iterations) * 1000; // in microseconds
|
|
70
|
+
console.log(`Run ${(run + 1).toString().padStart(2)}: ${time.toFixed(2).padStart(8)}ms total | ${avgPerQuery.toFixed(2).padStart(6)}µs per query`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
console.log("\n📊 Statistics:");
|
|
74
|
+
const avg = times.reduce((a, b) => a + b, 0) / times.length;
|
|
75
|
+
const min = Math.min(...times);
|
|
76
|
+
const max = Math.max(...times);
|
|
77
|
+
const sorted = [...times].sort((a, b) => a - b);
|
|
78
|
+
const median = sorted[Math.floor(sorted.length / 2)];
|
|
79
|
+
const stdDev = Math.sqrt(times.reduce((sum, t) => sum + Math.pow(t - avg, 2), 0) / times.length);
|
|
80
|
+
|
|
81
|
+
console.log(` Average: ${avg.toFixed(2)}ms (${(avg / iterations * 1000).toFixed(2)}µs per query)`);
|
|
82
|
+
console.log(` Median: ${median.toFixed(2)}ms (${(median / iterations * 1000).toFixed(2)}µs per query)`);
|
|
83
|
+
console.log(` Min: ${min.toFixed(2)}ms (${(min / iterations * 1000).toFixed(2)}µs per query)`);
|
|
84
|
+
console.log(` Max: ${max.toFixed(2)}ms (${(max / iterations * 1000).toFixed(2)}µs per query)`);
|
|
85
|
+
console.log(` Std Dev: ${stdDev.toFixed(2)}ms`);
|
|
86
|
+
console.log(` Variance: ${((max - min) / avg * 100).toFixed(1)}%`);
|
|
87
|
+
|
|
88
|
+
console.log("\n✅ Consistency Analysis:");
|
|
89
|
+
if (stdDev < avg * 0.1) {
|
|
90
|
+
console.log(" VERY CONSISTENT (std dev < 10% of average)");
|
|
91
|
+
} else if (stdDev < avg * 0.2) {
|
|
92
|
+
console.log(" CONSISTENT (std dev < 20% of average)");
|
|
93
|
+
} else {
|
|
94
|
+
console.log(" INCONSISTENT (std dev > 20% of average)");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
console.log("\n🔍 Cache Detection:");
|
|
98
|
+
const firstRun = times[0];
|
|
99
|
+
const avgLaterRuns = times.slice(1).reduce((a, b) => a + b, 0) / (times.length - 1);
|
|
100
|
+
const improvement = ((firstRun - avgLaterRuns) / firstRun * 100);
|
|
101
|
+
|
|
102
|
+
if (improvement > 50) {
|
|
103
|
+
console.log(` CACHE DETECTED! First run: ${firstRun.toFixed(2)}ms, Later runs: ${avgLaterRuns.toFixed(2)}ms`);
|
|
104
|
+
console.log(` Improvement: ${improvement.toFixed(1)}% faster after warmup`);
|
|
105
|
+
} else if (improvement > 10) {
|
|
106
|
+
console.log(` WARMUP DETECTED: ${improvement.toFixed(1)}% faster after first run (JIT optimization)`);
|
|
107
|
+
} else {
|
|
108
|
+
console.log(` NO CACHE: Performance consistent across all runs (${improvement.toFixed(1)}% variance)`);
|
|
109
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import Database from "better-sqlite3";
|
|
2
|
+
|
|
3
|
+
console.log("🏴☠️ better-sqlite3 Large-Scale Benchmark (1M docs)\n");
|
|
4
|
+
|
|
5
|
+
const db = new Database("benchmark-node-1m.db");
|
|
6
|
+
|
|
7
|
+
db.exec("PRAGMA journal_mode = WAL");
|
|
8
|
+
db.exec("PRAGMA synchronous = 1");
|
|
9
|
+
|
|
10
|
+
db.exec("DROP TABLE IF EXISTS docs");
|
|
11
|
+
db.exec(`
|
|
12
|
+
CREATE TABLE docs (
|
|
13
|
+
id TEXT PRIMARY KEY,
|
|
14
|
+
name TEXT,
|
|
15
|
+
age INTEGER,
|
|
16
|
+
email TEXT,
|
|
17
|
+
status TEXT
|
|
18
|
+
)
|
|
19
|
+
`);
|
|
20
|
+
|
|
21
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_age ON docs(age)`);
|
|
22
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_status ON docs(status)`);
|
|
23
|
+
|
|
24
|
+
console.log("📊 Test: Bulk INSERT (1,000,000 docs in batches of 10k)");
|
|
25
|
+
const insertStmt = db.prepare("INSERT INTO docs (id, name, age, email, status) VALUES (?, ?, ?, ?, ?)");
|
|
26
|
+
const insertBatch = db.transaction((docs: Array<{ id: string; name: string; age: number; email: string; status: string }>) => {
|
|
27
|
+
for (const doc of docs) {
|
|
28
|
+
insertStmt.run(doc.id, doc.name, doc.age, doc.email, doc.status);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const totalDocs = 1_000_000;
|
|
33
|
+
const batchSize = 10_000;
|
|
34
|
+
const batches = totalDocs / batchSize;
|
|
35
|
+
|
|
36
|
+
const insertStart = performance.now();
|
|
37
|
+
for (let batch = 0; batch < batches; batch++) {
|
|
38
|
+
const docs = [];
|
|
39
|
+
for (let i = 0; i < batchSize; i++) {
|
|
40
|
+
const docId = batch * batchSize + i;
|
|
41
|
+
docs.push({
|
|
42
|
+
id: `doc${docId}`,
|
|
43
|
+
name: `User ${docId}`,
|
|
44
|
+
age: 18 + (docId % 50),
|
|
45
|
+
email: `user${docId}@example.com`,
|
|
46
|
+
status: docId % 2 === 0 ? "active" : "inactive"
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
insertBatch(docs);
|
|
50
|
+
if ((batch + 1) % 100 === 0) {
|
|
51
|
+
console.log(` Progress: ${((batch + 1) / batches * 100).toFixed(1)}% (${(batch + 1) * batchSize} docs)`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const insertEnd = performance.now();
|
|
55
|
+
const insertTime = insertEnd - insertStart;
|
|
56
|
+
console.log(`\n Total time: ${(insertTime / 1000).toFixed(2)}s`);
|
|
57
|
+
console.log(` Throughput: ${(totalDocs / (insertTime / 1000)).toFixed(0)} docs/sec\n`);
|
|
58
|
+
|
|
59
|
+
console.log("📊 Test: SELECT by ID (10,000 random lookups)");
|
|
60
|
+
const selectStmt = db.prepare("SELECT * FROM docs WHERE id = ?");
|
|
61
|
+
const selectStart = performance.now();
|
|
62
|
+
for (let i = 0; i < 10000; i++) {
|
|
63
|
+
const randomId = Math.floor(Math.random() * totalDocs);
|
|
64
|
+
selectStmt.get(`doc${randomId}`);
|
|
65
|
+
}
|
|
66
|
+
const selectEnd = performance.now();
|
|
67
|
+
const selectTime = selectEnd - selectStart;
|
|
68
|
+
console.log(` Time: ${selectTime.toFixed(2)}ms`);
|
|
69
|
+
console.log(` Throughput: ${(10000 / (selectTime / 1000)).toFixed(0)} docs/sec\n`);
|
|
70
|
+
|
|
71
|
+
console.log("📊 Test: SELECT with WHERE (indexed)");
|
|
72
|
+
const whereStmt = db.prepare("SELECT * FROM docs WHERE age > ? AND status = ?");
|
|
73
|
+
const whereStart = performance.now();
|
|
74
|
+
const results = whereStmt.all(30, "active");
|
|
75
|
+
const whereEnd = performance.now();
|
|
76
|
+
const whereTime = whereEnd - whereStart;
|
|
77
|
+
console.log(` Found: ${results.length} docs`);
|
|
78
|
+
console.log(` Time: ${whereTime.toFixed(2)}ms\n`);
|
|
79
|
+
|
|
80
|
+
console.log("✅ Summary:");
|
|
81
|
+
console.log(` INSERT 10M: ${(insertTime / 1000).toFixed(2)}s (${(totalDocs / (insertTime / 1000)).toFixed(0)} docs/sec)`);
|
|
82
|
+
console.log(` SELECT by ID: ${selectTime.toFixed(2)}ms (${(10000 / (selectTime / 1000)).toFixed(0)} docs/sec)`);
|
|
83
|
+
console.log(` WHERE indexed: ${whereTime.toFixed(2)}ms`);
|
|
84
|
+
|
|
85
|
+
db.close();
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import Database from "better-sqlite3";
|
|
2
|
+
|
|
3
|
+
console.log("🏴☠️ better-sqlite3 Raw Performance Benchmark\n");
|
|
4
|
+
|
|
5
|
+
const db = new Database(":memory:");
|
|
6
|
+
|
|
7
|
+
// Enable WAL mode + optimize for speed
|
|
8
|
+
db.exec("PRAGMA journal_mode = WAL");
|
|
9
|
+
db.exec("PRAGMA synchronous = 1");
|
|
10
|
+
|
|
11
|
+
// Create table
|
|
12
|
+
db.exec(`
|
|
13
|
+
CREATE TABLE docs (
|
|
14
|
+
id TEXT PRIMARY KEY,
|
|
15
|
+
name TEXT,
|
|
16
|
+
age INTEGER,
|
|
17
|
+
email TEXT,
|
|
18
|
+
status TEXT
|
|
19
|
+
)
|
|
20
|
+
`);
|
|
21
|
+
|
|
22
|
+
db.exec(`CREATE INDEX idx_age ON docs(age)`);
|
|
23
|
+
db.exec(`CREATE INDEX idx_status ON docs(status)`);
|
|
24
|
+
|
|
25
|
+
console.log("📊 Test 1: Bulk INSERT (10,000 docs)");
|
|
26
|
+
const insertStmt = db.prepare("INSERT INTO docs (id, name, age, email, status) VALUES (?, ?, ?, ?, ?)");
|
|
27
|
+
const insertMany = db.transaction((docs: Array<{ id: string; name: string; age: number; email: string; status: string }>) => {
|
|
28
|
+
for (const doc of docs) {
|
|
29
|
+
insertStmt.run(doc.id, doc.name, doc.age, doc.email, doc.status);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const docs = [];
|
|
34
|
+
for (let i = 0; i < 10000; i++) {
|
|
35
|
+
docs.push({
|
|
36
|
+
id: `doc${i}`,
|
|
37
|
+
name: `User ${i}`,
|
|
38
|
+
age: 18 + (i % 50),
|
|
39
|
+
email: `user${i}@example.com`,
|
|
40
|
+
status: i % 2 === 0 ? "active" : "inactive"
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const insertStart = performance.now();
|
|
45
|
+
insertMany(docs);
|
|
46
|
+
const insertEnd = performance.now();
|
|
47
|
+
const insertTime = insertEnd - insertStart;
|
|
48
|
+
console.log(` Time: ${insertTime.toFixed(2)}ms`);
|
|
49
|
+
console.log(` Throughput: ${(10000 / (insertTime / 1000)).toFixed(0)} docs/sec\n`);
|
|
50
|
+
|
|
51
|
+
console.log("📊 Test 2: SELECT by ID (1,000 lookups)");
|
|
52
|
+
const selectStmt = db.prepare("SELECT * FROM docs WHERE id = ?");
|
|
53
|
+
const selectStart = performance.now();
|
|
54
|
+
for (let i = 0; i < 1000; i++) {
|
|
55
|
+
selectStmt.get(`doc${i}`);
|
|
56
|
+
}
|
|
57
|
+
const selectEnd = performance.now();
|
|
58
|
+
const selectTime = selectEnd - selectStart;
|
|
59
|
+
console.log(` Time: ${selectTime.toFixed(2)}ms`);
|
|
60
|
+
console.log(` Throughput: ${(1000 / (selectTime / 1000)).toFixed(0)} docs/sec\n`);
|
|
61
|
+
|
|
62
|
+
console.log("📊 Test 3: SELECT with WHERE (indexed)");
|
|
63
|
+
const whereStmt = db.prepare("SELECT * FROM docs WHERE age > ? AND status = ?");
|
|
64
|
+
const whereStart = performance.now();
|
|
65
|
+
const results = whereStmt.all(30, "active");
|
|
66
|
+
const whereEnd = performance.now();
|
|
67
|
+
const whereTime = whereEnd - whereStart;
|
|
68
|
+
console.log(` Found: ${results.length} docs`);
|
|
69
|
+
console.log(` Time: ${whereTime.toFixed(2)}ms\n`);
|
|
70
|
+
|
|
71
|
+
console.log("📊 Test 4: Complex query (no index)");
|
|
72
|
+
const complexStmt = db.prepare("SELECT * FROM docs WHERE name LIKE ? ORDER BY age DESC LIMIT 100");
|
|
73
|
+
const complexStart = performance.now();
|
|
74
|
+
const complexResults = complexStmt.all("%User 1%");
|
|
75
|
+
const complexEnd = performance.now();
|
|
76
|
+
const complexTime = complexEnd - complexStart;
|
|
77
|
+
console.log(` Found: ${complexResults.length} docs`);
|
|
78
|
+
console.log(` Time: ${complexTime.toFixed(2)}ms\n`);
|
|
79
|
+
|
|
80
|
+
console.log("✅ Summary:");
|
|
81
|
+
console.log(` INSERT: ${insertTime.toFixed(2)}ms (${(10000 / (insertTime / 1000)).toFixed(0)} docs/sec)`);
|
|
82
|
+
console.log(` SELECT by ID: ${selectTime.toFixed(2)}ms (${(1000 / (selectTime / 1000)).toFixed(0)} docs/sec)`);
|
|
83
|
+
console.log(` WHERE indexed: ${whereTime.toFixed(2)}ms`);
|
|
84
|
+
console.log(` Complex query: ${complexTime.toFixed(2)}ms`);
|
|
85
|
+
|
|
86
|
+
db.close();
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { Database } from "bun:sqlite";
|
|
2
|
+
|
|
3
|
+
console.log("🏴☠️ Bun SQLite Large-Scale Benchmark (1M docs)\n");
|
|
4
|
+
|
|
5
|
+
const db = new Database("benchmark-bun-1m.db");
|
|
6
|
+
|
|
7
|
+
db.run("PRAGMA journal_mode = WAL");
|
|
8
|
+
db.run("PRAGMA synchronous = 1");
|
|
9
|
+
|
|
10
|
+
db.run("DROP TABLE IF EXISTS docs");
|
|
11
|
+
db.run(`
|
|
12
|
+
CREATE TABLE docs (
|
|
13
|
+
id TEXT PRIMARY KEY,
|
|
14
|
+
name TEXT,
|
|
15
|
+
age INTEGER,
|
|
16
|
+
email TEXT,
|
|
17
|
+
status TEXT
|
|
18
|
+
)
|
|
19
|
+
`);
|
|
20
|
+
|
|
21
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_age ON docs(age)`);
|
|
22
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_status ON docs(status)`);
|
|
23
|
+
|
|
24
|
+
console.log("📊 Test: Bulk INSERT (1,000,000 docs in batches of 10k)");
|
|
25
|
+
const insertStmt = db.prepare("INSERT INTO docs (id, name, age, email, status) VALUES (?, ?, ?, ?, ?)");
|
|
26
|
+
const insertBatch = db.transaction((docs: Array<{ id: string; name: string; age: number; email: string; status: string }>) => {
|
|
27
|
+
for (const doc of docs) {
|
|
28
|
+
insertStmt.run(doc.id, doc.name, doc.age, doc.email, doc.status);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const totalDocs = 1_000_000;
|
|
33
|
+
const batchSize = 10_000;
|
|
34
|
+
const batches = totalDocs / batchSize;
|
|
35
|
+
|
|
36
|
+
const insertStart = performance.now();
|
|
37
|
+
for (let batch = 0; batch < batches; batch++) {
|
|
38
|
+
const docs = [];
|
|
39
|
+
for (let i = 0; i < batchSize; i++) {
|
|
40
|
+
const docId = batch * batchSize + i;
|
|
41
|
+
docs.push({
|
|
42
|
+
id: `doc${docId}`,
|
|
43
|
+
name: `User ${docId}`,
|
|
44
|
+
age: 18 + (docId % 50),
|
|
45
|
+
email: `user${docId}@example.com`,
|
|
46
|
+
status: docId % 2 === 0 ? "active" : "inactive"
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
insertBatch(docs);
|
|
50
|
+
if ((batch + 1) % 100 === 0) {
|
|
51
|
+
console.log(` Progress: ${((batch + 1) / batches * 100).toFixed(1)}% (${(batch + 1) * batchSize} docs)`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const insertEnd = performance.now();
|
|
55
|
+
const insertTime = insertEnd - insertStart;
|
|
56
|
+
console.log(`\n Total time: ${(insertTime / 1000).toFixed(2)}s`);
|
|
57
|
+
console.log(` Throughput: ${(totalDocs / (insertTime / 1000)).toFixed(0)} docs/sec\n`);
|
|
58
|
+
|
|
59
|
+
console.log("📊 Test: SELECT by ID (10,000 random lookups)");
|
|
60
|
+
const selectStmt = db.prepare("SELECT * FROM docs WHERE id = ?");
|
|
61
|
+
const selectStart = performance.now();
|
|
62
|
+
for (let i = 0; i < 10000; i++) {
|
|
63
|
+
const randomId = Math.floor(Math.random() * totalDocs);
|
|
64
|
+
selectStmt.get(`doc${randomId}`);
|
|
65
|
+
}
|
|
66
|
+
const selectEnd = performance.now();
|
|
67
|
+
const selectTime = selectEnd - selectStart;
|
|
68
|
+
console.log(` Time: ${selectTime.toFixed(2)}ms`);
|
|
69
|
+
console.log(` Throughput: ${(10000 / (selectTime / 1000)).toFixed(0)} docs/sec\n`);
|
|
70
|
+
|
|
71
|
+
console.log("📊 Test: SELECT with WHERE (indexed)");
|
|
72
|
+
const whereStmt = db.prepare("SELECT * FROM docs WHERE age > ? AND status = ?");
|
|
73
|
+
const whereStart = performance.now();
|
|
74
|
+
const results = whereStmt.all(30, "active");
|
|
75
|
+
const whereEnd = performance.now();
|
|
76
|
+
const whereTime = whereEnd - whereStart;
|
|
77
|
+
console.log(` Found: ${results.length} docs`);
|
|
78
|
+
console.log(` Time: ${whereTime.toFixed(2)}ms\n`);
|
|
79
|
+
|
|
80
|
+
console.log("✅ Summary:");
|
|
81
|
+
console.log(` INSERT 10M: ${(insertTime / 1000).toFixed(2)}s (${(totalDocs / (insertTime / 1000)).toFixed(0)} docs/sec)`);
|
|
82
|
+
console.log(` SELECT by ID: ${selectTime.toFixed(2)}ms (${(10000 / (selectTime / 1000)).toFixed(0)} docs/sec)`);
|
|
83
|
+
console.log(` WHERE indexed: ${whereTime.toFixed(2)}ms`);
|
|
84
|
+
|
|
85
|
+
db.close();
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { Database } from "bun:sqlite";
|
|
2
|
+
|
|
3
|
+
console.log("🏴☠️ Bun SQLite Raw Performance Benchmark\n");
|
|
4
|
+
|
|
5
|
+
const db = new Database(":memory:");
|
|
6
|
+
|
|
7
|
+
// Enable WAL mode + optimize for speed
|
|
8
|
+
db.run("PRAGMA journal_mode = WAL");
|
|
9
|
+
db.run("PRAGMA synchronous = 1");
|
|
10
|
+
|
|
11
|
+
// Create table
|
|
12
|
+
db.run(`
|
|
13
|
+
CREATE TABLE docs (
|
|
14
|
+
id TEXT PRIMARY KEY,
|
|
15
|
+
name TEXT,
|
|
16
|
+
age INTEGER,
|
|
17
|
+
email TEXT,
|
|
18
|
+
status TEXT
|
|
19
|
+
)
|
|
20
|
+
`);
|
|
21
|
+
|
|
22
|
+
db.run(`CREATE INDEX idx_age ON docs(age)`);
|
|
23
|
+
db.run(`CREATE INDEX idx_status ON docs(status)`);
|
|
24
|
+
|
|
25
|
+
console.log("📊 Test 1: Bulk INSERT (10,000 docs)");
|
|
26
|
+
const insertStmt = db.prepare("INSERT INTO docs (id, name, age, email, status) VALUES (?, ?, ?, ?, ?)");
|
|
27
|
+
const insertMany = db.transaction((docs: Array<{ id: string; name: string; age: number; email: string; status: string }>) => {
|
|
28
|
+
for (const doc of docs) {
|
|
29
|
+
insertStmt.run(doc.id, doc.name, doc.age, doc.email, doc.status);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const docs = [];
|
|
34
|
+
for (let i = 0; i < 10000; i++) {
|
|
35
|
+
docs.push({
|
|
36
|
+
id: `doc${i}`,
|
|
37
|
+
name: `User ${i}`,
|
|
38
|
+
age: 18 + (i % 50),
|
|
39
|
+
email: `user${i}@example.com`,
|
|
40
|
+
status: i % 2 === 0 ? "active" : "inactive"
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const insertStart = performance.now();
|
|
45
|
+
insertMany(docs);
|
|
46
|
+
const insertEnd = performance.now();
|
|
47
|
+
const insertTime = insertEnd - insertStart;
|
|
48
|
+
console.log(` Time: ${insertTime.toFixed(2)}ms`);
|
|
49
|
+
console.log(` Throughput: ${(10000 / (insertTime / 1000)).toFixed(0)} docs/sec\n`);
|
|
50
|
+
|
|
51
|
+
console.log("📊 Test 2: SELECT by ID (1,000 lookups)");
|
|
52
|
+
const selectStmt = db.prepare("SELECT * FROM docs WHERE id = ?");
|
|
53
|
+
const selectStart = performance.now();
|
|
54
|
+
for (let i = 0; i < 1000; i++) {
|
|
55
|
+
selectStmt.get(`doc${i}`);
|
|
56
|
+
}
|
|
57
|
+
const selectEnd = performance.now();
|
|
58
|
+
const selectTime = selectEnd - selectStart;
|
|
59
|
+
console.log(` Time: ${selectTime.toFixed(2)}ms`);
|
|
60
|
+
console.log(` Throughput: ${(1000 / (selectTime / 1000)).toFixed(0)} docs/sec\n`);
|
|
61
|
+
|
|
62
|
+
console.log("📊 Test 3: SELECT with WHERE (indexed)");
|
|
63
|
+
const whereStmt = db.prepare("SELECT * FROM docs WHERE age > ? AND status = ?");
|
|
64
|
+
const whereStart = performance.now();
|
|
65
|
+
const results = whereStmt.all(30, "active");
|
|
66
|
+
const whereEnd = performance.now();
|
|
67
|
+
const whereTime = whereEnd - whereStart;
|
|
68
|
+
console.log(` Found: ${results.length} docs`);
|
|
69
|
+
console.log(` Time: ${whereTime.toFixed(2)}ms\n`);
|
|
70
|
+
|
|
71
|
+
console.log("📊 Test 4: Complex query (no index)");
|
|
72
|
+
const complexStmt = db.prepare("SELECT * FROM docs WHERE name LIKE ? ORDER BY age DESC LIMIT 100");
|
|
73
|
+
const complexStart = performance.now();
|
|
74
|
+
const complexResults = complexStmt.all("%User 1%");
|
|
75
|
+
const complexEnd = performance.now();
|
|
76
|
+
const complexTime = complexEnd - complexStart;
|
|
77
|
+
console.log(` Found: ${complexResults.length} docs`);
|
|
78
|
+
console.log(` Time: ${complexTime.toFixed(2)}ms\n`);
|
|
79
|
+
|
|
80
|
+
console.log("✅ Summary:");
|
|
81
|
+
console.log(` INSERT: ${insertTime.toFixed(2)}ms (${(10000 / (insertTime / 1000)).toFixed(0)} docs/sec)`);
|
|
82
|
+
console.log(` SELECT by ID: ${selectTime.toFixed(2)}ms (${(1000 / (selectTime / 1000)).toFixed(0)} docs/sec)`);
|
|
83
|
+
console.log(` WHERE indexed: ${whereTime.toFixed(2)}ms`);
|
|
84
|
+
console.log(` Complex query: ${complexTime.toFixed(2)}ms`);
|
|
85
|
+
|
|
86
|
+
db.close();
|