Skip to content

Commit 787e814

Browse files
committed
versions
1 parent 334ea4a commit 787e814

File tree

20 files changed

+2525
-78
lines changed

20 files changed

+2525
-78
lines changed

full/Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ ifdef EMSCRIPTEN
5757
-I$(LIBPG_QUERY_DIR) \
5858
-I$(LIBPG_QUERY_DIR)/vendor \
5959
-L$(LIBPG_QUERY_DIR) \
60-
-sEXPORTED_FUNCTIONS="['_malloc','_free','_wasm_parse_query','_wasm_parse_query_protobuf','_wasm_get_protobuf_len','_wasm_deparse_protobuf','_wasm_parse_plpgsql','_wasm_fingerprint','_wasm_normalize_query','_wasm_scan','_wasm_parse_query_detailed','_wasm_free_detailed_result','_wasm_free_string']" \
61-
-sEXPORTED_RUNTIME_METHODS="['lengthBytesUTF8','stringToUTF8','UTF8ToString','HEAPU8','HEAPU32']" \
60+
-sEXPORTED_FUNCTIONS="['_malloc','_free','_wasm_parse_query','_wasm_parse_query_protobuf','_wasm_get_protobuf_len','_wasm_deparse_protobuf','_wasm_parse_plpgsql','_wasm_fingerprint','_wasm_normalize_query','_wasm_scan','_wasm_parse_query_detailed','_wasm_free_detailed_result','_wasm_free_string','_wasm_parse_query_raw','_wasm_free_parse_result']" \
61+
-sEXPORTED_RUNTIME_METHODS="['lengthBytesUTF8','stringToUTF8','UTF8ToString','getValue','HEAPU8','HEAPU32']" \
6262
-sEXPORT_NAME="$(WASM_MODULE_NAME)" \
6363
-sENVIRONMENT="web,node" \
6464
-sMODULARIZE=1 \

full/src/index.ts

Lines changed: 157 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,71 @@ export interface ScanResult {
1616
tokens: ScanToken[];
1717
}
1818

