From fe64c8d52cc02fed586a071100dfa3f3ebc7c942 Mon Sep 17 00:00:00 2001 From: Michel Date: Wed, 15 Jan 2025 12:24:37 +0100 Subject: [PATCH 1/9] Add support for ?-placeholder --- lexer.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lexer.go b/lexer.go index c9ceeda..dc2c7e3 100644 --- a/lexer.go +++ b/lexer.go @@ -37,6 +37,7 @@ const ( tokenDelimeter // ',' tokenLeftParenthesis // '(' tokenRightParenthesis // ')' + tokenPlaceholder // '?' tokenInteger // integer tokenString // string including quotes tokenAnd // AND @@ -70,6 +71,7 @@ var tokenToString = map[tokenType]string{ tokenDelimeter: "delimeter", tokenLeftParenthesis: "leftParenthesis", tokenRightParenthesis: "rightParenthesis", + tokenPlaceholder: "placeholder", tokenInteger: "integer", tokenString: "string", tokenAnd: "AND", @@ -227,6 +229,9 @@ func lexStatement(l *lexer) lexFunc { case r == ')': l.produce(tokenRightParenthesis) return lexStatement + case r == '?': + l.produce(tokenPlaceholder) + return lexStatement case r == '"': return lexString case r == ',': From b7fb9d458f284d3b2cc777a2b6752f2449da05c2 Mon Sep 17 00:00:00 2001 From: Michel Date: Wed, 15 Jan 2025 12:25:02 +0100 Subject: [PATCH 2/9] Add support for ?-placeholder --- parser.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/parser.go b/parser.go index 755d196..3dbbf20 100644 --- a/parser.go +++ b/parser.go @@ -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. @@ -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 @@ -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 } @@ -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} From 6aa058d7f26d78198ac48a3a82505918980f7e0b Mon Sep 17 00:00:00 2001 From: Michel Date: Wed, 15 Jan 2025 12:25:23 +0100 Subject: [PATCH 3/9] Add placeholder tests --- parser_test.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/parser_test.go b/parser_test.go index 042282e..ecc0cd8 100644 --- a/parser_test.go +++ b/parser_test.go @@ -63,7 +63,7 @@ func TestParser(t *testing.T) { "unfinished SELECT FROM WHERE statement", "SELECT col FROM table1 WHERE", nil, - fmt.Errorf("expected identifier, integer, string, but got end: \"\""), + fmt.Errorf("expected identifier, integer, string, placeholder, but got end: \"\""), }, { "unfinished WHERE statement", @@ -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 == ?", + &sql.Select{"table1", []string{"col1"}, &sql.Where{sql.ExprOperation{sql.ExprIdentifier{"col1"}, sql.OperatorEquals, sql.ExprValuePlaceholder{"?"}}}, ""}, + nil, + }, { "SELECT FROM with WHERE AND LIMIT", "SELECT col1, col2 FROM table1 WHERE col1 == col2 AND col3 == col4 LIMIT 10", @@ -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) From 56389ac3b29ba0f3296960b34e9c90243cd31488 Mon Sep 17 00:00:00 2001 From: Michel Date: Wed, 15 Jan 2025 12:31:06 +0100 Subject: [PATCH 4/9] Fix parser tests for placeholder --- parser_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/parser_test.go b/parser_test.go index ecc0cd8..9383900 100644 --- a/parser_test.go +++ b/parser_test.go @@ -63,13 +63,13 @@ func TestParser(t *testing.T) { "unfinished SELECT FROM WHERE statement", "SELECT col FROM table1 WHERE", nil, - fmt.Errorf("expected identifier, integer, string, placeholder, but got end: \"\""), + fmt.Errorf("expected identifier, integer, string, but got end: \"\""), }, { "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", From 1cbfaf4602a37d108b38db59f396b9b75b1663d8 Mon Sep 17 00:00:00 2001 From: Michel Date: Wed, 15 Jan 2025 12:31:31 +0100 Subject: [PATCH 5/9] Add lexer test for placeholders --- lexer_test.go | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/lexer_test.go b/lexer_test.go index 0f22dc5..b9b9c5e 100644 --- a/lexer_test.go +++ b/lexer_test.go @@ -102,6 +102,39 @@ func TestLexer(t *testing.T) { {tokenEnd, ""}, }, }, + { + "full SELECT query with placeholders", + "SELECT c1, c2 FROM table1 WHERE c1 == ? AND c2 == ?", + []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, "?"}, + {tokenSpace, " "}, + {tokenAnd, "AND"}, + {tokenSpace, " "}, + {tokenIdentifier, "c2"}, + {tokenSpace, " "}, + {tokenEquals, "=="}, + {tokenSpace, " "}, + {tokenPlaceholder, "?"}, + {tokenEnd, ""}, + }, + }, { "integer", "123456789", From 4568c55be76936cec7369e3db8fdc140a3a19aa6 Mon Sep 17 00:00:00 2001 From: Michel Date: Wed, 15 Jan 2025 13:17:53 +0100 Subject: [PATCH 6/9] Change placeholder from ? to {placeholder-name} pattern --- lexer.go | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/lexer.go b/lexer.go index dc2c7e3..f393138 100644 --- a/lexer.go +++ b/lexer.go @@ -229,9 +229,9 @@ func lexStatement(l *lexer) lexFunc { case r == ')': l.produce(tokenRightParenthesis) return lexStatement - case r == '?': - l.produce(tokenPlaceholder) - return lexStatement + case r == '{': + l.revert() + return lexPlaceholder case r == '"': return lexString case r == ',': @@ -286,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() From 7a0ca537e4102842c1a9ffd2cb123bf060d46df3 Mon Sep 17 00:00:00 2001 From: Michel Date: Wed, 15 Jan 2025 13:18:41 +0100 Subject: [PATCH 7/9] Update lexer tests --- lexer_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lexer_test.go b/lexer_test.go index b9b9c5e..1aeffcb 100644 --- a/lexer_test.go +++ b/lexer_test.go @@ -104,7 +104,7 @@ func TestLexer(t *testing.T) { }, { "full SELECT query with placeholders", - "SELECT c1, c2 FROM table1 WHERE c1 == ? AND c2 == ?", + "SELECT c1, c2 FROM table1 WHERE c1 == {0} AND c2 == {placeholder}", []token{ {tokenSelect, "SELECT"}, {tokenSpace, " "}, @@ -123,7 +123,7 @@ func TestLexer(t *testing.T) { {tokenSpace, " "}, {tokenEquals, "=="}, {tokenSpace, " "}, - {tokenPlaceholder, "?"}, + {tokenPlaceholder, "{0}"}, {tokenSpace, " "}, {tokenAnd, "AND"}, {tokenSpace, " "}, @@ -131,7 +131,7 @@ func TestLexer(t *testing.T) { {tokenSpace, " "}, {tokenEquals, "=="}, {tokenSpace, " "}, - {tokenPlaceholder, "?"}, + {tokenPlaceholder, "{placeholder}"}, {tokenEnd, ""}, }, }, From 9c2ccf4899123ca26144e122287eddd5e5ac5ca8 Mon Sep 17 00:00:00 2001 From: Michel Date: Wed, 15 Jan 2025 13:18:54 +0100 Subject: [PATCH 8/9] Update parser tests --- parser_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/parser_test.go b/parser_test.go index 9383900..e35dfbf 100644 --- a/parser_test.go +++ b/parser_test.go @@ -175,8 +175,8 @@ func TestParser(t *testing.T) { }, { "SELECT FROM with simple WHERE with placeholder", - "SELECT col1 FROM table1 WHERE col1 == ?", - &sql.Select{"table1", []string{"col1"}, &sql.Where{sql.ExprOperation{sql.ExprIdentifier{"col1"}, sql.OperatorEquals, sql.ExprValuePlaceholder{"?"}}}, ""}, + "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, }, { From a49c86eb4d13b668165468dd0928d1ecb65ffc26 Mon Sep 17 00:00:00 2001 From: Michel Date: Wed, 15 Jan 2025 13:24:26 +0100 Subject: [PATCH 9/9] Update README.md --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f9436c9..083a5da 100644 --- a/README.md +++ b/README.md @@ -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: @@ -99,4 +105,4 @@ ok github.com/krasun/gosqlparser 0.470s ## License -**gosqlparser** is released under [the MIT license](LICENSE). \ No newline at end of file +**gosqlparser** is released under [the MIT license](LICENSE).