splitter.go 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. package sqlutil
  2. import (
  3. "strings"
  4. )
  5. // SplitStatements splits a query string into individual SQL statements.
  6. // This robust implementation handles SQL comments, quoted strings, and escaped characters.
  7. //
  8. // Features:
  9. // - Handles single-line comments (-- comment)
  10. // - Handles multi-line comments (/* comment */)
  11. // - Properly escapes single quotes in strings ('don”t')
  12. // - Properly escapes double quotes in identifiers ("column""name")
  13. // - Ignores semicolons within quoted strings and comments
  14. // - Returns clean, trimmed statements with empty statements filtered out
  15. func SplitStatements(query string) []string {
  16. var statements []string
  17. var current strings.Builder
  18. query = strings.TrimSpace(query)
  19. if query == "" {
  20. return []string{}
  21. }
  22. runes := []rune(query)
  23. i := 0
  24. for i < len(runes) {
  25. char := runes[i]
  26. // Handle single-line comments (-- comment)
  27. if char == '-' && i+1 < len(runes) && runes[i+1] == '-' {
  28. // Skip the entire comment without including it in any statement
  29. for i < len(runes) && runes[i] != '\n' && runes[i] != '\r' {
  30. i++
  31. }
  32. // Skip the newline if present
  33. if i < len(runes) {
  34. i++
  35. }
  36. continue
  37. }
  38. // Handle multi-line comments (/* comment */)
  39. if char == '/' && i+1 < len(runes) && runes[i+1] == '*' {
  40. // Skip the /* opening
  41. i++
  42. i++
  43. // Skip to end of comment or end of input without including content
  44. for i < len(runes) {
  45. if runes[i] == '*' && i+1 < len(runes) && runes[i+1] == '/' {
  46. i++ // Skip the *
  47. i++ // Skip the /
  48. break
  49. }
  50. i++
  51. }
  52. continue
  53. }
  54. // Handle single-quoted strings
  55. if char == '\'' {
  56. current.WriteRune(char)
  57. i++
  58. for i < len(runes) {
  59. char = runes[i]
  60. current.WriteRune(char)
  61. if char == '\'' {
  62. // Check if it's an escaped quote
  63. if i+1 < len(runes) && runes[i+1] == '\'' {
  64. i++ // Skip the next quote (it's escaped)
  65. if i < len(runes) {
  66. current.WriteRune(runes[i])
  67. }
  68. } else {
  69. break // End of string
  70. }
  71. }
  72. i++
  73. }
  74. i++
  75. continue
  76. }
  77. // Handle double-quoted identifiers
  78. if char == '"' {
  79. current.WriteRune(char)
  80. i++
  81. for i < len(runes) {
  82. char = runes[i]
  83. current.WriteRune(char)
  84. if char == '"' {
  85. // Check if it's an escaped quote
  86. if i+1 < len(runes) && runes[i+1] == '"' {
  87. i++ // Skip the next quote (it's escaped)
  88. if i < len(runes) {
  89. current.WriteRune(runes[i])
  90. }
  91. } else {
  92. break // End of identifier
  93. }
  94. }
  95. i++
  96. }
  97. i++
  98. continue
  99. }
  100. // Handle semicolon (statement separator)
  101. if char == ';' {
  102. stmt := strings.TrimSpace(current.String())
  103. if stmt != "" {
  104. statements = append(statements, stmt)
  105. }
  106. current.Reset()
  107. } else {
  108. current.WriteRune(char)
  109. }
  110. i++
  111. }
  112. // Add any remaining statement
  113. if current.Len() > 0 {
  114. stmt := strings.TrimSpace(current.String())
  115. if stmt != "" {
  116. statements = append(statements, stmt)
  117. }
  118. }
  119. // If no statements found, return the original query as a single statement
  120. if len(statements) == 0 {
  121. return []string{strings.TrimSpace(strings.TrimSuffix(strings.TrimSpace(query), ";"))}
  122. }
  123. return statements
  124. }