forked from eatonphil/gosql
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for binary expressions, filtering via WHERE (eatonphil#3)
* Start support for binary expressions * Everything building, tests passing * Gofmt * Filtering generally working * Update readme, add tablewriter library * Add readline support * Add makefile and more info to readme
- Loading branch information
Showing
13 changed files
with
779 additions
and
269 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
fmt: | ||
gofmt -w -s . | ||
|
||
test: | ||
go test -race . |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
# gosql | ||
|
||
An early PostgreSQL implementation in Go (WIP) | ||
An early PostgreSQL implementation in Go. | ||
|
||
## Example | ||
|
||
|
@@ -9,21 +9,67 @@ $ git clone [email protected]:eatonphil/gosql | |
$ cd gosql | ||
$ go run cmd/main.go | ||
Welcome to gosql. | ||
# CREATE TABLE users (id INT, name TEXT); | ||
# CREATE TABLE users (name TEXT, age INT); | ||
ok | ||
# INSERT INTO users VALUES (1, 'Phil'); | ||
# INSERT INTO users VALUES ('Stephen', 16); | ||
ok | ||
# SELECT id, name FROM users; | ||
| id | name | | ||
==================== | ||
| 1 | Phil | | ||
# SELECT name, age FROM users; | ||
name | age | ||
----------+------ | ||
Stephen | 16 | ||
(1 result) | ||
ok | ||
# INSERT INTO users VALUES (2, 'Kate'); | ||
# INSERT INTO users VALUES ('Adrienne', 23); | ||
ok | ||
# SELECT name, id FROM users; | ||
| name | id | | ||
==================== | ||
| Phil | 1 | | ||
| Kate | 2 | | ||
# SELECT age + 2, name FROM users WHERE age = 23; | ||
age | name | ||
------+----------- | ||
25 | Adrienne | ||
(1 result) | ||
ok | ||
# SELECT name FROM users; | ||
name | ||
------------ | ||
Stephen | ||
Adrienne | ||
(2 results) | ||
ok | ||
``` | ||
|
||
## Architecture | ||
|
||
* [cmd/main.go](./cmd/main.go) | ||
* Contains the REPL and high-level interface to the project | ||
* Dataflow is: user input -> lexer -> parser -> in-memory backend | ||
* [lexer.go](./lexer.go) | ||
* Handles breaking user input into tokens for the parser | ||
* [parser.go](./parser.go) | ||
* Matches a list of tokens into an AST or fails if the user input is not a valid program | ||
* [memory.go](./memory.go) | ||
* An example, in-memory backend supporting the Backend interface (defined in backend.go) | ||
|
||
## Contributing | ||
|
||
* Add a new operator (such as `<`, `>`, etc.) supported by PostgreSQL | ||
* Add a new data type supported by PostgreSQL | ||
|
||
In each case, you'll probably have to add support in the lexer, | ||
parser, and in-memory backend. I recommend going in that order. | ||
|
||
In all cases, make sure the code is formatted (`make fmt`) and passes | ||
tests (`make test`). New code should have tests. | ||
|
||
## Blog series | ||
|
||
* [https://notes.eatonphil.com/database-basics.html](Writing a SQL database from scratch in Go) | ||
|
||
## Further reading | ||
|
||
Here are some similar projects written in Go. | ||
|
||
* [go-mysql-server](https://github.com/src-d/go-mysql-server) | ||
* This is a MySQL frontend (with an in-memory backend for testing only). | ||
* [ramsql](https://github.com/proullon/ramsql) | ||
* This is a WIP PostgreSQL-compatible in-memory database. | ||
* [CockroachDB](https://github.com/cockroachdb/cockroach) | ||
* This is a production-ready PostgreSQL-compatible database. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,81 +1,134 @@ | ||
package main | ||
|
||
import ( | ||
"bufio" | ||
"fmt" | ||
"io" | ||
"os" | ||
"strings" | ||
|
||
"github.com/eatonphil/gosql" | ||
|
||
"github.com/chzyer/readline" | ||
"github.com/olekukonko/tablewriter" | ||
) | ||
|
||
func doSelect(mb gosql.Backend, slct *gosql.SelectStatement) error { | ||
results, err := mb.Select(slct) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if len(results.Rows) == 0 { | ||
fmt.Println("(no results)") | ||
return nil | ||
} | ||
|
||
table := tablewriter.NewWriter(os.Stdout) | ||
header := []string{} | ||
for _, col := range results.Columns { | ||
header = append(header, col.Name) | ||
} | ||
table.SetHeader(header) | ||
table.SetAutoFormatHeaders(false) | ||
|
||
rows := [][]string{} | ||
for _, result := range results.Rows { | ||
row := []string{} | ||
for i, cell := range result { | ||
typ := results.Columns[i].Type | ||
s := "" | ||
switch typ { | ||
case gosql.IntType: | ||
s = fmt.Sprintf("%d", cell.AsInt()) | ||
case gosql.TextType: | ||
s = cell.AsText() | ||
case gosql.BoolType: | ||
s = "true" | ||
if !cell.AsBool() { | ||
s = "false" | ||
} | ||
} | ||
|
||
row = append(row, s) | ||
} | ||
|
||
rows = append(rows, row) | ||
} | ||
|
||
table.SetBorder(false) | ||
table.AppendBulk(rows) | ||
table.Render() | ||
|
||
if len(rows) == 1 { | ||
fmt.Println("(1 result)") | ||
} else { | ||
fmt.Printf("(%d results)\n", len(rows)) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func main() { | ||
mb := gosql.NewMemoryBackend() | ||
|
||
reader := bufio.NewReader(os.Stdin) | ||
l, err := readline.NewEx(&readline.Config{ | ||
Prompt: "# ", | ||
HistoryFile: "/tmp/gosql.tmp", | ||
InterruptPrompt: "^C", | ||
EOFPrompt: "exit", | ||
}) | ||
if err != nil { | ||
panic(err) | ||
} | ||
defer l.Close() | ||
|
||
fmt.Println("Welcome to gosql.") | ||
repl: | ||
for { | ||
fmt.Print("# ") | ||
text, err := reader.ReadString('\n') | ||
text = strings.Replace(text, "\n", "", -1) | ||
line, err := l.Readline() | ||
if err == readline.ErrInterrupt { | ||
if len(line) == 0 { | ||
break | ||
} else { | ||
continue repl | ||
} | ||
} else if err == io.EOF { | ||
break | ||
} | ||
if err != nil { | ||
fmt.Println("Error while reading line:", err) | ||
continue repl | ||
} | ||
|
||
ast, err := gosql.Parse(text) | ||
ast, err := gosql.Parse(line) | ||
if err != nil { | ||
panic(err) | ||
fmt.Println("Error while parsing:", err) | ||
continue repl | ||
} | ||
|
||
for _, stmt := range ast.Statements { | ||
switch stmt.Kind { | ||
case gosql.CreateTableKind: | ||
err = mb.CreateTable(ast.Statements[0].CreateTableStatement) | ||
if err != nil { | ||
panic(err) | ||
fmt.Println("Error creating table", err) | ||
continue repl | ||
} | ||
fmt.Println("ok") | ||
case gosql.InsertKind: | ||
err = mb.Insert(stmt.InsertStatement) | ||
if err != nil { | ||
panic(err) | ||
fmt.Println("Error inserting values:", err) | ||
continue repl | ||
} | ||
|
||
fmt.Println("ok") | ||
case gosql.SelectKind: | ||
results, err := mb.Select(stmt.SelectStatement) | ||
err := doSelect(mb, stmt.SelectStatement) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
for _, col := range results.Columns { | ||
fmt.Printf("| %s ", col.Name) | ||
fmt.Println("Error selecting values:", err) | ||
continue repl | ||
} | ||
fmt.Println("|") | ||
|
||
for i := 0; i < 20; i++ { | ||
fmt.Printf("=") | ||
} | ||
fmt.Println() | ||
|
||
for _, result := range results.Rows { | ||
fmt.Printf("|") | ||
|
||
for i, cell := range result { | ||
typ := results.Columns[i].Type | ||
s := "" | ||
switch typ { | ||
case gosql.IntType: | ||
s = fmt.Sprintf("%d", cell.AsInt()) | ||
case gosql.TextType: | ||
s = cell.AsText() | ||
} | ||
|
||
fmt.Printf(" %s | ", s) | ||
} | ||
|
||
fmt.Println() | ||
} | ||
|
||
fmt.Println("ok") | ||
} | ||
} | ||
|
||
fmt.Println("ok") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.