Skip to content

Commit 647aeea

Browse files
fix: handle SQL comments in EXIT paren balancing
1 parent 87d3d3b commit 647aeea

2 files changed

Lines changed: 50 additions & 0 deletions

File tree

pkg/sqlcmd/commands.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,12 +211,35 @@ func (c Commands) SetBatchTerminator(terminator string) error {
211211
// isExitParenBalanced checks if the parentheses in an EXIT command argument are balanced.
212212
// It tracks quotes to avoid counting parens inside string literals.
213213
// It handles SQL Server's quote escaping: ” inside single-quoted strings, "" inside double-quoted strings, and ]] inside bracket identifiers.
214+
// It also ignores parentheses inside SQL comments (-- single-line and /* multi-line */).
214215
func isExitParenBalanced(s string) bool {
215216
depth := 0
216217
var quote rune
218+
inLineComment := false
219+
inBlockComment := false
217220
runes := []rune(s)
218221
for i := 0; i < len(runes); i++ {
219222
c := runes[i]
223+
224+
// Handle line comment state
225+
if inLineComment {
226+
// Line comment ends at newline
227+
if c == '\n' {
228+
inLineComment = false
229+
}
230+
continue
231+
}
232+
233+
// Handle block comment state
234+
if inBlockComment {
235+
// Check for end of block comment
236+
if c == '*' && i+1 < len(runes) && runes[i+1] == '/' {
237+
inBlockComment = false
238+
i++ // skip the '/'
239+
}
240+
continue
241+
}
242+
220243
switch {
221244
case quote != 0:
222245
// Inside a quoted string
@@ -228,6 +251,14 @@ func isExitParenBalanced(s string) bool {
228251
quote = 0
229252
}
230253
}
254+
case c == '-' && i+1 < len(runes) && runes[i+1] == '-':
255+
// Start of single-line comment
256+
inLineComment = true
257+
i++ // skip the second '-'
258+
case c == '/' && i+1 < len(runes) && runes[i+1] == '*':
259+
// Start of block comment
260+
inBlockComment = true
261+
i++ // skip the '*'
231262
case c == '\'' || c == '"':
232263
quote = c
233264
case c == '[':
@@ -247,6 +278,12 @@ func readExitContinuation(s *Sqlcmd, params string) (string, error) {
247278
var builder strings.Builder
248279
builder.WriteString(params)
249280

281+
// Save original prompt and restore it when done (if batch is initialized)
282+
if s.batch != nil {
283+
originalPrompt := s.Prompt()
284+
defer s.lineIo.SetPrompt(originalPrompt)
285+
}
286+
250287
for !isExitParenBalanced(builder.String()) {
251288
// Show continuation prompt
252289
s.lineIo.SetPrompt(" -> ")

pkg/sqlcmd/commands_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,19 @@ func TestIsExitParenBalanced(t *testing.T) {
484484
{"(select [col]]name])", true}, // escaped bracket identifier
485485
{"(select 'it''s a )test')", true}, // escaped quote with paren
486486
{"(select [a]]])", true}, // escaped bracket with paren
487+
// SQL comment tests
488+
{"(select 1 -- unmatched (\n)", true}, // line comment with paren
489+
{"(select 1 /* ( */ )", true}, // block comment with paren
490+
{"(select /* nested ( */ 1)", true}, // block comment in middle
491+
{"(select 1 -- comment\n+ 2)", true}, // line comment continues to next line
492+
{"(select /* multi\nline\n( */ 1)", true}, // multi-line block comment
493+
{"(select 1 -- ) still need close\n)", true}, // paren in line comment doesn't count
494+
{"(select 1 /* ) */ + /* ( */ 2)", true}, // multiple block comments
495+
{"(select 1 -- (\n-- )\n)", true}, // multiple line comments
496+
{"(select '-- not a comment (' )", true}, // -- inside string is not a comment
497+
{"(select '/* not a comment (' )", true}, // /* inside string is not a comment
498+
{"(select 1 /* unclosed comment", false}, // unclosed block comment, missing )
499+
{"(select 1) -- trailing comment (", true}, // trailing comment after balanced
487500
}
488501
for _, test := range tests {
489502
t.Run(test.input, func(t *testing.T) {

0 commit comments

Comments
 (0)