forked from tursodatabase/turso
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathturso-sql-runner.mjs
More file actions
135 lines (119 loc) · 4.21 KB
/
Copy pathturso-sql-runner.mjs
File metadata and controls
135 lines (119 loc) · 4.21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#!/usr/bin/env node
/**
* SQL runner script for the sqltest JavaScript backend.
* Reads SQL from stdin, executes via @tursodatabase/database, outputs pipe-separated results.
*
* Usage: node turso-sql-runner.mjs <database_path> [--readonly]
*
* This script expects to be run from the bindings/javascript directory where
* the @tursodatabase/database package is available.
*
* Known limitations:
* - JavaScript's number type doesn't distinguish between 1 and 1.0, so float
* formatting may differ from the Rust backend for whole-number floats.
* - Very large integers (exceeding i64) may have precision loss as JavaScript
* numbers are IEEE 754 doubles with 53 bits of mantissa precision.
*/
import { pathToFileURL } from 'node:url';
import { splitStatements } from './turso-sql-split.mjs';
async function readStdin() {
const chunks = [];
for await (const chunk of process.stdin) {
chunks.push(chunk);
}
return Buffer.concat(chunks).toString('utf-8');
}
function formatValue(value) {
if (value === null || value === undefined) {
return '';
}
if (typeof value === 'bigint') {
return value.toString();
}
if (typeof value === 'number') {
// Handle special float values to match SQLite output
if (value === Infinity) {
return 'Inf';
}
if (value === -Infinity) {
return '-Inf';
}
if (Number.isNaN(value)) {
return ''; // SQLite returns NULL for NaN
}
// For integers, use toString() directly
if (Number.isInteger(value)) {
return value.toString();
}
// SQLite uses %.15g format (15 significant digits, trailing zeros removed)
// toPrecision gives significant digits, parseFloat removes trailing zeros
return parseFloat(value.toPrecision(15)).toString();
}
if (value instanceof Uint8Array || Buffer.isBuffer(value)) {
// Output blob as raw bytes (matches SQLite/Rust backend behavior)
// This will display as text if the bytes are printable ASCII
return Buffer.from(value).toString('utf-8');
}
return String(value);
}
function formatRow(row) {
// Row is an array in raw mode
return row.map(formatValue).join('|');
}
async function main() {
const args = process.argv.slice(2);
if (args.length < 1) {
console.error('Usage: turso-sql-runner.mjs <database_path> [--readonly]');
process.exit(1);
}
const dbPath = args[0];
const readonly = args.includes('--readonly');
const sql = await readStdin();
if (!sql.trim()) {
process.exit(0);
}
let db;
try {
const { connect } = await import('@tursodatabase/database');
db = await connect(dbPath, { readonly, experimental: ['triggers', 'attach', 'generated_columns', 'without_rowid'] });
// Enable safe integers to preserve precision for large integers
db.defaultSafeIntegers(true);
} catch (err) {
console.error(`Error: ${err.message}`);
process.exit(1);
}
try {
// Split into individual statements, filtering out comments and empty lines
const statements = splitStatements(sql);
// Accumulate results from ALL queries (matches Rust backend behavior)
const allResults = [];
for (const stmt of statements) {
const trimmed = stmt.trim();
if (!trimmed) continue;
const prepared = db.prepare(trimmed);
prepared.raw(true);
const rows = await prepared.all();
allResults.push(...rows);
prepared.close();
}
// Output all accumulated results
for (const row of allResults) {
console.log(formatRow(row));
}
} catch (err) {
// Output error in a format the test runner can detect
console.log(`Error: ${err.message}`);
process.exit(0); // Exit 0 so the error can be captured as output
} finally {
if (db) {
await db.close();
}
}
}
const isMain = process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href;
if (isMain) {
main().catch(err => {
console.error(`Error: ${err.message}`);
process.exit(1);
});
}