@@ -215,14 +215,92 @@ export abstract class BasicSQL<
215215
216216 /**
217217 * Validate input string and return syntax errors if exists.
218+ * When input contains multiple statements separated by semicolons
219+ * and the initial parse doesn't capture all errors, each statement
220+ * is validated independently to collect all errors.
218221 * @param input source string
219222 * @returns syntax errors
220223 */
221224 public validate ( input : string ) : ParseError [ ] {
222225 this . parseWithCache ( input ) ;
226+
227+ if ( this . _parseErrors . length > 0 ) {
228+ const statements = this . splitStatements ( input ) ;
229+ if ( statements . length > 1 ) {
230+ const splitErrors = this . validateStatements ( statements ) ;
231+ if ( splitErrors . length > this . _parseErrors . length ) {
232+ this . _parseErrors = splitErrors ;
233+ }
234+ }
235+ }
236+
223237 return this . _parseErrors ;
224238 }
225239
240+ /**
241+ * Validate each statement fragment independently and collect all errors.
242+ */
243+ private validateStatements ( statements : { text : string ; start : number } [ ] ) : ParseError [ ] {
244+ const allErrors : ParseError [ ] = [ ] ;
245+ let lineOffset = 0 ;
246+ for ( const statement of statements ) {
247+ if ( ! statement . text . trim ( ) ) {
248+ const newlines = ( statement . text . match ( / \n / g) || [ ] ) . length ;
249+ lineOffset += newlines ;
250+ continue ;
251+ }
252+ const parser = this . createParser ( statement . text ) ;
253+ const errors : ParseError [ ] = [ ] ;
254+ parser . removeErrorListeners ( ) ;
255+ parser . addErrorListener (
256+ this . createErrorListener ( ( error ) => {
257+ errors . push ( {
258+ startLine : error . startLine + lineOffset ,
259+ endLine : error . endLine + lineOffset ,
260+ startColumn : error . startColumn ,
261+ endColumn : error . endColumn ,
262+ message : error . message ,
263+ } ) ;
264+ } )
265+ ) ;
266+ parser . errorHandler = new ErrorStrategy ( ) ;
267+ parser . program ( ) ;
268+ allErrors . push ( ...errors ) ;
269+ const newlines = ( statement . text . match ( / \n / g) || [ ] ) . length ;
270+ lineOffset += newlines ;
271+ }
272+ return allErrors ;
273+ }
274+
275+ /**
276+ * Split input into individual statement strings by semicolons.
277+ * Handles semicolons inside quoted strings correctly.
278+ */
279+ private splitStatements ( input : string ) : { text : string ; start : number } [ ] {
280+ const statements : { text : string ; start : number } [ ] = [ ] ;
281+ let inSingleQuote = false ;
282+ let inDoubleQuote = false ;
283+ let lastSplit = 0 ;
284+
285+ for ( let i = 0 ; i < input . length ; i ++ ) {
286+ const ch = input [ i ] ;
287+ if ( ch === "'" && ! inDoubleQuote ) {
288+ inSingleQuote = ! inSingleQuote ;
289+ } else if ( ch === '"' && ! inSingleQuote ) {
290+ inDoubleQuote = ! inDoubleQuote ;
291+ } else if ( ch === ';' && ! inSingleQuote && ! inDoubleQuote ) {
292+ statements . push ( { text : input . slice ( lastSplit , i + 1 ) , start : lastSplit } ) ;
293+ lastSplit = i + 1 ;
294+ }
295+ }
296+
297+ if ( lastSplit < input . length ) {
298+ statements . push ( { text : input . slice ( lastSplit ) , start : lastSplit } ) ;
299+ }
300+
301+ return statements ;
302+ }
303+
226304 /**
227305 * Get the input string that has been parsed.
228306 */
0 commit comments