@@ -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
2085import 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
87155export 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}
0 commit comments