Skip to content
Open
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
24 changes: 24 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package main

import "strings"

func main() {
query := "select id, name from employees where department_id = 3 and salary > 10"
println(query)
r := strings.NewReader(query)

parser := NewParser(r)

stmt, err := parser.Parse()
if err != nil {
println(err.Error())
}

println(stmt.TableName)
for _, field := range stmt.Fields {
println(field)
}
for _, condition := range stmt.Conditions {
println(condition)
}
}
34 changes: 31 additions & 3 deletions parser.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package sql
package main

import (
"fmt"
Expand All @@ -7,8 +7,9 @@ import (

// SelectStatement represents a SQL SELECT statement.
type SelectStatement struct {
Fields []string
TableName string
Fields []string
TableName string
Conditions []string
}

// Parser represents a parser.
Expand Down Expand Up @@ -63,6 +64,33 @@ func (p *Parser) Parse() (*SelectStatement, error) {
}
stmt.TableName = lit

// Next we should see the "WHERE" keyword.
if tok, lit := p.scanIgnoreWhitespace(); tok != WHERE {
return nil, fmt.Errorf("found %q, expected WHERE", lit)
}

// Next we should loop over all our and delimited conditions.
condition := ""
for {
// Read a field.
_, lit := p.scanIgnoreWhitespace()
condition += lit

if tok, lit := p.scanIgnoreWhitespace(); tok == AND {
stmt.Conditions = append(stmt.Conditions, condition)
condition = ""
} else {
if tok == EOF {
stmt.Conditions = append(stmt.Conditions, condition)
p.unscan()
break
} else {
condition += lit
}

}
}

// Return the successfully parsed statement.
return stmt, nil
}
Expand Down
2 changes: 1 addition & 1 deletion parser_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package sql_test
package main

import (
"reflect"
Expand Down
19 changes: 17 additions & 2 deletions scanner.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package sql
package main

import (
"bufio"
Expand Down Expand Up @@ -35,6 +35,12 @@ func (s *Scanner) Scan() (tok Token, lit string) {

// Otherwise read the individual character.
switch ch {
case '=':
return OPER, string(ch)
case '>':
return OPER, string(ch)
case '<':
return OPER, string(ch)
case eof:
return EOF, ""
case '*':
Expand Down Expand Up @@ -93,6 +99,10 @@ func (s *Scanner) scanIdent() (tok Token, lit string) {
return SELECT, buf.String()
case "FROM":
return FROM, buf.String()
case "WHERE":
return WHERE, buf.String()
case "AND":
return AND, buf.String()
}

// Otherwise return as a regular identifier.
Expand All @@ -116,10 +126,15 @@ func (s *Scanner) unread() { _ = s.r.UnreadRune() }
func isWhitespace(ch rune) bool { return ch == ' ' || ch == '\t' || ch == '\n' }

// isLetter returns true if the rune is a letter.
func isLetter(ch rune) bool { return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') }
func isLetter(ch rune) bool {
return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') //|| isOpers(ch)
}

// isDigit returns true if the rune is a digit.
func isDigit(ch rune) bool { return (ch >= '0' && ch <= '9') }

// isOpers returns true if the rune is a digit.
func isOpers(ch rune) bool { return ch == '=' || ch == '>' || ch == '<' }

// eof represents a marker rune for the end of the reader.
var eof = rune(0)
2 changes: 1 addition & 1 deletion scanner_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package sql_test
package main

import (
"strings"
Expand Down
5 changes: 4 additions & 1 deletion token.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package sql
package main

// Token represents a lexical token.
type Token int
Expand All @@ -19,4 +19,7 @@ const (
// Keywords
SELECT
FROM
WHERE
AND // and
OPER
)