Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: (close #93) boolean parsing in INSERT query #94

Merged
merged 2 commits into from
Oct 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 26 additions & 4 deletions driver/driver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1473,12 +1473,12 @@ func TestInsertSingle(t *testing.T) {
}
defer db.Close()

_, err = db.Exec("CREATE TABLE cat (id INT AUTOINCREMENT, breed TEXT, name TEXT)")
_, err = db.Exec("CREATE TABLE cat (id INT AUTOINCREMENT, breed TEXT, name TEXT, funny BOOLEAN)")
if err != nil {
t.Fatalf("sql.Exec: Error: %s\n", err)
}

result, err := db.Exec("INSERT INTO cat (breed, name) VALUES ('indeterminate', 'Uhura')")
result, err := db.Exec("INSERT INTO cat (breed, name, funny) VALUES ('indeterminate', 'Uhura', false)")
if err != nil {
t.Fatalf("Cannot insert into table account: %s", err)
}
Expand All @@ -1496,21 +1496,43 @@ func TestInsertSingle(t *testing.T) {
t.Fatalf("Cannot check last inserted ID: %s", err)
}

row := db.QueryRow("SELECT breed, name FROM cat WHERE id = ?", insertedId)
row := db.QueryRow("SELECT breed, name, funny FROM cat WHERE id = ?", insertedId)
if row == nil {
t.Fatalf("sql.Query failed")
}

var breed string
var name string
err = row.Scan(&breed, &name)
var funny bool
funny = true

err = row.Scan(&breed, &name, &funny)
if err != nil {
t.Fatalf("row.Scan: %s", err)
}

if breed != "indeterminate" || name != "Uhura" {
t.Fatalf("Expected breed 'indeterminate' and name 'Uhura', got breed '%v' and name '%v'", breed, name)
}

if funny != false {
t.Fatalf("Expected funny=false, got true")
}

result, err = db.Exec("INSERT INTO cat (breed, name, funny) VALUES ('indeterminate', 'Kuhura', true)")
if err != nil {
t.Fatalf("Cannot insert into table account: %s", err)
}

err = db.QueryRow("SELECT breed, name, funny FROM cat WHERE name = ?", "Kuhura").Scan(&breed, &name, &funny)
if err != nil {
t.Fatalf("row.Scan: %s", err)
}

if funny != true {
t.Fatalf("Expected funny=true, got false")
}

}

