Skip to content

Commit

Permalink
Merge pull request #2 from monstermichl/main
Browse files Browse the repository at this point in the history
Add support for placeholders
  • Loading branch information
krasun authored Jan 15, 2025
2 parents dfda5d4 + a49c86e commit 5ccde5d
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 3 deletions.
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@ DELETE:
DELETE FROM table1 WHERE c1 == 5 AND c3 == "quoted string"
```

## Placeholders
It is possible to use placeholders by including identifiers between curly brackets.
```
SELECT c1, c2 FROM table1 WHERE c3 == {0} AND c4 == {p}
```

## Tests

To make sure that the code is fully tested and covered:
Expand All @@ -99,4 +105,4 @@ ok github.com/krasun/gosqlparser 0.470s

## License

**gosqlparser** is released under [the MIT license](LICENSE).
**gosqlparser** is released under [the MIT license](LICENSE).
20 changes: 20 additions & 0 deletions lexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const (
tokenDelimeter // ','
tokenLeftParenthesis // '('
tokenRightParenthesis // ')'
tokenPlaceholder // '?'
tokenInteger // integer
tokenString // string including quotes
tokenAnd // AND
Expand Down Expand Up @@ -70,6 +71,7 @@ var tokenToString = map[tokenType]string{
tokenDelimeter: "delimeter",
tokenLeftParenthesis: "leftParenthesis",
tokenRightParenthesis: "rightParenthesis",
tokenPlaceholder: "placeholder",
tokenInteger: "integer",
tokenString: "string",
tokenAnd: "AND",
Expand Down Expand Up @@ -227,6 +229,9 @@ func lexStatement(l *lexer) lexFunc {
case r == ')':
l.produce(tokenRightParenthesis)
return lexStatement
case r == '{':
l.revert()
return lexPlaceholder
case r == '"':
return lexString
case r == ',':
Expand Down Expand Up @@ -281,6 +286,21 @@ func lexString(l *lexer) lexFunc {
return lexString
}

func lexPlaceholder(l *lexer) lexFunc {
r := l.next()

switch r {
case '}':
l.produce(tokenPlaceholder)

return lexStatement
case end:
return l.errorf("expected }")
}

return lexPlaceholder
}

func lexIdentifier(l *lexer) lexFunc {
r := l.next()

Expand Down
33 changes: 33 additions & 0 deletions lexer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,39 @@ func TestLexer(t *testing.T) {
{tokenEnd, ""},
},
},
{
"full SELECT query with placeholders",
"SELECT c1, c2 FROM table1 WHERE c1 == {0} AND c2 == {placeholder}",
[]token{
{tokenSelect, "SELECT"},
{tokenSpace, " "},
{tokenIdentifier, "c1"},
{tokenDelimeter, ","},
{tokenSpace, " "},
{tokenIdentifier, "c2"},
{tokenSpace, " "},
{tokenFrom, "FROM"},
{tokenSpace, " "},
{tokenIdentifier, "table1"},
{tokenSpace, " "},
{tokenWhere, "WHERE"},
{tokenSpace, " "},
{tokenIdentifier, "c1"},
{tokenSpace, " "},
{tokenEquals, "=="},
{tokenSpace, " "},
{tokenPlaceholder, "{0}"},
{tokenSpace, " "},
{tokenAnd, "AND"},
{tokenSpace, " "},
{tokenIdentifier, "c2"},
{tokenSpace, " "},
{tokenEquals, "=="},
{tokenSpace, " "},
{tokenPlaceholder, "{placeholder}"},
{tokenEnd, ""},
},
},
{
"integer",
"123456789",
Expand Down
10 changes: 9 additions & 1 deletion parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ type Expr interface {
func (ExprIdentifier) i() {}
func (ExprValueInteger) i() {}
func (ExprValueString) i() {}
func (ExprValuePlaceholder) i() {}
func (ExprOperation) i() {}

// ExprIdentifier holds the name of the identifier.
Expand All @@ -189,6 +190,11 @@ type ExprValueString struct {
Value string
}

// ExprValuePlaceholder holds the placeholder.
type ExprValuePlaceholder struct {
Value string
}

// ExprOperation represents operation with == or AND operators.
type ExprOperation struct {
Left Expr
Expand Down Expand Up @@ -636,7 +642,7 @@ func parseExprOperation(p *parser, terminalTokenTypes ...tokenType) (Expr, *toke
operator = OperatorEquals
}

t, err = p.scanFor(tokenIdentifier, tokenInteger, tokenString)
t, err = p.scanFor(tokenIdentifier, tokenInteger, tokenString, tokenPlaceholder)
if err != nil {
return nil, nil, err
}
Expand All @@ -649,6 +655,8 @@ func parseExprOperation(p *parser, terminalTokenTypes ...tokenType) (Expr, *toke
right = ExprValueInteger{t.value}
case tokenString:
right = ExprValueString{t.value}
case tokenPlaceholder:
right = ExprValuePlaceholder{t.value}
}

var expr = ExprOperation{left, operator, right}
Expand Down
9 changes: 8 additions & 1 deletion parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func TestParser(t *testing.T) {
"unfinished WHERE statement",
"SELECT col FROM table1 WHERE a ==",
nil,
fmt.Errorf("expected identifier, integer, string, but got end: \"\""),
fmt.Errorf("expected identifier, integer, string, placeholder, but got end: \"\""),
},
{
"unfinished WHERE statement",
Expand Down Expand Up @@ -173,6 +173,12 @@ func TestParser(t *testing.T) {
&sql.Select{"table1", []string{"col1", "col2"}, &sql.Where{sql.ExprOperation{sql.ExprIdentifier{"col1"}, sql.OperatorEquals, sql.ExprIdentifier{"col2"}}}, ""},
nil,
},
{
"SELECT FROM with simple WHERE with placeholder",
"SELECT col1 FROM table1 WHERE col1 == {0}",
&sql.Select{"table1", []string{"col1"}, &sql.Where{sql.ExprOperation{sql.ExprIdentifier{"col1"}, sql.OperatorEquals, sql.ExprValuePlaceholder{"{0}"}}}, ""},
nil,
},
{
"SELECT FROM with WHERE AND LIMIT",
"SELECT col1, col2 FROM table1 WHERE col1 == col2 AND col3 == col4 LIMIT 10",
Expand Down Expand Up @@ -220,6 +226,7 @@ func TestParser(t *testing.T) {
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
statement, err := sql.Parse(testCase.input)

if testCase.err != nil {
if err == nil {
t.Errorf("expected error \"%s\", but got nil", testCase.err)
Expand Down

0 comments on commit 5ccde5d

Please sign in to comment.