Repository: tomarrell/lbadd
Pull request #71 (2020-03-09 11:04:56)

Implement ATTACH statement
This PR completes the implementation of ATTACH statement (https://sqlite.org/lang_attach.html), and completes a part of #66 


Patch:
diff --git a/internal/parser/simple_parser_rules.go b/internal/parser/simple_parser_rules.go
index 89c8729..3559db3 100644
--- a/internal/parser/simple_parser_rules.go
+++ b/internal/parser/simple_parser_rules.go
@@ -42,6 +42,8 @@ func (p *simpleParser) parseSQLStatement(r reporter) (stmt *ast.SQLStmt) {
 	switch next.Type() {
 	case token.KeywordAlter:
 		stmt.AlterTableStmt = p.parseAlterTableStmt(r)
+	case token.KeywordAttach:
+		stmt.AttachStmt = p.parseAttachDatabaseStmt(r)
 	case token.StatementSeparator:
 		r.incompleteStatement()
 		p.consumeToken()
@@ -616,7 +618,61 @@ func (p *simpleParser) parseExpression(r reporter) (expr *ast.Expr) {
 	if !ok {
 		return
 	}
-	r.unsupportedConstruct(next)
-	p.searchNext(r, token.StatementSeparator, token.EOF)
+	if next.Type() == token.Literal {
+		expr.LiteralValue = next
+		p.consumeToken()
+	} else {
+		r.unsupportedConstruct(next)
+		p.searchNext(r, token.StatementSeparator, token.EOF)
+	}
+	return
+}
+
+// parseAttachDatabaseStmt parses statments of the form :
+// ATTACH DATABASE expr AS schema-name
+func (p *simpleParser) parseAttachDatabaseStmt(r reporter) (stmt *ast.AttachStmt) {
+	stmt = &ast.AttachStmt{}
+	p.searchNext(r, token.KeywordAttach)
+	next, ok := p.lookahead(r)
+	if !ok {
+		return
+	}
+	stmt.Attach = next
+	p.consumeToken()
+
+	next, ok = p.lookahead(r)
+	if !ok {
+		return
+	}
+
+	if next.Type() == token.KeywordDatabase {
+		stmt.Database = next
+		p.consumeToken()
+	}
+	stmt.Expr = p.parseExpression(r)
+
+	next, ok = p.lookahead(r)
+	if !ok {
+		return
+	}
+
+	if next.Type() == token.KeywordAs {
+		stmt.As = next
+		p.consumeToken()
+	} else {
+		r.unexpectedToken(token.KeywordAs)
+		return
+	}
+
+	schemaName, ok := p.lookahead(r)
+	if !ok {
+		return
+	}
+	if schemaName.Type() != token.Literal {
+		r.unexpectedToken(token.Literal)
+		return
+	}
+	stmt.SchemaName = schemaName
+	p.consumeToken()
 	return
 }


Test patch:
diff --git a/internal/parser/parser_test.go b/internal/parser/parser_test.go
index 6b64007..799ea57 100644
--- a/internal/parser/parser_test.go
+++ b/internal/parser/parser_test.go
@@ -135,6 +135,33 @@ func TestSingleStatementParse(t *testing.T) {
 				},
 			},
 		},
+		{
+			"ATTACH DATABASE myDb AS newDb",
+			&ast.SQLStmt{
+				AttachStmt: &ast.AttachStmt{
+					Attach:   token.New(1, 1, 0, 6, token.KeywordAttach, "ATTACH"),
+					Database: token.New(1, 8, 7, 8, token.KeywordDatabase, "DATABASE"),
+					Expr: &ast.Expr{
+						LiteralValue: token.New(1, 17, 16, 4, token.Literal, "myDb"),
+					},
+					As:         token.New(1, 22, 21, 2, token.KeywordAs, "AS"),
+					SchemaName: token.New(1, 25, 24, 5, token.Literal, "newDb"),
+				},
+			},
+		},
+		{
+			"ATTACH mySchema AS newSchema",
+			&ast.SQLStmt{
+				AttachStmt: &ast.AttachStmt{
+					Attach: token.New(1, 1, 0, 6, token.KeywordAttach, "ATTACH"),
+					Expr: &ast.Expr{
+						LiteralValue: token.New(1, 8, 7, 8, token.Literal, "mySchema"),
+					},
+					As:         token.New(1, 17, 16, 2, token.KeywordAs, "AS"),
+					SchemaName: token.New(1, 20, 19, 9, token.Literal, "newSchema"),
+				},
+			},
+		},
 	}
 	for _, input := range inputs {
 		t.Run(input.Query[0:11], func(t *testing.T) {
@@ -165,7 +192,6 @@ func TestSingleStatementParse(t *testing.T) {
 						t1.Value() == t2.Value()
 				}),
 			}
-
 			t.Log(cmp.Diff(input.Stmt, stmt, opts...))
 			assert.True(cmp.Equal(input.Stmt, stmt, opts...))