Repository: proullon/ramsql
Pull request #57 (2022-06-10 09:21:19)

Fix bug of default clause
The current version of ramsql fails to parse the following CREATE statement.

```go
package main

import (
        "database/sql"
        "fmt"

        _ "github.com/proullon/ramsql/driver"
)

func main() {
        queries := []string{
                `
                CREATE TABLE "user" (
                        "id" int,
                        "email" VARCHAR(10) DEFAULT 'myuu222'
                )
                `,
        }

        db, _ := sql.Open("ramsql", "TEST")
        defer db.Close()

        for _, q := range queries {
                _, err := db.Exec(q)
                fmt.Println(err)
        }
}
```

Result
```
Syntax error near default ' myuu222
```

This PR fixes this bug.


Patch:
diff --git a/engine/parser/create.go b/engine/parser/create.go
index 8cc2657..a94e8c0 100644
--- a/engine/parser/create.go
+++ b/engine/parser/create.go
@@ -174,16 +174,11 @@ func (p *parser) parseTable(tokens []Token) (*Decl, error) {
 					timeDecl.Add(zoneDecl)
 				}
 			case DefaultToken: // DEFAULT
-				dDecl, err := p.consumeToken(DefaultToken)
+				dDecl, err := p.parseDefaultClause()
 				if err != nil {
 					return nil, err
 				}
 				newAttribute.Add(dDecl)
-				vDecl, err := p.consumeToken(FalseToken, StringToken, NumberToken, LocalTimestampToken, NowToken)
-				if err != nil {
-					return nil, err
-				}
-				dDecl.Add(vDecl)
 			default:
 				// Unknown column constraint
 				return nil, p.syntaxError()
@@ -205,6 +200,27 @@ func (p *parser) parseTable(tokens []Token) (*Decl, error) {
 	return tableDecl, nil
 }
 
+func (p *parser) parseDefaultClause() (*Decl, error) {
+	dDecl, err := p.consumeToken(DefaultToken)
+	if err != nil {
+		return nil, err
+	}
+
+	var vDecl *Decl
+
+	if p.is(SimpleQuoteToken) || p.is(DoubleQuoteToken) {
+		vDecl, err = p.parseStringLiteral()
+	} else {
+		vDecl, err = p.consumeToken(FalseToken, NumberToken, LocalTimestampToken, NowToken)
+	}
+
+	if err != nil {
+		return nil, err
+	}
+	dDecl.Add(vDecl)
+	return dDecl, nil
+}
+
 func (p *parser) parsePrimaryKey() (*Decl, error) {
 	primaryDecl, err := p.consumeToken(PrimaryToken)
 	if err != nil {
diff --git a/engine/parser/parser.go b/engine/parser/parser.go
index c621466..3dc496b 100644
--- a/engine/parser/parser.go
+++ b/engine/parser/parser.go
@@ -754,6 +754,26 @@ func (p *parser) parseValue() (*Decl, error) {
 	return valueDecl, nil
 }
 
+func (p *parser) parseStringLiteral() (*Decl, error) {
+	singleQuoted := p.is(SimpleQuoteToken)
+	_, err := p.consumeToken(SimpleQuoteToken, DoubleQuoteToken)
+	if err != nil {
+		return nil, err
+	}
+	valueDecl, err := p.consumeToken(StringToken)
+	if err != nil {
+		return nil, err
+	}
+	if (singleQuoted && p.is(DoubleQuoteToken)) || (!singleQuoted && p.is(SimpleQuoteToken)) {
+		return nil, fmt.Errorf("Quotation marks do not match.")
+	}
+	_, err = p.consumeToken(SimpleQuoteToken, DoubleQuoteToken)
+	if err != nil {
+		return nil, err
+	}
+	return valueDecl, nil
+}
+
 // parseJoin parses the JOIN keywords and all its condition
 // JOIN user_addresses ON address.id=user_addresses.address_id
 func (p *parser) parseJoin() (*Decl, error) {


Test patch:
diff --git a/engine/insert_test.go b/engine/insert_test.go
index 81f7a93..4c85c3b 100644
--- a/engine/insert_test.go
+++ b/engine/insert_test.go
@@ -99,6 +99,61 @@ func TestInsertSingleReturning(t *testing.T) {
 	}
 }
 
+func TestInsertWithMissingValue(t *testing.T) {
+	log.UseTestLogger(t)
+
+	db, err := sql.Open("ramsql", "TestInsertWithMissingValue")
+	if err != nil {
+		t.Fatalf("sql.Open : Error : %s\n", err)
+	}
+	defer db.Close()
+
+	_, err = db.Exec(`
+		CREATE TABLE user (
+			id INT AUTOINCREMENT,
+			email TEXT DEFAULT 'example@example.com',
+			name TEXT
+		)
+	`)
+	if err != nil {
+		t.Fatalf("sql.Exec: Error: %s\n", err)
+	}
+
+	result, err := db.Exec("INSERT INTO user (name) VALUES ('Bob')")
+	if err != nil {
+		t.Fatalf("Cannot insert into table user: %s", err)
+	}
+
+	rowsAffected, err := result.RowsAffected()
+	if err != nil {
+		t.Fatalf("Cannot check rows affected: %s", err)
+	}
+	if rowsAffected != 1 {
+		t.Fatalf("Expected to affect 1 row, affected %v", rowsAffected)
+	}
+
+	insertedId, err := result.LastInsertId()
+	if err != nil {
+		t.Fatalf("Cannot check last inserted ID: %s", err)
+	}
+
+	row := db.QueryRow("SELECT email, name FROM user WHERE id = ?", insertedId)
+	if row == nil {
+		t.Fatalf("sql.Query failed")
+	}
+
+	var email string
+	var name string
+	err = row.Scan(&email, &name)
+	if err != nil {
+		t.Fatalf("row.Scan: %s", err)
+	}
+
+	if email != "example@example.com" || name != "Bob" {
+		t.Fatalf("Expected email 'example@example.com' and name 'Bob', got email '%v' and name '%v'", email, name)
+	}
+}
+
 func TestInsertMultiple(t *testing.T) {
 	log.UseTestLogger(t)
 
diff --git a/engine/parser/parser_test.go b/engine/parser/parser_test.go
index 942b4fe..96eafce 100644
--- a/engine/parser/parser_test.go
+++ b/engine/parser/parser_test.go
@@ -16,6 +16,15 @@ func TestParserCreateTableSimpleWithPrimaryKey(t *testing.T) {
 	parse(query, 1, t)
 }
 
+func TestParserCreateTableWithDefaultClause(t *testing.T) {
+	query := `CREATE TABLE account_detail
+	(
+		id INT PRIMARY KEY,
+		email TEXT DEFAULT 'example@example.com'
+	)`
+	parse(query, 1, t)
+}
+
 func TestParserMultipleInstructions(t *testing.T) {
 	query := `CREATE TABLE account (id INT, email TEXT);CREATE TABLE user (id INT, email TEXT)`
 	parse(query, 2, t)