diff --git a/main.go b/main.go new file mode 100644 index 0000000..a39c720 --- /dev/null +++ b/main.go @@ -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) + } +} diff --git a/parser.go b/parser.go index e68a052..0b2dd13 100644 --- a/parser.go +++ b/parser.go @@ -1,4 +1,4 @@ -package sql +package main import ( "fmt" @@ -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. @@ -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 } diff --git a/parser_test.go b/parser_test.go index f50c402..cb60f5b 100644 --- a/parser_test.go +++ b/parser_test.go @@ -1,4 +1,4 @@ -package sql_test +package main import ( "reflect" diff --git a/scanner.go b/scanner.go index 53a1f1f..203d372 100644 --- a/scanner.go +++ b/scanner.go @@ -1,4 +1,4 @@ -package sql +package main import ( "bufio" @@ -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 '*': @@ -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. @@ -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) diff --git a/scanner_test.go b/scanner_test.go index 78cfa49..dedeed7 100644 --- a/scanner_test.go +++ b/scanner_test.go @@ -1,4 +1,4 @@ -package sql_test +package main import ( "strings" diff --git a/token.go b/token.go index c375a69..ea05133 100644 --- a/token.go +++ b/token.go @@ -1,4 +1,4 @@ -package sql +package main // Token represents a lexical token. type Token int @@ -19,4 +19,7 @@ const ( // Keywords SELECT FROM + WHERE + AND // and + OPER )