Repository: proullon/ramsql
Pull request #36 (2020-01-24 10:27:26)

Export NewDriver func.
So we can use this in an SQL interceptor project like
https://github.com/ngrok/sqlmw.

`sqlmw` needs us to use the driver object to be intercepted.
And we currently can't create the driver.



Patch:
diff --git a/driver/driver.go b/driver/driver.go
index bf3c13c..549ad18 100644
--- a/driver/driver.go
+++ b/driver/driver.go
@@ -14,7 +14,7 @@ import (
 )
 
 func init() {
-	sql.Register("ramsql", newDriver())
+	sql.Register("ramsql", NewDriver())
 	log.SetLevel(log.WarningLevel)
 }
 
@@ -40,7 +40,8 @@ type Driver struct {
 	servers map[string]*Server
 }
 
-func newDriver() *Driver {
+// NewDriver creates a driver object
+func NewDriver() *Driver {
 	d := &Driver{}
 	d.servers = make(map[string]*Server)
 	return d
diff --git a/engine/operator.go b/engine/operator.go
index 2446e46..f8cd6b8 100644
--- a/engine/operator.go
+++ b/engine/operator.go
@@ -16,6 +16,8 @@ func NewOperator(token int, lexeme string) (Operator, error) {
 	switch token {
 	case parser.EqualityToken:
 		return equalityOperator, nil
+	case parser.DistinctnessToken:
+		return distinctnessOperator, nil
 	case parser.LeftDipleToken:
 		return lessThanOperator, nil
 	case parser.RightDipleToken:
@@ -170,6 +172,16 @@ func equalityOperator(leftValue Value, rightValue Value) bool {
 	return false
 }
 
+// DistinctnessOperator checks if given value are distinct
+func distinctnessOperator(leftValue Value, rightValue Value) bool {
+
+	if fmt.Sprintf("%v", leftValue.v) != rightValue.lexeme {
+		return true
+	}
+
+	return false
+}
+
 // TrueOperator always returns true
 func TrueOperator(leftValue Value, rightValue Value) bool {
 	return true
diff --git a/engine/parser/lexer.go b/engine/parser/lexer.go
index 21d797e..fcf906d 100644
--- a/engine/parser/lexer.go
+++ b/engine/parser/lexer.go
@@ -28,6 +28,7 @@ const (
 	SimpleQuoteToken
 	StarToken
 	EqualityToken
+	DistinctnessToken
 	PeriodToken
 
 	// First order Token
@@ -123,6 +124,7 @@ func (l *lexer) lex(instruction []byte) ([]Token, error) {
 	matchers = append(matchers, l.MatchStarToken)
 	matchers = append(matchers, l.MatchSimpleQuoteToken)
 	matchers = append(matchers, l.MatchEqualityToken)
+	matchers = append(matchers, l.MatchDistinctnessToken)
 	matchers = append(matchers, l.MatchPeriodToken)
 	matchers = append(matchers, l.MatchDoubleQuoteToken)
 	matchers = append(matchers, l.MatchLessOrEqualToken)
@@ -471,6 +473,10 @@ func (l *lexer) MatchEqualityToken() bool {
 	return l.MatchSingle('=', EqualityToken)
 }
 
+func (l *lexer) MatchDistinctnessToken() bool {
+	return l.Match([]byte("<>"), DistinctnessToken)
+}
+
 func (l *lexer) MatchLeftDipleToken() bool {
 	return l.MatchSingle('<', LeftDipleToken)
 }
diff --git a/engine/parser/parser.go b/engine/parser/parser.go
index 80ce805..dd07d84 100644
--- a/engine/parser/parser.go
+++ b/engine/parser/parser.go
@@ -560,7 +560,7 @@ func (p *parser) parseCondition() (*Decl, error) {
 	}
 
 	switch p.cur().Token {
-	case EqualityToken, LeftDipleToken, RightDipleToken, LessOrEqualToken, GreaterOrEqualToken:
+	case EqualityToken, DistinctnessToken, LeftDipleToken, RightDipleToken, LessOrEqualToken, GreaterOrEqualToken:
 		decl, err := p.consumeToken(p.cur().Token)
 		if err != nil {
 			return nil, err
diff --git a/engine/select.go b/engine/select.go
index f908e0b..63ac478 100644
--- a/engine/select.go
+++ b/engine/select.go
@@ -363,7 +363,7 @@ func whereExecutor2(e *Engine, decl []*parser.Decl, fromTableName string) (Predi
 	}
 
 	switch cond.Decl[0].Token {
-	case parser.IsToken, parser.InToken, parser.EqualityToken, parser.LeftDipleToken, parser.RightDipleToken, parser.LessOrEqualToken, parser.GreaterOrEqualToken:
+	case parser.IsToken, parser.InToken, parser.EqualityToken, parser.DistinctnessToken, parser.LeftDipleToken, parser.RightDipleToken, parser.LessOrEqualToken, parser.GreaterOrEqualToken:
 		break
 	default:
 		fromTableName = cond.Decl[0].Lexeme
@@ -444,8 +444,8 @@ func whereExecutor(whereDecl *parser.Decl, fromTableName string) ([]Predicate, e
 		}
 
 		switch cond.Decl[0].Token {
-		case parser.EqualityToken, parser.LeftDipleToken, parser.RightDipleToken, parser.LessOrEqualToken, parser.GreaterOrEqualToken:
-			log.Debug("whereExecutor: it's = < > <= >=\n")
+		case parser.EqualityToken, parser.DistinctnessToken, parser.LeftDipleToken, parser.RightDipleToken, parser.LessOrEqualToken, parser.GreaterOrEqualToken:
+			log.Debug("whereExecutor: it's = <> < > <= >=\n")
 			break
 		case parser.InToken:
 			log.Debug("whereExecutor: it's IN\n")


Test patch:
diff --git a/driver/driver_test.go b/driver/driver_test.go
index d680b31..0b0eecb 100644
--- a/driver/driver_test.go
+++ b/driver/driver_test.go
@@ -499,6 +499,62 @@ func TestAnd(t *testing.T) {
 
 }
 
+func TestEqualAndDistinct(t *testing.T) {
+	log.UseTestLogger(t)
+
+	batch := []string{
+		`CREATE TABLE user (name TEXT, surname TEXT, age INT);`,
+		`INSERT INTO user (name, surname, age) VALUES ('Foo', 'Bar', 20);`,
+		`INSERT INTO user (name, surname, age) VALUES ('John', 'Doe', 32);`,
+		`INSERT INTO user (name, surname, age) VALUES ('Jane', 'Doe', 33);`,
+		`INSERT INTO user (name, surname, age) VALUES ('Joe', 'Doe', 10);`,
+		`INSERT INTO user (name, surname, age) VALUES ('Homer', 'Simpson', 40);`,
+		`INSERT INTO user (name, surname, age) VALUES ('Marge', 'Simpson', 40);`,
+		`INSERT INTO user (name, surname, age) VALUES ('Bruce', 'Wayne', 3333);`,
+	}
+
+	db, err := sql.Open("ramsql", "TestEqualAndDistinct")
+	if err != nil {
+		t.Fatalf("sql.Open : Error : %s\n", err)
+	}
+	defer db.Close()
+
+	for _, b := range batch {
+		_, err = db.Exec(b)
+		if err != nil {
+			t.Fatalf("sql.Exec: Error: %s\n", err)
+		}
+	}
+
+	query := `SELECT * FROM user
+			WHERE user.surname = 'Doe'
+			AND user.name <> 'Jane'`
+
+	rows, err := db.Query(query)
+	if err != nil {
+		t.Fatalf("sql.Query: %s", err)
+	}
+
+	var nb int
+	for rows.Next() {
+		var name, surname string
+		var age int
+		if err := rows.Scan(&name, &surname, &age); err != nil {
+			t.Fatalf("Cannot scan row: %s", err)
+		}
+		if name == "Jane" || surname != "Doe" {
+			t.Fatalf("Unwanted row: %s %s %d", name, surname, age)
+		}
+
+		nb++
+	}
+
+	if nb != 2 {
+		t.Fatalf("Expected 2 rows, got %d", nb)
+	}
+
+}
+
 func TestGreaterThanOrEqualAndLessThanOrEqual(t *testing.T) {
 	log.UseTestLogger(t)
 
diff --git a/engine/parser/lexer_test.go b/engine/parser/lexer_test.go
index 2d6ce36..e60661a 100644
--- a/engine/parser/lexer_test.go
+++ b/engine/parser/lexer_test.go
@@ -42,3 +42,17 @@ func TestLexerWithGTOEandLTOEOperator(t *testing.T) {
 		t.Fatalf("Lexing failed, expected 21 tokens, got %d", len(decls))
 	}
 }
+
+func TestLexerWithNEOperator(t *testing.T) {
+	query := `SELECT FROM foo WHERE 0 <> 1 AND 2 <> 3`
+
+	lexer := lexer{}
+	decls, err := lexer.lex([]byte(query))
+	if err != nil {
+		t.Fatalf("Cannot lex <%s> string", query)
+	}
+
+	if len(decls) != 21 {
+		t.Fatalf("Lexing failed, expected 21 tokens, got %d", len(decls))
+	}
+}