synbase: query: add "is nil" and "is not nil" expressions.

Change-Id: Idb02b8a471a20f207d577822c43c14f5788c7722
diff --git a/v23/syncbase/nosql/internal/query/eval.go b/v23/syncbase/nosql/internal/query/eval.go
index e14cee7..e923c60 100644
--- a/v23/syncbase/nosql/internal/query/eval.go
+++ b/v23/syncbase/nosql/internal/query/eval.go
@@ -37,6 +37,16 @@
 
 func evalComparisonOperators(k string, v interface{}, e *query_parser.Expression) bool {
 	lhsValue := resolveOperand(k, v, e.Operand1)
+	// Check for an is nil epression (i.e., v[.<field>...] is nil).
+	// These expressions evaluate to true if the field cannot be resolved.
+	if e.Operator.Type == query_parser.Is && e.Operand2.Type == query_parser.TypNil {
+		return lhsValue == nil
+	}
+	if e.Operator.Type == query_parser.IsNot && e.Operand2.Type == query_parser.TypNil {
+		return lhsValue != nil
+	}
+	// For anything but "is[not] nil" (which is handled above), an unresolved operator
+	// results in the expression evaluating to false.
 	if lhsValue == nil {
 		return false
 	}
diff --git a/v23/syncbase/nosql/internal/query/query_checker/query_checker.go b/v23/syncbase/nosql/internal/query/query_checker/query_checker.go
index fb1ba20..301bbc1 100644
--- a/v23/syncbase/nosql/internal/query/query_checker/query_checker.go
+++ b/v23/syncbase/nosql/internal/query/query_checker/query_checker.go
@@ -119,6 +119,16 @@
 		e.Operand2.CompRegex = compRegex
 	}
 