19+
export interface SqlErrorDetails {
20+
message: string;
21+
cursorPosition: number;
22+
fileName?: string;
23+
functionName?: string;
24+
lineNumber?: number;
25+
context?: string;
26+
}
27+
28+
export class SqlError extends Error {
29+
sqlDetails?: SqlErrorDetails;
30+
31+
constructor(message: string, details?: SqlErrorDetails) {
32+
super(message);
33+
this.name = 'SqlError';
34+
this.sqlDetails = details;
35+
}
36+
}
37+
38+
export function hasSqlDetails(error: unknown): error is SqlError {
39+
return error instanceof SqlError && error.sqlDetails !== undefined;
40+
}
41+
42+
export function formatSqlError(error: SqlError, query?: string, options?: {
43+
showPosition?: boolean;
44+
showSource?: boolean;
45+
useColors?: boolean;
46+
}): string {
47+
const opts = { showPosition: true, showSource: true, useColors: false, ...options };
48+
let output = `Error: ${error.message}`;
49+
50+
if (error.sqlDetails) {
51+
const details = error.sqlDetails;
52+
53+
if (opts.showPosition && details.cursorPosition !== undefined) {
54+
output += `\nPosition: ${details.cursorPosition}`;
55+
}
56+
57+
if (opts.showSource && (details.fileName || details.functionName || details.lineNumber)) {
58+
output += '\nSource:';
59+
if (details.fileName) output += ` file: ${details.fileName},`;
60+
if (details.functionName) output += ` function: ${details.functionName},`;
61+
if (details.lineNumber) output += ` line: ${details.lineNumber}`;
62+
}
63+
64+
if (opts.showPosition && query && details.cursorPosition !== undefined && details.cursorPosition >= 0) {
65+
const lines = query.split('\n');
66+
let currentPos = 0;
67+
68+
for (let i = 0; i < lines.length; i++) {
69+
const lineLength = lines[i].length + 1; // +1 for newline
70+
if (currentPos + lineLength > details.cursorPosition) {
71+
const posInLine = details.cursorPosition - currentPos;
72+
output += `\n${lines[i]}`;
73+
output += '\n' + ' '.repeat(posInLine) + '^';
74+
break;
75+
}
76+
currentPos += lineLength;
77+
}
78+
}
79+
}
80+
81+
return output;
82+
}
83+
1984
// @ts-ignore
2085
import PgQueryModule from './libpg-query.js';
2186
// @ts-ignore
@@ -26,6 +91,8 @@ interface WasmModule {
2691
_free: (ptr: number) => void;
2792
_wasm_free_string: (ptr: number) => void;
2893
_wasm_parse_query: (queryPtr: number) => number;
94+
_wasm_parse_query_raw: (queryPtr: number) => number;
95+
_wasm_free_parse_result: (ptr: number) => void;
2996
_wasm_deparse_protobuf: (dataPtr: number, length: number) => number;
3097
_wasm_parse_plpgsql: (queryPtr: number) => number;
3198
_wasm_fingerprint: (queryPtr: number) => number;
@@ -34,6 +101,7 @@ interface WasmModule {
34101
lengthBytesUTF8: (str: string) => number;
35102
stringToUTF8: (str: string, ptr: number, len: number) => void;
36103
UTF8ToString: (ptr: number) => string;
104+
getValue: (ptr: number, type: string) => number;
37105
HEAPU8: Uint8Array;
38106
}
39107

@@ -85,22 +153,60 @@ function ptrToString(ptr: number): string {
85153
}
86154

87155
export const parse = awaitInit(async (query: string): Promise<ParseResult> => {
156+
// Input validation
157+
if (query === null || query === undefined) {
158+
throw new SqlError('Query cannot be null or undefined');
159+
}
160+
161+
if (query === '') {
162+
throw new SqlError('Query cannot be empty');
163+
}
164+
88165
const queryPtr = stringToPtr(query);
89166
let resultPtr = 0;
90167

91168
try {
92-
resultPtr = wasmModule._wasm_parse_query(queryPtr);
93-
const resultStr = ptrToString(resultPtr);
169+
resultPtr = wasmModule._wasm_parse_query_raw(queryPtr);
170+
if (!resultPtr) {
171+
throw new SqlError('Failed to parse query: memory allocation failed');
172+
}
94173

95-
if (resultStr.startsWith('syntax error') || resultStr.startsWith('deparse error') || resultStr.startsWith('ERROR')) {
96-
throw new Error(resultStr);
174+
// Read the PgQueryParseResult struct
175+
const parseTreePtr = wasmModule.getValue(resultPtr, 'i32');
176+
const stderrBufferPtr = wasmModule.getValue(resultPtr + 4, 'i32');
177+
const errorPtr = wasmModule.getValue(resultPtr + 8, 'i32');
178+
179+
if (errorPtr) {
180+
// Read PgQueryError struct
181+
const messagePtr = wasmModule.getValue(errorPtr, 'i32');
182+
const funcnamePtr = wasmModule.getValue(errorPtr + 4, 'i32');
183+
const filenamePtr = wasmModule.getValue(errorPtr + 8, 'i32');
184+
const lineno = wasmModule.getValue(errorPtr + 12, 'i32');
185+
const cursorpos = wasmModule.getValue(errorPtr + 16, 'i32');
186+
187+
const message = messagePtr ? wasmModule.UTF8ToString(messagePtr) : 'Unknown error';
188+
const funcname = funcnamePtr ? wasmModule.UTF8ToString(funcnamePtr) : undefined;
189+
const filename = filenamePtr ? wasmModule.UTF8ToString(filenamePtr) : undefined;
190+
191+
throw new SqlError(message, {
192+
message,
193+
cursorPosition: cursorpos,
194+
fileName: filename,
195+
functionName: funcname,
196+
lineNumber: lineno > 0 ? lineno : undefined
197+
});
97198
}
98199

99-
return JSON.parse(resultStr);
200+
if (!parseTreePtr) {
201+
throw new SqlError('No parse tree generated');
202+
}
203+
204+
const parseTreeStr = wasmModule.UTF8ToString(parseTreePtr);
205+
return JSON.parse(parseTreeStr);
100206
} finally {
101207
wasmModule._free(queryPtr);
102208
if (resultPtr) {
103-
wasmModule._wasm_free_string(resultPtr);
209+
wasmModule._wasm_free_parse_result(resultPtr);
104210
}
105211
}
106212
});
@@ -202,22 +308,61 @@ export function parseSync(query: string): ParseResult {
202308
if (!wasmModule) {
203309
throw new Error('WASM module not initialized. Call loadModule() first.');
204310
}
311+
312+
// Input validation
313+
if (query === null || query === undefined) {
314+
throw new SqlError('Query cannot be null or undefined');
315+
}
316+
317+
if (query === '') {
318+
throw new SqlError('Query cannot be empty');
319+
}
320+
205321
const queryPtr = stringToPtr(query);
206322
let resultPtr = 0;
207323

208324
try {
209-
resultPtr = wasmModule._wasm_parse_query(queryPtr);
210-
const resultStr = ptrToString(resultPtr);
325+
resultPtr = wasmModule._wasm_parse_query_raw(queryPtr);
326+
if (!resultPtr) {
327+
throw new SqlError('Failed to parse query: memory allocation failed');
328+
}
211329

212-
if (resultStr.startsWith('syntax error') || resultStr.startsWith('deparse error') || resultStr.startsWith('ERROR')) {
213-
throw new Error(resultStr);
330+
// Read the PgQueryParseResult struct
331+
const parseTreePtr = wasmModule.getValue(resultPtr, 'i32');
332+
const stderrBufferPtr = wasmModule.getValue(resultPtr + 4, 'i32');
333+
const errorPtr = wasmModule.getValue(resultPtr + 8, 'i32');
334+
335+
if (errorPtr) {
336+
// Read PgQueryError struct
337+
const messagePtr = wasmModule.getValue(errorPtr, 'i32');
338+
const funcnamePtr = wasmModule.getValue(errorPtr + 4, 'i32');
339+
const filenamePtr = wasmModule.getValue(errorPtr + 8, 'i32');
340+
const lineno = wasmModule.getValue(errorPtr + 12, 'i32');
341+
const cursorpos = wasmModule.getValue(errorPtr + 16, 'i32');
342+
343+
const message = messagePtr ? wasmModule.UTF8ToString(messagePtr) : 'Unknown error';
344+
const funcname = funcnamePtr ? wasmModule.UTF8ToString(funcnamePtr) : undefined;
345+
const filename = filenamePtr ? wasmModule.UTF8ToString(filenamePtr) : undefined;
346+
347+
throw new SqlError(message, {
348+
message,
349+
cursorPosition: cursorpos,
350+
fileName: filename,
351+
functionName: funcname,
352+
lineNumber: lineno > 0 ? lineno : undefined
353+
});
214354
}
215355

216-
return JSON.parse(resultStr);
356+
if (!parseTreePtr) {
357+
throw new SqlError('No parse tree generated');
358+
}
359+
360+
const parseTreeStr = wasmModule.UTF8ToString(parseTreePtr);
361+
return JSON.parse(parseTreeStr);
217362
} finally {
218363
wasmModule._free(queryPtr);
219364
if (resultPtr) {
220-
wasmModule._wasm_free_string(resultPtr);
365+
wasmModule._wasm_free_parse_result(resultPtr);
221366
}
222367
}
223368
}

full/src/wasm_wrapper.c

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,25 @@ char* wasm_parse_query(const char* input) {
4646
return parse_tree;
4747
}
4848

49+
EMSCRIPTEN_KEEPALIVE
50+
PgQueryParseResult* wasm_parse_query_raw(const char* input) {
51+
PgQueryParseResult* result = (PgQueryParseResult*)malloc(sizeof(PgQueryParseResult));
52+
if (!result) {
53+
return NULL;
54+
}
55+
56+
*result = pg_query_parse(input);
57+
return result;
58+
}
59+
60+
EMSCRIPTEN_KEEPALIVE
61+
void wasm_free_parse_result(PgQueryParseResult* result) {
62+
if (result) {
63+
pg_query_free_parse_result(*result);
64+
free(result);
65+
}
66+
}
67+
4968
EMSCRIPTEN_KEEPALIVE
5069
char* wasm_deparse_protobuf(const char* protobuf_data, size_t data_len) {
5170
if (!protobuf_data || data_len == 0) {

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
"build:all": "pnpm -r build",
1111
"test:all": "pnpm -r test",
1212
"clean:all": "pnpm -r clean",
13+
"build:versions": "pnpm --filter './versions/*' build",
14+
"test:versions": "pnpm --filter './versions/*' test",
15+
"clean:versions": "pnpm --filter './versions/*' clean",
1316
"analyze:sizes": "node scripts/analyze-sizes.js",
1417
"fetch:protos": "node scripts/fetch-protos.js",
1518
"build:types": "node scripts/build-types.js",

versions/13/Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@ ifdef EMSCRIPTEN
6060
-I$(LIBPG_QUERY_DIR) \
6161
-I$(LIBPG_QUERY_DIR)/vendor \
6262
-L$(LIBPG_QUERY_DIR) \
63-
-sEXPORTED_FUNCTIONS="['_malloc','_free','_wasm_parse_query','_wasm_free_string']" \
64-
-sEXPORTED_RUNTIME_METHODS="['lengthBytesUTF8','stringToUTF8','UTF8ToString','HEAPU8','HEAPU32']" \
63+
-sEXPORTED_FUNCTIONS="['_malloc','_free','_wasm_parse_query','_wasm_free_string','_wasm_parse_query_raw','_wasm_free_parse_result']" \
64+
-sEXPORTED_RUNTIME_METHODS="['lengthBytesUTF8','stringToUTF8','UTF8ToString','getValue','HEAPU8','HEAPU32']" \
6565
-sEXPORT_NAME="$(WASM_MODULE_NAME)" \
6666
-sENVIRONMENT="web,node" \
6767
-sMODULARIZE=1 \

0 commit comments

Comments
 (0)