-
-
Notifications
You must be signed in to change notification settings - Fork 184
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
🐛 fix logic of parsing multiple columns (i.e. for PRIMARY KEYS, CONST…
…RAINT) (#193) * ✨ impl parseAllColumns * 🐛 replacing getAllColumns with parseAllColumns (cherry picked from commit 0c7e33b) * 🐛 (parseAllColumns)fix for []quoted cases
- Loading branch information
Showing
3 changed files
with
177 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
package sqlite | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
) | ||
|
||
type parseAllColumnsState int | ||
|
||
const ( | ||
parseAllColumnsState_NONE parseAllColumnsState = iota | ||
parseAllColumnsState_Beginning | ||
parseAllColumnsState_ReadingRawName | ||
parseAllColumnsState_ReadingQuotedName | ||
parseAllColumnsState_EndOfName | ||
parseAllColumnsState_State_End | ||
) | ||
|
||
func parseAllColumns(in string) ([]string, error) { | ||
s := []rune(in) | ||
columns := make([]string, 0) | ||
state := parseAllColumnsState_NONE | ||
quote := rune(0) | ||
name := make([]rune, 0) | ||
for i := 0; i < len(s); i++ { | ||
switch state { | ||
case parseAllColumnsState_NONE: | ||
if s[i] == '(' { | ||
state = parseAllColumnsState_Beginning | ||
} | ||
case parseAllColumnsState_Beginning: | ||
if isSpace(s[i]) { | ||
continue | ||
} | ||
if isQuote(s[i]) { | ||
state = parseAllColumnsState_ReadingQuotedName | ||
quote = s[i] | ||
continue | ||
} | ||
if s[i] == '[' { | ||
state = parseAllColumnsState_ReadingQuotedName | ||
quote = ']' | ||
continue | ||
} else if s[i] == ')' { | ||
return columns, fmt.Errorf("unexpected token: %s", string(s[i])) | ||
} | ||
state = parseAllColumnsState_ReadingRawName | ||
name = append(name, s[i]) | ||
case parseAllColumnsState_ReadingRawName: | ||
if isSeparator(s[i]) { | ||
state = parseAllColumnsState_Beginning | ||
columns = append(columns, string(name)) | ||
name = make([]rune, 0) | ||
continue | ||
} | ||
if s[i] == ')' { | ||
state = parseAllColumnsState_State_End | ||
columns = append(columns, string(name)) | ||
} | ||
if isQuote(s[i]) { | ||
return nil, fmt.Errorf("unexpected token: %s", string(s[i])) | ||
} | ||
if isSpace(s[i]) { | ||
state = parseAllColumnsState_EndOfName | ||
columns = append(columns, string(name)) | ||
name = make([]rune, 0) | ||
continue | ||
} | ||
name = append(name, s[i]) | ||
case parseAllColumnsState_ReadingQuotedName: | ||
if s[i] == quote { | ||
// check if quote character is escaped | ||
if i+1 < len(s) && s[i+1] == quote { | ||
name = append(name, quote) | ||
i++ | ||
continue | ||
} | ||
state = parseAllColumnsState_EndOfName | ||
columns = append(columns, string(name)) | ||
name = make([]rune, 0) | ||
continue | ||
} | ||
name = append(name, s[i]) | ||
case parseAllColumnsState_EndOfName: | ||
if isSpace(s[i]) { | ||
continue | ||
} | ||
if isSeparator(s[i]) { | ||
state = parseAllColumnsState_Beginning | ||
continue | ||
} | ||
if s[i] == ')' { | ||
state = parseAllColumnsState_State_End | ||
continue | ||
} | ||
return nil, fmt.Errorf("unexpected token: %s", string(s[i])) | ||
case parseAllColumnsState_State_End: | ||
break | ||
} | ||
} | ||
if state != parseAllColumnsState_State_End { | ||
return nil, errors.New("unexpected end") | ||
} | ||
return columns, nil | ||
} | ||
|
||
func isSpace(r rune) bool { | ||
return r == ' ' || r == '\t' | ||
} | ||
|
||
func isQuote(r rune) bool { | ||
return r == '`' || r == '"' || r == '\'' | ||
} | ||
|
||
func isSeparator(r rune) bool { | ||
return r == ',' | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package sqlite | ||
|
||
import "testing" | ||
|
||
func TestParseAllColumns(t *testing.T) { | ||
tc := []struct { | ||
name string | ||
input string | ||
expected []string | ||
}{ | ||
{ | ||
name: "Simple case", | ||
input: "PRIMARY KEY (column1, column2)", | ||
expected: []string{"column1", "column2"}, | ||
}, | ||
{ | ||
name: "Quoted column name", | ||
input: "PRIMARY KEY (`column,xxx`, \"column 2\", \"column)3\", 'column''4', \"column\"\"5\")", | ||
expected: []string{"column,xxx", "column 2", "column)3", "column'4", "column\"5"}, | ||
}, | ||
{ | ||
name: "Japanese column name", | ||
input: "PRIMARY KEY (カラム1, `カラム2`)", | ||
expected: []string{"カラム1", "カラム2"}, | ||
}, | ||
{ | ||
name: "Column name quoted with []", | ||
input: "PRIMARY KEY ([column1], [column2])", | ||
expected: []string{"column1", "column2"}, | ||
}, | ||
} | ||
for _, tt := range tc { | ||
t.Run(tt.name, func(t *testing.T) { | ||
cols, err := parseAllColumns(tt.input) | ||
if err != nil { | ||
t.Errorf("Failed to parse columns: %s", err) | ||
} | ||
if len(cols) != len(tt.expected) { | ||
t.Errorf("Expected %d columns, got %d", len(tt.expected), len(cols)) | ||
} | ||
for i, col := range cols { | ||
if col != tt.expected[i] { | ||
t.Errorf("Expected %s, got %s", tt.expected[i], col) | ||
} | ||
} | ||
}) | ||
} | ||
} |