func TestInsertSingleReturning(t *testing.T) {
Expand Down
156 changes: 156 additions & 0 deletions engine/parser/insert.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package parser

// Parses an INSERT statement.
//
// The generated AST is as follows:
//
// |-> "INSERT" (InsertToken)
// |-> "INTO" (IntoToken)
// |-> table name
// |-> column name
// |-> (...)
// |-> "VALUES" (ValuesToken)
// |-> "(" (BracketOpeningToken)
// |-> value
// |-> (...)
// |-> (...)
// |-> "RETURNING" (ReturningToken) (optional)
// |-> column name
func (p *parser) parseInsert() (*Instruction, error) {
i := &Instruction{}

// Set INSERT decl
insertDecl, err := p.consumeToken(InsertToken)
if err != nil {
return nil, err
}
i.Decls = append(i.Decls, insertDecl)

// should be INTO
intoDecl, err := p.consumeToken(IntoToken)
if err != nil {
return nil, err
}
insertDecl.Add(intoDecl)

// should be table Name
tableDecl, err := p.parseTableName()
if err != nil {
return nil, err
}
intoDecl.Add(tableDecl)

_, err = p.consumeToken(BracketOpeningToken)
if err != nil {
return nil, err
}

// concerned attribute
for {
decl, err := p.parseListElement()
if err != nil {
return nil, err
}
tableDecl.Add(decl)

if p.is(BracketClosingToken) {
if _, err = p.consumeToken(BracketClosingToken); err != nil {
return nil, err
}

break
}

_, err = p.consumeToken(CommaToken)
if err != nil {
return nil, err
}
}

// should be VALUES
valuesDecl, err := p.consumeToken(ValuesToken)
if err != nil {
return nil, err
}
insertDecl.Add(valuesDecl)

for {
openingBracketDecl, err := p.consumeToken(BracketOpeningToken)
if err != nil {
return nil, err
}
valuesDecl.Add(openingBracketDecl)

// should be a list of values for specified attributes
for {
decl, err := p.parseListElement()
if err != nil {
return nil, err
}
openingBracketDecl.Add(decl)

if p.is(BracketClosingToken) {
p.consumeToken(BracketClosingToken)
break
}

_, err = p.consumeToken(CommaToken)
if err != nil {
return nil, err
}
}

if p.is(CommaToken) {
p.consumeToken(CommaToken)
continue
}

break
}

// we may have `returning "something"` here
if retDecl, err := p.consumeToken(ReturningToken); err == nil {
insertDecl.Add(retDecl)

// returned attribute
attrDecl, err := p.parseAttribute()
if err != nil {
return nil, err
}
retDecl.Add(attrDecl)
}

return i, nil
}

func (p *parser) parseListElement() (*Decl, error) {
quoted := false

// In case of INSERT, can be DEFAULT here
if p.is(DefaultToken) {
v, err := p.consumeToken(DefaultToken)
if err != nil {
return nil, err
}
return v, nil
}

if p.is(SimpleQuoteToken) || p.is(DoubleQuoteToken) {
quoted = true
p.next()
}

var valueDecl *Decl
valueDecl, err := p.consumeToken(FloatToken, StringToken, NumberToken, NullToken, DateToken, NowToken, ArgToken, NamedArgToken, FalseToken)
if err != nil {
return nil, err
}

if quoted {
if _, err := p.consumeToken(SimpleQuoteToken, DoubleQuoteToken); err != nil {
return nil, err
}
}

return valueDecl, nil
}
2 changes: 1 addition & 1 deletion engine/parser/lexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ func (l *lexer) lex(instruction []byte) ([]Token, error) {
matchers = append(matchers, l.genericStringMatcher("for", ForToken))
matchers = append(matchers, l.genericStringMatcher("default", DefaultToken))
matchers = append(matchers, l.genericStringMatcher("localtimestamp", LocalTimestampToken))
matchers = append(matchers, l.genericStringMatcher("false", LocalTimestampToken))
matchers = append(matchers, l.genericStringMatcher("false", FalseToken))
matchers = append(matchers, l.genericStringMatcher("unique", UniqueToken))
matchers = append(matchers, l.genericStringMatcher("now()", NowToken))
matchers = append(matchers, l.genericStringMatcher("offset", OffsetToken))
Expand Down
155 changes: 0 additions & 155 deletions engine/parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,129 +219,6 @@ func (p *parser) parseUpdate() (*Instruction, error) {
return i, nil
}

// Parses an INSERT statement.
//
// The generated AST is as follows:
//
// |-> "INSERT" (InsertToken)
// |-> "INTO" (IntoToken)
// |-> table name
// |-> column name
// |-> (...)
// |-> "VALUES" (ValuesToken)
// |-> "(" (BracketOpeningToken)
// |-> value
// |-> (...)
// |-> (...)
// |-> "RETURNING" (ReturningToken) (optional)
// |-> column name
func (p *parser) parseInsert() (*Instruction, error) {
i := &Instruction{}

// Set INSERT decl
insertDecl, err := p.consumeToken(InsertToken)
if err != nil {
return nil, err
}
i.Decls = append(i.Decls, insertDecl)

// should be INTO
intoDecl, err := p.consumeToken(IntoToken)
if err != nil {
return nil, err
}
insertDecl.Add(intoDecl)

// should be table Name
tableDecl, err := p.parseTableName()
if err != nil {
return nil, err
}
intoDecl.Add(tableDecl)

_, err = p.consumeToken(BracketOpeningToken)
if err != nil {
return nil, err
}

// concerned attribute
for {
decl, err := p.parseListElement()
if err != nil {
return nil, err
}
tableDecl.Add(decl)

if p.is(BracketClosingToken) {
if _, err = p.consumeToken(BracketClosingToken); err != nil {
return nil, err
}

break
}

_, err = p.consumeToken(CommaToken)
if err != nil {
return nil, err
}
}

// should be VALUES
valuesDecl, err := p.consumeToken(ValuesToken)
if err != nil {
return nil, err
}
insertDecl.Add(valuesDecl)

for {
openingBracketDecl, err := p.consumeToken(BracketOpeningToken)
if err != nil {
return nil, err
}
valuesDecl.Add(openingBracketDecl)

// should be a list of values for specified attributes
for {
decl, err := p.parseListElement()
if err != nil {
return nil, err
}
openingBracketDecl.Add(decl)

if p.is(BracketClosingToken) {
p.consumeToken(BracketClosingToken)
break
}

_, err = p.consumeToken(CommaToken)
if err != nil {
return nil, err
}
}

if p.is(CommaToken) {
p.consumeToken(CommaToken)
continue
}

break
}

// we may have `returning "something"` here
if retDecl, err := p.consumeToken(ReturningToken); err == nil {
insertDecl.Add(retDecl)

// returned attribute
attrDecl, err := p.parseAttribute()
if err != nil {
return nil, err
}
retDecl.Add(attrDecl)
}

return i, nil
}

func (p *parser) parseType() (*Decl, error) {
typeDecl, err := p.consumeToken(FloatToken, DateToken, DecimalToken, NumberToken, StringToken)
if err != nil {
Expand Down Expand Up @@ -812,38 +689,6 @@ func (p *parser) parseJoin() (*Decl, error) {
return joinDecl, nil
}

func (p *parser) parseListElement() (*Decl, error) {
quoted := false

// In case of INSERT, can be DEFAULT here
if p.is(DefaultToken) {
v, err := p.consumeToken(DefaultToken)
if err != nil {
return nil, err
}
return v, nil
}

if p.is(SimpleQuoteToken) || p.is(DoubleQuoteToken) {
quoted = true
p.next()
}

var valueDecl *Decl
valueDecl, err := p.consumeToken(FloatToken, StringToken, NumberToken, NullToken, DateToken, NowToken, ArgToken, NamedArgToken)
if err != nil {
return nil, err
}

if quoted {
if _, err := p.consumeToken(SimpleQuoteToken, DoubleQuoteToken); err != nil {
return nil, err
}
}

return valueDecl, nil
}

func (p *parser) next() error {
if !p.hasNext() {
return fmt.Errorf("Unexpected end")
Expand Down