From 19ce7b18d7b559473ec370c57020d7bc9f115c9d Mon Sep 17 00:00:00 2001 From: Viacheslav Poturaev Date: Thu, 7 Aug 2025 19:04:21 +0200 Subject: [PATCH 1/2] Add SplitStatements --- statement.go | 134 ++++++++++++++++++++++++++++++++++++++++++++++ statement_test.go | 36 +++++++++++++ 2 files changed, 170 insertions(+) create mode 100644 statement.go create mode 100644 statement_test.go diff --git a/statement.go b/statement.go new file mode 100644 index 0000000..3181d1f --- /dev/null +++ b/statement.go @@ -0,0 +1,134 @@ +package sqluct + +import "strings" + +// SplitStatements splits a string in multiple SQL statements separated by semicolon (';'). +// +// Semicolons in comments and string literals are not treated as separators. +func SplitStatements(s string) []string { + var ( + quoteChars = [...]int32{'"', '\'', '`'} + quoteOpened int32 + ) + + prevStart := 0 + prevQuot := false + + // -- Line comment. + lineCommentStarted := false + prevDash := false + + // /* Block comment */ + blockCommentStarted := false + prevSlash := false + prevAsterisk := false + + var res []string + + for i, c := range s { + if blockCommentStarted && c != '*' && c != '/' { + continue + } + + if lineCommentStarted { + if c == '\n' { + lineCommentStarted = false + + continue + } + + continue + } + + if quoteOpened != 0 { + // This may be a closing quote or an escaped quot if it is immediately followed by another same quot. + if c == quoteOpened { + if prevQuot { + prevQuot = false + + continue + } + + prevQuot = true + + continue + } + + if prevQuot { + prevQuot = false + quoteOpened = 0 + } + } + + // quoteOpened is 0 + for _, q := range quoteChars { + if c == q { + quoteOpened = q + } + } + + if quoteOpened != 0 { + continue + } + + // Might be a line comment. + if c == '-' { + if prevDash { + prevDash = false + lineCommentStarted = true + + continue + } + + prevDash = true + } else { + prevDash = false + } + + if c == '/' { + if prevAsterisk && blockCommentStarted { + blockCommentStarted = false + prevAsterisk = false + + continue + } + + prevSlash = true + + continue + } + + if c == '*' { + if prevSlash && !blockCommentStarted { + blockCommentStarted = true + prevSlash = false + + continue + } + + prevAsterisk = true + + continue + } + + prevSlash = false + prevAsterisk = false + + // Not in an enquoted string, so that's a statement separator. + if c == ';' { + st := strings.TrimSpace(s[prevStart:i]) + if len(st) > 0 { + res = append(res, st) + } + + prevStart = i + 1 + } + } + + st := strings.TrimSpace(s[prevStart:]) + if len(st) > 0 { + res = append(res, st) + } + + return res +} diff --git a/statement_test.go b/statement_test.go new file mode 100644 index 0000000..d9fcd31 --- /dev/null +++ b/statement_test.go @@ -0,0 +1,36 @@ +package sqluct_test + +import ( + "github.com/bool64/sqluct" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSplitStatements(t *testing.T) { + s := `SELECT "ol'o""lo","",''; + -- next sta;tement + SELECT * FROM refers order by ts desc limit 15; + -- next statement + SELECT * FROM visitor WHERE hash=8361038239347337526; +SELECT /* 1;2;3 */ 'aaa' +` + + res := sqluct.SplitStatements(s) + assert.Equal(t, []string{ + `SELECT "ol'o""lo","",''`, + "-- next sta;tement\n\tSELECT * FROM refers order by ts desc limit 15", + "-- next statement\n\tSELECT * FROM visitor WHERE hash=8361038239347337526", + "SELECT /* 1;2;3 */ 'aaa'", + }, res) +} + +func TestSplitStatements2(t *testing.T) { + s := `";";';'` + res := sqluct.SplitStatements(s) + + assert.Equal(t, []string{ + `";"`, + `';'`, + }, res) +} From 7d52ec73bf8458d460759e08747d28f3868bc333 Mon Sep 17 00:00:00 2001 From: Viacheslav Poturaev Date: Thu, 7 Aug 2025 19:06:41 +0200 Subject: [PATCH 2/2] Fix lint --- statement.go | 2 +- statement_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/statement.go b/statement.go index 3181d1f..3387134 100644 --- a/statement.go +++ b/statement.go @@ -5,7 +5,7 @@ import "strings" // SplitStatements splits a string in multiple SQL statements separated by semicolon (';'). // // Semicolons in comments and string literals are not treated as separators. -func SplitStatements(s string) []string { +func SplitStatements(s string) []string { //nolint:gocognit,gocyclo,funlen var ( quoteChars = [...]int32{'"', '\'', '`'} quoteOpened int32 diff --git a/statement_test.go b/statement_test.go index d9fcd31..dc1bf9c 100644 --- a/statement_test.go +++ b/statement_test.go @@ -1,9 +1,9 @@ package sqluct_test import ( - "github.com/bool64/sqluct" "testing" + "github.com/bool64/sqluct" "github.com/stretchr/testify/assert" )