+	// Is/IsNot expressions require operand1 to be a value and operand2 to be nil.
+	if e.Operator.Type == query_parser.Is || e.Operator.Type == query_parser.IsNot {
+		if !IsField(e.Operand1) {
+			return Error(e.Operand1.Off, "'Is/is not' expressions require left operand to be a value operand.")
+		}
+		if e.Operand2.Type != query_parser.TypNil {
+			return Error(e.Operand2.Off, "'Is/is not' expressions require right operand to be nil.")
+		}
+	}
+
 	// type as an operand must be the first operand, the operator must be = and the 2nd operand must be string literal.
 	if (IsType(e.Operand1) && (e.Operator.Type != query_parser.Equal || e.Operand2.Type != query_parser.TypStr)) || IsType(e.Operand2) {
 		return Error(e.Off, "Type expressions must be 't = <string-literal>'.")
diff --git a/v23/syncbase/nosql/internal/query/query_checker/query_checker_test.go b/v23/syncbase/nosql/internal/query/query_checker/query_checker_test.go
index b819c9d..e51e7e0 100644
--- a/v23/syncbase/nosql/internal/query/query_checker/query_checker_test.go
+++ b/v23/syncbase/nosql/internal/query/query_checker/query_checker_test.go
@@ -83,6 +83,10 @@
 		{"select v from Customer where false = true"},
 		{"select v from Customer where true = false"},
 		{"select v from Customer where false <> true"},
+		{"select v from Customer where v.ZipCode is nil"},
+		{"select v from Customer where v.ZipCode Is Nil"},
+		{"select v from Customer where v.ZipCode is not nil"},
+		{"select v from Customer where v.ZipCode IS NOT NIL"},
 	}
 
 	for _, test := range basic {
@@ -254,6 +258,20 @@
 		{"select v from Customer where true <= v.A", query_checker.Error(34, "Boolean operands may only be used in equals and not equals expressions.")},
 		{"select v from Customer where Now() < Date(\"2015/07/22\")", query_checker.Error(29, "Functions are not yet supported.  Stay tuned.")},
 		{"select v from Customer where Foo(\"2015/07/22\", true, 3.14157) = true", query_checker.Error(29, "Functions are not yet supported.  Stay tuned.")},
+		{"select v from Customer where t is nil", query_checker.Error(29, "Type expressions must be 't = <string-literal>'.")},
+		{"select v from Customer where k is nil", query_checker.Error(29, "Key (i.e., 'k') expressions must be of form 'k like|= <string-literal>'.")},
+		{"select v from Customer where nil is v.ZipCode", query_checker.Error(29, "'Is/is not' expressions require left operand to be a value operand.")},
+		{"select v from Customer where v.ZipCode is \"94303\"", query_checker.Error(42, "'Is/is not' expressions require right operand to be nil.")},
+		{"select v from Customer where v.ZipCode is 94303", query_checker.Error(42, "'Is/is not' expressions require right operand to be nil.")},
+		{"select v from Customer where v.ZipCode is true", query_checker.Error(42, "'Is/is not' expressions require right operand to be nil.")},
+		{"select v from Customer where v.ZipCode is 943.03", query_checker.Error(42, "'Is/is not' expressions require right operand to be nil.")},
+		{"select v from Customer where t is not nil", query_checker.Error(29, "Type expressions must be 't = <string-literal>'.")},
+		{"select v from Customer where k is not nil", query_checker.Error(29, "Key (i.e., 'k') expressions must be of form 'k like|= <string-literal>'.")},
+		{"select v from Customer where nil is not v.ZipCode", query_checker.Error(29, "'Is/is not' expressions require left operand to be a value operand.")},
+		{"select v from Customer where v.ZipCode is not \"94303\"", query_checker.Error(46, "'Is/is not' expressions require right operand to be nil.")},
+		{"select v from Customer where v.ZipCode is not 94303", query_checker.Error(46, "'Is/is not' expressions require right operand to be nil.")},
+		{"select v from Customer where v.ZipCode is not true", query_checker.Error(46, "'Is/is not' expressions require right operand to be nil.")},
+		{"select v from Customer where v.ZipCode is not 943.03", query_checker.Error(46, "'Is/is not' expressions require right operand to be nil.")},
 	}
 
 	for _, test := range basic {
diff --git a/v23/syncbase/nosql/internal/query/query_parser/query_parser.go b/v23/syncbase/nosql/internal/query/query_parser/query_parser.go
index 61cce96..502fe43 100644
--- a/v23/syncbase/nosql/internal/query/query_parser/query_parser.go
+++ b/v23/syncbase/nosql/internal/query/query_parser/query_parser.go
@@ -80,6 +80,8 @@
 	Equal
 	GreaterThan
 	GreaterThanOrEqual
+	Is
+	IsNot
 	LessThan
 	LessThanOrEqual
 	Like
@@ -104,6 +106,7 @@
 	TypFloat
 	TypFunction
 	TypInt
+	TypNil
 	TypStr
 	TypObject // Only as the result of a ResolveOperand.
 	TypUint   // Only as a result of a ResolveOperand
@@ -535,12 +538,14 @@
 		field.Segments = append(field.Segments, segment)
 		token = scanToken(s)
 
-		// Check for true/false.  If so, change this operand to a bool.
-		// If the next token is not a period, check for true and false operands.
+		// If the next token is not a period, check for true/false/nil.
+		// If true/false or nil, change this operand to a bool or nil, respectively.
 		// Also, check for function call.  If so, change to a function operand.
-		if token.Tok != TokPERIOD && strings.ToLower(segment.Value) == "true" || strings.ToLower(segment.Value) == "false" {
+		if token.Tok != TokPERIOD && (strings.ToLower(segment.Value) == "true" || strings.ToLower(segment.Value) == "false") {
 			operand.Type = TypBool
 			operand.Bool = strings.ToLower(segment.Value) == "true"
+		} else if token.Tok != TokPERIOD && strings.ToLower(segment.Value) == "nil" {
+			operand.Type = TypNil
 		} else if token.Tok == TokLEFTPAREN {
 			operand.Type = TypFunction
 			var function Function
@@ -651,8 +656,18 @@
 		switch strings.ToLower(token.Value) {
 		case "equal":
 			operator.Type = Equal
+			token = scanToken(s)
+		case "is":
+			operator.Type = Is
+			token = scanToken(s)
+			// if the next token is "not", change to IsNot
+			if token.Tok != TokEOF && strings.ToLower(token.Value) == "not" {
+				operator.Type = IsNot
+				token = scanToken(s)
+			}
 		case "like":
 			operator.Type = Like
+			token = scanToken(s)
 		case "not":
 			token = scanToken(s)
 			if token.Tok == TokEOF || (strings.ToLower(token.Value) != "equal" && strings.ToLower(token.Value) != "like") {
@@ -664,10 +679,10 @@
 			default: //case "like":
 				operator.Type = NotLike
 			}
+			token = scanToken(s)
 		default:
 			return nil, nil, Error(token.Off, fmt.Sprintf("Expected operator ('like', 'not like', '=', '<>', '<', '<=', '>', '>=', 'equal' or 'not equal', found '%s'.", token.Value))
 		}
-		token = scanToken(s)
 	} else {
 		switch token.Tok {
 		case TokEQUAL:
@@ -909,6 +924,8 @@
 	case TypExpr:
 		val += "(expr)"
 		val += o.Expr.String()
+	case TypNil:
+		val += "<nil>"
 	case TypObject:
 		val += "(object)"
 		val += fmt.Sprintf("%v", o.Object)
@@ -930,6 +947,10 @@
 		val += ">"
 	case GreaterThanOrEqual:
 		val += ">="
+	case Is:
+		val += "IS"
+	case IsNot:
+		val += "IS NOT"
 	case LessThan:
 		val += "<"
 	case LessThanOrEqual:
diff --git a/v23/syncbase/nosql/internal/query/query_parser/query_parser_test.go b/v23/syncbase/nosql/internal/query/query_parser/query_parser_test.go
index 78a6075..8053e43 100644
--- a/v23/syncbase/nosql/internal/query/query_parser/query_parser_test.go
+++ b/v23/syncbase/nosql/internal/query/query_parser/query_parser_test.go
@@ -368,6 +368,124 @@
 			nil,
 		},
 		{
+			"select v from Customer where v.ZipCode is nil",
+			query_parser.SelectStatement{
+				Select: &query_parser.SelectClause{
+					Columns: []query_parser.Field{
+						query_parser.Field{
+							Segments: []query_parser.Segment{
+								query_parser.Segment{
+									Value: "v",
+									Node:  query_parser.Node{Off: 7},
+								},
+							},
+							Node: query_parser.Node{Off: 7},
+						},
+					},
+					Node: query_parser.Node{Off: 0},
+				},
+				From: &query_parser.FromClause{
+					Table: query_parser.TableEntry{
+						Name: "Customer",
+						Node: query_parser.Node{Off: 14},
+					},
+					Node: query_parser.Node{Off: 9},
+				},
+				Where: &query_parser.WhereClause{
+					Expr: &query_parser.Expression{
+						Operand1: &query_parser.Operand{
+							Type: query_parser.TypField,
+							Column: &query_parser.Field{
+								Segments: []query_parser.Segment{
+									query_parser.Segment{
+										Value: "v",
+										Node:  query_parser.Node{Off: 29},
+									},
+									query_parser.Segment{
+										Value: "ZipCode",
+										Node:  query_parser.Node{Off: 31},
+									},
+								},
+								Node: query_parser.Node{Off: 29},
+							},
+							Node: query_parser.Node{Off: 29},
+						},
+						Operator: &query_parser.BinaryOperator{
+							Type: query_parser.Is,
+							Node: query_parser.Node{Off: 39},
+						},
+						Operand2: &query_parser.Operand{
+							Type: query_parser.TypNil,
+							Node: query_parser.Node{Off: 42},
+						},
+						Node: query_parser.Node{Off: 29},
+					},
+					Node: query_parser.Node{Off: 23},
+				},
+				Node: query_parser.Node{Off: 0},
+			},
+			nil,
+		},
+		{
+			"select v from Customer where v.ZipCode is not nil",
+			query_parser.SelectStatement{
+				Select: &query_parser.SelectClause{
+					Columns: []query_parser.Field{
+						query_parser.Field{
+							Segments: []query_parser.Segment{
+								query_parser.Segment{
+									Value: "v",
+									Node:  query_parser.Node{Off: 7},
+								},
+							},
+							Node: query_parser.Node{Off: 7},
+						},
+					},
+					Node: query_parser.Node{Off: 0},
+				},
+				From: &query_parser.FromClause{
+					Table: query_parser.TableEntry{
+						Name: "Customer",
+						Node: query_parser.Node{Off: 14},
+					},
+					Node: query_parser.Node{Off: 9},
+				},
+				Where: &query_parser.WhereClause{
+					Expr: &query_parser.Expression{
+						Operand1: &query_parser.Operand{
+							Type: query_parser.TypField,
+							Column: &query_parser.Field{
+								Segments: []query_parser.Segment{
+									query_parser.Segment{
+										Value: "v",
+										Node:  query_parser.Node{Off: 29},
+									},
+									query_parser.Segment{
+										Value: "ZipCode",
+										Node:  query_parser.Node{Off: 31},
+									},
+								},
+								Node: query_parser.Node{Off: 29},
+							},
+							Node: query_parser.Node{Off: 29},
+						},
+						Operator: &query_parser.BinaryOperator{
+							Type: query_parser.IsNot,
+							Node: query_parser.Node{Off: 39},
+						},
+						Operand2: &query_parser.Operand{
+							Type: query_parser.TypNil,
+							Node: query_parser.Node{Off: 46},
+						},
+						Node: query_parser.Node{Off: 29},
+					},
+					Node: query_parser.Node{Off: 23},
+				},
+				Node: query_parser.Node{Off: 0},
+			},
+			nil,
+		},
+		{
 			"select v from Customer where v.Value = false",
 			query_parser.SelectStatement{
 				Select: &query_parser.SelectClause{
@@ -2118,6 +2236,8 @@
 		{"select v from Customer where Foo(,1) = true", query_parser.Error(33, "Expected operand, found ','.")},
 		{"select v from Customer where Foo(1, 2.0 = true", query_parser.Error(40, "Expected right paren or comma.")},
 		{"select v from Customer where Foo(1, 2.0 limit 100", query_parser.Error(40, "Expected right paren or comma.")},
+		{"select v from Customer where v is", query_parser.Error(33, "Unexpected end of statement, expected operand.")},
+		{"select v from Customer where v = 1.0 is k = \"abc\"", query_parser.Error(37, "Unexpected: 'is'.")},
 	}
 
 	for _, test := range basic {
diff --git a/v23/syncbase/nosql/internal/query/query_test.go b/v23/syncbase/nosql/internal/query/query_test.go
index dced5d7..a703e17 100644
--- a/v23/syncbase/nosql/internal/query/query_test.go
+++ b/v23/syncbase/nosql/internal/query/query_test.go
@@ -231,6 +231,85 @@
 			},
 		},
 		{
+			// Select values where v.InvoiceNum is nil
+			// Since InvoiceNum does not exist for Invoice,
+			// this will return just customers.
+			"select v from Customer where v.InvoiceNum is nil",
+			[]interface{}{
+				[]interface{}{customerRows[0].value},
+				[]interface{}{customerRows[4].value},
+			},
+		},
+		{
+			// Select values where v.InvoiceNum is nil
+			// or v.Name is nil This will select all customers
+			// with the former and all invoices with the latter.
+			// Hence, all records are returned.
+			"select v from Customer where v.InvoiceNum is nil or v.Name is nil",
+			[]interface{}{
+				[]interface{}{customerRows[0].value},
+				[]interface{}{customerRows[1].value},
+				[]interface{}{customerRows[2].value},
+				[]interface{}{customerRows[3].value},
+				[]interface{}{customerRows[4].value},
+				[]interface{}{customerRows[5].value},
+				[]interface{}{customerRows[6].value},
+				[]interface{}{customerRows[7].value},
+				[]interface{}{customerRows[8].value},
+			},
+		},
+		{
+			// Select values where v.InvoiceNum is nil
+			// and v.Name is nil.  Expect nothing returned.
+			"select v from Customer where v.InvoiceNum is nil and v.Name is nil",
+			[]interface{}{},
+		},
+		{
+			// Select values where v.InvoiceNum is not nil
+			// This will return just invoices.
+			"select v from Customer where v.InvoiceNum is not nil",
+			[]interface{}{
+				[]interface{}{customerRows[1].value},
+				[]interface{}{customerRows[2].value},
+				[]interface{}{customerRows[3].value},
+				[]interface{}{customerRows[5].value},
+				[]interface{}{customerRows[6].value},
+				[]interface{}{customerRows[7].value},
+				[]interface{}{customerRows[8].value},
+			},
+		},
+		{
+			// Select values where v.InvoiceNum is not nil
+			// or v.Name is not nil. All records are returned.
+			"select v from Customer where v.InvoiceNum is not nil or v.Name is not nil",
+			[]interface{}{
+				[]interface{}{customerRows[0].value},
+				[]interface{}{customerRows[1].value},
+				[]interface{}{customerRows[2].value},
+				[]interface{}{customerRows[3].value},
+				[]interface{}{customerRows[4].value},
+				[]interface{}{customerRows[5].value},
+				[]interface{}{customerRows[6].value},
+				[]interface{}{customerRows[7].value},
+				[]interface{}{customerRows[8].value},
+			},
+		},
+		{
+			// Select values where v.InvoiceNum is nil and v.Name is not nil.
+			// All customers are returned.
+			"select v from Customer where v.InvoiceNum is nil and v.Name is not nil",
+			[]interface{}{
+				[]interface{}{customerRows[0].value},
+				[]interface{}{customerRows[4].value},
+			},
+		},
+		{
+			// Select values where v.InvoiceNum is not nil
+			// and v.Name is not nil.  Expect nothing returned.
+			"select v from Customer where v.InvoiceNum is not nil and v.Name is not nil",
+			[]interface{}{},
+		},
+		{
 			// Select keys & values for all customer records.
 			"select k, v from Customer where t = \"Customer\"",
 			[]interface{}{
@@ -389,6 +468,18 @@
 			},
 		},
 		{
+			// Select records where v.Foo.FooBarBaz.Baz is 84 and v.InvoiceNum is not nil.
+			"select v from Customer where v.Foo.FooBarBaz.Baz = 84 and v.InvoiceNum is not nil",
+			[]interface{}{},
+		},
+		{
+			// Select records where v.Foo.FooBarBaz.Baz is 84 and v.InvoiceNum is nil.
+			"select v from Customer where v.Foo.FooBarBaz.Baz = 84 and v.InvoiceNum is nil",
+			[]interface{}{
+				[]interface{}{customerRows[4].value},
+			},
+		},
+		{
 			// Select customer name for customer ID (i.e., key) "001".
 			"select v.Name from Customer where t = \"Customer\" and k = \"001\"",
 			[]interface{}{
@@ -449,6 +540,22 @@
 				[]interface{}{customerRows[5].value},
 			},
 		},
+		{
+			// Select records where v.Foo.FooBarBaz.Baz is 84 or (type is Invoice and v.InvoiceNum is not nil).
+			// Limit 3 Offset 2
+			"select v from Customer where v.Foo.FooBarBaz.Baz = 84 or (t = \"Invoice\" and v.InvoiceNum is not nil) limit 3 offset 2",
+			[]interface{}{
+				[]interface{}{customerRows[3].value},
+				[]interface{}{customerRows[4].value},
+				[]interface{}{customerRows[5].value},
+			},
+		},
+		{
+			// Select records where v.Foo.FooBarBaz.Baz is 84 or (type is Invoice and v.InvoiceNum is nil).
+			// Limit 3 Offset 2
+			"select v from Customer where v.Foo.FooBarBaz.Baz = 84 or (t = \"Invoice\" and v.InvoiceNum is nil) limit 3 offset 2",
+			[]interface{}{},
+		},
 	}
 
 	for _, test := range basic {