Repository: tomarrell/lbadd
Pull request #74 (2020-03-09 12:32:38)

Implements VACUUM statement
This PR implements the VACUUM statement (https://sqlite.org/lang_vacuum.html) and is a part of #66


Patch:
diff --git a/internal/parser/simple_parser_rules.go b/internal/parser/simple_parser_rules.go
index 8147cf7..3beea56 100644
--- a/internal/parser/simple_parser_rules.go
+++ b/internal/parser/simple_parser_rules.go
@@ -46,6 +46,8 @@ func (p *simpleParser) parseSQLStatement(r reporter) (stmt *ast.SQLStmt) {
 		stmt.AttachStmt = p.parseAttachDatabaseStmt(r)
 	case token.KeywordDetach:
 		stmt.DetachStmt = p.parseDetachDatabaseStmt(r)
+	case token.KeywordVacuum:
+		stmt.VacuumStmt = p.parseVacuumStmt(r)
 	case token.StatementSeparator:
 		r.incompleteStatement()
 		p.consumeToken()
@@ -630,8 +632,8 @@ func (p *simpleParser) parseExpression(r reporter) (expr *ast.Expr) {
 	return
 }
 
-// parseAttachDatabaseStmt parses statments of the form :
-// ATTACH DATABASE expr AS schema-name
+// parseAttachDatabaseStmt parses statments as defined in the spec:
+// https://sqlite.org/lang_attach.html
 func (p *simpleParser) parseAttachDatabaseStmt(r reporter) (stmt *ast.AttachStmt) {
 	stmt = &ast.AttachStmt{}
 	p.searchNext(r, token.KeywordAttach)
@@ -679,9 +681,8 @@ func (p *simpleParser) parseAttachDatabaseStmt(r reporter) (stmt *ast.AttachStmt
 	return
 }
 
-// parseDetachDatabaseStmt parses statements of the form :
-// DETACH DATABASE schema-name
-// DETACH schema-name
+// parseDetachDatabaseStmt parses statements as defined in spec:
+// https://sqlite.org/lang_detach.html
 func (p *simpleParser) parseDetachDatabaseStmt(r reporter) (stmt *ast.DetachStmt) {
 	stmt = &ast.DetachStmt{}
 	p.searchNext(r, token.KeywordDetach)
@@ -714,3 +715,52 @@ func (p *simpleParser) parseDetachDatabaseStmt(r reporter) (stmt *ast.DetachStmt
 	p.consumeToken()
 	return
 }
+
+// parseVacuumStmt parses the staments as defined in the spec:
+// https://sqlite.org/lang_vacuum.html
+func (p *simpleParser) parseVacuumStmt(r reporter) (stmt *ast.VacuumStmt) {
+	stmt = &ast.VacuumStmt{}
+	p.searchNext(r, token.KeywordVacuum)
+	next, ok := p.lookahead(r)
+	if !ok {
+		return
+	}
+	stmt.Vacuum = next
+	p.consumeToken()
+
+	// optionalLookahead is used because, the lookahead function
+	// always looks for the next "real" token and not EOF.
+	// Since Just "VACUUM" is a valid statement, we have to accept
+	// the fact that there can be no tokens after the first keyword.
+	// Same logic is applied for the next INTO keyword check too.
+	next, ok = p.optionalLookahead(r)
+	if !ok {
+		return
+	}
+
+	if next.Type() == token.Literal {
+		stmt.SchemaName = next
+		p.consumeToken()
+	}
+
+	next, ok = p.optionalLookahead(r)
+	if !ok {
+		return
+	}
+
+	if next.Type() == token.KeywordInto {
+		stmt.Into = next
+		p.consumeToken()
+		fileName, ok := p.lookahead(r)
+		if !ok {
+			return
+		}
+		if fileName.Type() != token.Literal {
+			r.unexpectedToken(token.Literal)
+			return
+		}
+		stmt.Filename = fileName
+		p.consumeToken()
+	}
+	return
+}


Test patch:
diff --git a/internal/parser/parser_test.go b/internal/parser/parser_test.go
index 43a3f9a..09cd040 100644
--- a/internal/parser/parser_test.go
+++ b/internal/parser/parser_test.go
@@ -11,10 +11,12 @@ import (
 
 func TestSingleStatementParse(t *testing.T) {
 	inputs := []struct {
+		Name  string
 		Query string
 		Stmt  *ast.SQLStmt
 	}{
 		{
+			"alter rename table",
 			"ALTER TABLE users RENAME TO admins",
 			&ast.SQLStmt{
 				AlterTableStmt: &ast.AlterTableStmt{
@@ -28,6 +30,7 @@ func TestSingleStatementParse(t *testing.T) {
 			},
 		},
 		{
+			"alter rename column",
 			"ALTER TABLE users RENAME COLUMN name TO username",
 			&ast.SQLStmt{
 				AlterTableStmt: &ast.AlterTableStmt{
@@ -43,6 +46,7 @@ func TestSingleStatementParse(t *testing.T) {
 			},
 		},
 		{
+			"alter rename column implicit",
 			"ALTER TABLE users RENAME name TO username",
 			&ast.SQLStmt{
 				AlterTableStmt: &ast.AlterTableStmt{
@@ -57,6 +61,7 @@ func TestSingleStatementParse(t *testing.T) {
 			},
 		},
 		{
+			"alter add column with two constraints",
 			"ALTER TABLE users ADD COLUMN foo VARCHAR(15) CONSTRAINT pk PRIMARY KEY AUTOINCREMENT CONSTRAINT nn NOT NULL",
 			&ast.SQLStmt{
 				AlterTableStmt: &ast.AlterTableStmt{
@@ -97,6 +102,7 @@ func TestSingleStatementParse(t *testing.T) {
 			},
 		},
 		{
+			"alter add column implicit with two constraints",
 			"ALTER TABLE users ADD foo VARCHAR(15) CONSTRAINT pk PRIMARY KEY AUTOINCREMENT CONSTRAINT nn NOT NULL",
 			&ast.SQLStmt{
 				AlterTableStmt: &ast.AlterTableStmt{
@@ -136,6 +142,7 @@ func TestSingleStatementParse(t *testing.T) {
 			},
 		},
 		{
+			"attach database",
 			"ATTACH DATABASE myDb AS newDb",
 			&ast.SQLStmt{
 				AttachStmt: &ast.AttachStmt{
@@ -150,6 +157,7 @@ func TestSingleStatementParse(t *testing.T) {
 			},
 		},
 		{
+			"attach schema",
 			"ATTACH mySchema AS newSchema",
 			&ast.SQLStmt{
 				AttachStmt: &ast.AttachStmt{
@@ -163,6 +171,7 @@ func TestSingleStatementParse(t *testing.T) {
 			},
 		},
 		{
+			"DETACH with DATABASE",
 			"DETACH DATABASE newDb",
 			&ast.SQLStmt{
 				DetachStmt: &ast.DetachStmt{
@@ -173,6 +182,7 @@ func TestSingleStatementParse(t *testing.T) {
 			},
 		},
 		{
+			"DETACH without DATABASE",
 			"DETACH newSchema",
 			&ast.SQLStmt{
 				DetachStmt: &ast.DetachStmt{
@@ -181,9 +191,51 @@ func TestSingleStatementParse(t *testing.T) {
 				},
 			},
 		},
+		{
+			"vacuum",
+			"VACUUM",
+			&ast.SQLStmt{
+				VacuumStmt: &ast.VacuumStmt{
+					Vacuum: token.New(1, 1, 0, 6, token.KeywordVacuum, "VACUUM"),
+				},
+			},
+		},
+		{
+			"VACUUM with schema-name",
+			"VACUUM mySchema",
+			&ast.SQLStmt{
+				VacuumStmt: &ast.VacuumStmt{
+					Vacuum:     token.New(1, 1, 0, 6, token.KeywordVacuum, "VACUUM"),
+					SchemaName: token.New(1, 8, 7, 8, token.Literal, "mySchema"),
+				},
+			},
+		},
+		{
+			"VACUUM with INTO",
+			"VACUUM INTO newFile",
+			&ast.SQLStmt{
+				VacuumStmt: &ast.VacuumStmt{
+					Vacuum:   token.New(1, 1, 0, 6, token.KeywordVacuum, "VACUUM"),
+					Into:     token.New(1, 8, 7, 4, token.KeywordInto, "INTO"),
+					Filename: token.New(1, 13, 12, 7, token.Literal, "newFile"),
+				},
+			},
+		},
+		{
+			"VACUUM with schema-name and INTO",
+			"VACUUM mySchema INTO newFile",
+			&ast.SQLStmt{
+				VacuumStmt: &ast.VacuumStmt{
+					Vacuum:     token.New(1, 1, 0, 6, token.KeywordVacuum, "VACUUM"),
+					SchemaName: token.New(1, 8, 7, 8, token.Literal, "mySchema"),
+					Into:       token.New(1, 17, 16, 4, token.KeywordInto, "INTO"),
+					Filename:   token.New(1, 22, 21, 7, token.Literal, "newFile"),
+				},
+			},
+		},
 	}
 	for _, input := range inputs {
-		t.Run(input.Query[0:11], func(t *testing.T) {
+		t.Run(input.Name, func(t *testing.T) {
 			assert := assert.New(t)
 
 			p := New(input.Query)