syncbase: SyncQL: Support key expression operators: <,<=,>,>=,<>.

Rather than approximating key ranges (and, therefore, sometimes
iterating over more keys than necessary), this change computes
precise key ranges.  At the same time, operators in key
expressions can now include <, <=, >, >= and <> (in addition to =
and like that were already supported).

Change-Id: I1bb96400169988664ea934e6b746982b62bd0be7
diff --git a/v23/syncbase/nosql/internal/query/demo/db/db.go b/v23/syncbase/nosql/internal/query/demo/db/db.go
index 8a96f0b..9ec201a 100644
--- a/v23/syncbase/nosql/internal/query/demo/db/db.go
+++ b/v23/syncbase/nosql/internal/query/demo/db/db.go
@@ -48,16 +48,25 @@
 	rangeCursor int
 }
 
+func compareKeyToLimit(key, limit string) int {
+	if limit == "" || key < limit {
+		return -1
+	} else if key == limit {
+		return 0
+	} else {
+		return 1
+	}
+}
+
 func (kvs *keyValueStreamImpl) Advance() bool {
 	for true {
 		kvs.cursor++ // initialized to -1
 		if kvs.cursor >= len(kvs.table.rows) {
 			return false
 		}
+		// does it match any keyRange
 		for kvs.rangeCursor < len(kvs.keyRanges) {
-			// does it match any keyRange (or is the keyRange the 0-255 wildcard)?
-			if (kvs.keyRanges[kvs.rangeCursor].Start == string([]byte{0}) && kvs.keyRanges[kvs.rangeCursor].Limit == string([]byte{255})) ||
-				(kvs.table.rows[kvs.cursor].key >= kvs.keyRanges[kvs.rangeCursor].Start && kvs.table.rows[kvs.cursor].key <= kvs.keyRanges[kvs.rangeCursor].Limit) {
+			if kvs.table.rows[kvs.cursor].key >= kvs.keyRanges[kvs.rangeCursor].Start && compareKeyToLimit(kvs.table.rows[kvs.cursor].key, kvs.keyRanges[kvs.rangeCursor].Limit) < 0 {
 				return true
 			}
 			// Keys and keyRanges are both sorted low to high, so we can increment
diff --git a/v23/syncbase/nosql/internal/query/eval.go b/v23/syncbase/nosql/internal/query/eval.go
index 6aa7b0b..62e51c8 100644
--- a/v23/syncbase/nosql/internal/query/eval.go
+++ b/v23/syncbase/nosql/internal/query/eval.go
@@ -507,24 +507,6 @@
 	return object, false, ""
 }
 
-// Evaluate the where clause, substituting false for all expressions involving the key and
-// true for all other expressions.  If the answer is true, it is possible to satisfy the
-// expression for any key.  As such, all keys must be fetched.
-func CheckIfAllKeysMustBeFetched(e *query_parser.Expression) bool {
-	switch e.Operator.Type {
-	case query_parser.And:
-		return CheckIfAllKeysMustBeFetched(e.Operand1.Expr) && CheckIfAllKeysMustBeFetched(e.Operand2.Expr)
-	case query_parser.Or:
-		return CheckIfAllKeysMustBeFetched(e.Operand1.Expr) || CheckIfAllKeysMustBeFetched(e.Operand2.Expr)
-	default: // =, > >=, <, <=, Like, <>, NotLike
-		if query_checker.IsKey(e.Operand1) {
-			return false
-		} else {
-			return true
-		}
-	}
-}
-
 // EvalWhereUsingOnlyKey return type.  See that function for details.
 type EvalWithKeyResult int
 
diff --git a/v23/syncbase/nosql/internal/query/query.go b/v23/syncbase/nosql/internal/query/query.go
index bcbb3cf..736997c 100644
--- a/v23/syncbase/nosql/internal/query/query.go
+++ b/v23/syncbase/nosql/internal/query/query.go
@@ -65,21 +65,6 @@
 	return projection
 }
 
-// Given a query (i.e.,, select statement), return the key ranges needed to satisfy the query.
-// A return of a single element array of string([]byte{0}), string([]byte{255}) means fetch all.
-func CompileKeyRanges(w *query_parser.WhereClause) query_db.KeyRanges {
-	// First determine if every key needs to be fetched.  To do this, evaluate the
-	// where clause substituting false for every key expression and true for every
-	// other (type for value) expression.  If the where clause evaluates to true,
-	// it is possible for a row to be selected without any dependence on the contents
-	// of the key.  In that case, all keys must be fetched.
-	if w == nil || CheckIfAllKeysMustBeFetched(w.Expr) {
-		return query_db.KeyRanges{query_db.KeyRange{string([]byte{0}), string([]byte{255})}}
-	} else {
-		return query_checker.CompileKeyRanges(w)
-	}
-}
-
 // For testing purposes, given a SelectStatement, k and v;
 // return nil if row not selected, else return the projection (type []*vdl.Value).
 // Note: limit and offset clauses are ignored for this function as they make no sense
@@ -176,8 +161,7 @@
 }
 
 func execSelect(db query_db.Database, s *query_parser.SelectStatement) ([]string, ResultStream, error) {
-	keyRanges := CompileKeyRanges(s.Where)
-	keyValueStream, err := s.From.Table.DBTable.Scan(keyRanges)
+	keyValueStream, err := s.From.Table.DBTable.Scan(*query_checker.CompileKeyRanges(s.Where))
 	if err != nil {
 		return nil, nil, syncql.NewErrScanError(db.GetContext(), s.Off, err)
 	}
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 81bfea3..e2c6e1c 100644
--- a/v23/syncbase/nosql/internal/query/query_checker/query_checker.go
+++ b/v23/syncbase/nosql/internal/query/query_checker/query_checker.go
@@ -16,6 +16,14 @@
 	"v.io/syncbase/v23/syncbase/nosql/syncql"
 )
 
+const (
+	MaxRangeLimit = ""
+)
+
+var (
+	KeyRangeAll = query_db.KeyRange{"", MaxRangeLimit}
+)
+
 func Check(db query_db.Database, s *query_parser.Statement) error {
 	switch sel := (*s).(type) {
 	case query_parser.SelectStatement:
@@ -138,8 +146,17 @@
 		return syncql.NewErrTypeExpressionForm(db.GetContext(), e.Off)
 	}
 
-	// k as an operand must be the first operand, the operator must be like or = and the 2nd operand must be a string literal.
-	if (IsKey(e.Operand1) && ((e.Operator.Type != query_parser.Equal && e.Operator.Type != query_parser.Like) || e.Operand2.Type != query_parser.TypStr)) || IsKey(e.Operand2) {
+	// k as an operand must be the first operand, the operator must be
+	// = | <> | > | >= | < | <= | like and the 2nd operand must be a string literal.
+	if (IsKey(e.Operand1) &&
+		((e.Operator.Type != query_parser.Equal &&
+			e.Operator.Type != query_parser.GreaterThan &&
+			e.Operator.Type != query_parser.GreaterThanOrEqual &&
+			e.Operator.Type != query_parser.LessThan &&
+			e.Operator.Type != query_parser.LessThanOrEqual &&
+			e.Operator.Type != query_parser.Like &&
+			e.Operator.Type != query_parser.NotEqual) ||
+			e.Operand2.Type != query_parser.TypStr)) || IsKey(e.Operand2) {
 		return syncql.NewErrKeyExpressionForm(db.GetContext(), e.Off)
 	}
 
@@ -348,11 +365,11 @@
 	return o.Type == query_parser.TypExpr
 }
 
-// Function copied from syncbase.
 func computeKeyRangeForPrefix(prefix string) query_db.KeyRange {
 	if prefix == "" {
-		return query_db.KeyRange{string([]byte{0}), string([]byte{255})}
+		return KeyRangeAll
 	}
+	// Copied from syncbase.
 	limit := []byte(prefix)
 	for len(limit) > 0 {
 		if limit[len(limit)-1] == 255 {
@@ -375,28 +392,133 @@
 }
 
 // Compute a list of key ranges to be used by query_db's Table.Scan implementation.
-func CompileKeyRanges(where *query_parser.WhereClause) query_db.KeyRanges {
+func CompileKeyRanges(where *query_parser.WhereClause) *query_db.KeyRanges {
 	if where == nil {
-		return query_db.KeyRanges{computeKeyRangeForPrefix("")}
+		return &query_db.KeyRanges{KeyRangeAll}
 	}
-	var keyRanges query_db.KeyRanges
-	collectKeyRanges(where.Expr, &keyRanges)
+	return collectKeyRanges(where.Expr)
+}
+
+func computeIntersection(lhs, rhs query_db.KeyRange) *query_db.KeyRange {
+	// Detect if lhs.Start is contained within rhs or rhs.Start is contained within lhs.
+	if (lhs.Start >= rhs.Start && compareStartToLimit(lhs.Start, rhs.Limit) < 0) ||
+		(rhs.Start >= lhs.Start && compareStartToLimit(rhs.Start, lhs.Limit) < 0) {
+		var start, limit string
+		if lhs.Start < rhs.Start {
+			start = rhs.Start
+		} else {
+			start = lhs.Start
+		}
+		if compareLimits(lhs.Limit, rhs.Limit) < 0 {
+			limit = lhs.Limit
+		} else {
+			limit = rhs.Limit
+		}
+		return &query_db.KeyRange{start, limit}
+	}
+	return nil
+}
+
+func keyRangeIntersection(lhs, rhs *query_db.KeyRanges) *query_db.KeyRanges {
+	keyRanges := &query_db.KeyRanges{}
+	lCur, rCur := 0, 0
+	for lCur < len(*lhs) && rCur < len(*rhs) {
+		// Any intersection at current cursors?
+		if intersection := computeIntersection((*lhs)[lCur], (*rhs)[rCur]); intersection != nil {
+			// Add the intersection
+			addKeyRange(*intersection, keyRanges)
+		}
+		// increment the range with the lesser limit
+		c := compareLimits((*lhs)[lCur].Limit, (*rhs)[rCur].Limit)
+		switch c {
+		case -1:
+			lCur++
+		case 1:
+			rCur++
+		default:
+			lCur++
+			rCur++
+		}
+	}
 	return keyRanges
 }
 
-func collectKeyRanges(expr *query_parser.Expression, keyRanges *query_db.KeyRanges) {
-	if IsKey(expr.Operand1) {
-		if expr.Operator.Type == query_parser.Like {
-			addKeyRange(computeKeyRangeForPrefix(expr.Operand2.Prefix), keyRanges)
-		} else { // OpEqual
-			addKeyRange(computeKeyRangeForSingleValue(expr.Operand2.Str), keyRanges)
+func collectKeyRanges(expr *query_parser.Expression) *query_db.KeyRanges {
+	if IsExpr(expr.Operand1) { // then both operands must be expressions
+		lhsKeyRanges := collectKeyRanges(expr.Operand1.Expr)
+		rhsKeyRanges := collectKeyRanges(expr.Operand2.Expr)
+		if expr.Operator.Type == query_parser.And {
+			// intersection of lhsKeyRanges and rhsKeyRanges
+			return keyRangeIntersection(lhsKeyRanges, rhsKeyRanges)
+		} else { // or
+			// union of lhsKeyRanges and rhsKeyRanges
+			for _, rhsKeyRange := range *rhsKeyRanges {
+				addKeyRange(rhsKeyRange, lhsKeyRanges)
+			}
+			return lhsKeyRanges
 		}
+	} else if IsKey(expr.Operand1) {
+		switch expr.Operator.Type {
+		case query_parser.Equal:
+			return &query_db.KeyRanges{computeKeyRangeForSingleValue(expr.Operand2.Str)}
+		case query_parser.GreaterThan:
+			return &query_db.KeyRanges{query_db.KeyRange{string(append([]byte(expr.Operand2.Str), 0)), MaxRangeLimit}}
+		case query_parser.GreaterThanOrEqual:
+			return &query_db.KeyRanges{query_db.KeyRange{expr.Operand2.Str, MaxRangeLimit}}
+		case query_parser.Like:
+			return &query_db.KeyRanges{computeKeyRangeForPrefix(expr.Operand2.Prefix)}
+		case query_parser.LessThan:
+			return &query_db.KeyRanges{query_db.KeyRange{"", expr.Operand2.Str}}
+		case query_parser.LessThanOrEqual:
+			return &query_db.KeyRanges{query_db.KeyRange{"", string(append([]byte(expr.Operand2.Str), 0))}}
+		default: // case query_parser.NotEqual:
+			return &query_db.KeyRanges{
+				query_db.KeyRange{"", expr.Operand2.Str},
+				query_db.KeyRange{string(append([]byte(expr.Operand2.Str), 0)), MaxRangeLimit},
+			}
+		}
+	} else { // not a key compare, so it applies to the entire key range
+		return &query_db.KeyRanges{KeyRangeAll}
 	}
-	if IsExpr(expr.Operand1) {
-		collectKeyRanges(expr.Operand1.Expr, keyRanges)
+}
+
+// Helper function to compare start and limit byte arrays  taking into account that
+// MaxRangeLimit is actually []byte{}.
+func compareLimits(limitA, limitB string) int {
+	if limitA == limitB {
+		return 0
+	} else if limitA == MaxRangeLimit {
+		return 1
+	} else if limitB == MaxRangeLimit {
+		return -1
+	} else if limitA < limitB {
+		return -1
+	} else {
+		return 1
 	}
-	if IsExpr(expr.Operand2) {
-		collectKeyRanges(expr.Operand2.Expr, keyRanges)
+}
+
+func compareStartToLimit(startA, limitB string) int {
+	if limitB == MaxRangeLimit {
+		return -1
+	} else if startA == limitB {
+		return 0
+	} else if startA < limitB {
+		return -1
+	} else {
+		return 1
+	}
+}
+
+func compareLimitToStart(limitA, startB string) int {
+	if limitA == MaxRangeLimit {
+		return -1
+	} else if limitA == startB {
+		return 0
+	} else if limitA < startB {
+		return -1
+	} else {
+		return 1
 	}
 }
 
@@ -408,9 +530,10 @@
 		// the first paren expr is true if the start of the range to be added is contained in r
 		// the second paren expr is true if the limit of the range to be added is contained in r
 		// the third paren expr is true if the range to be added entirely contains r
-		if (keyRange.Start >= r.Start && keyRange.Start <= r.Limit) ||
-			(keyRange.Limit >= r.Start && keyRange.Limit <= r.Limit) ||
-			(keyRange.Start <= r.Start && keyRange.Limit >= r.Limit) {
+		if (keyRange.Start >= r.Start && compareStartToLimit(keyRange.Start, r.Limit) <= 0) ||
+			(compareLimitToStart(keyRange.Limit, r.Start) >= 0 && compareLimits(keyRange.Limit, r.Limit) <= 0) ||
+			(keyRange.Start <= r.Start && compareLimits(keyRange.Limit, r.Limit) >= 0) {
+
 			// keyRange overlaps with existing range at keyRanges[i]
 			// set newKeyRange to a range that ecompasses both
 			var newKeyRange query_db.KeyRange
@@ -419,7 +542,7 @@
 			} else {
 				newKeyRange.Start = r.Start
 			}
-			if keyRange.Limit > r.Limit {
+			if compareLimits(keyRange.Limit, r.Limit) > 0 {
 				newKeyRange.Limit = keyRange.Limit
 			} else {
 				newKeyRange.Limit = r.Limit
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 2ea1196..5537b48 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
@@ -68,7 +68,7 @@
 
 type keyRangesTest struct {
 	query     string
-	keyRanges query_db.KeyRanges
+	keyRanges *query_db.KeyRanges
 }
 
 type regularExpressionsTest struct {
@@ -132,33 +132,97 @@
 	basic := []keyRangesTest{
 		{
 			"select k, v from Customer",
-			query_db.KeyRanges{
-				query_db.KeyRange{string([]byte{0}), string([]byte{255})},
+			&query_db.KeyRanges{
+				query_db.KeyRange{"", ""},
 			},
 		},
 		{
+			"select k, v from Customer where k = \"abc\" or k = \"def\"",
+			&query_db.KeyRanges{
+				query_db.KeyRange{"abc", appendZeroByte("abc")},
+				query_db.KeyRange{"def", appendZeroByte("def")},
+			},
+		},
+		{
+			"select k, v from Customer where k >= \"foo\" and k < \"goo\"",
+			&query_db.KeyRanges{
+				query_db.KeyRange{"foo", "goo"},
+			},
+		},
+		{
+			"select k, v from Customer where k >= \"foo\" and k <= \"goo\"",
+			&query_db.KeyRanges{
+				query_db.KeyRange{"foo", appendZeroByte("goo")},
+			},
+		},
+		{
+			"select k, v from Customer where k <> \"foo\"",
+			&query_db.KeyRanges{
+				query_db.KeyRange{"", "foo"},
+				query_db.KeyRange{appendZeroByte("foo"), ""},
+			},
+		},
+		{
+			"select k, v from Customer where k <> \"foo\" and k > \"bar\"",
+			&query_db.KeyRanges{
+				query_db.KeyRange{appendZeroByte("bar"), "foo"},
+				query_db.KeyRange{appendZeroByte("foo"), ""},
+			},
+		},
+		{
+			"select k, v from Customer where k <> \"foo\" or k > \"bar\"",
+			&query_db.KeyRanges{
+				query_db.KeyRange{"", ""},
+			},
+		},
+		{
+			"select k, v from Customer where k <> \"bar\" or k > \"foo\"",
+			&query_db.KeyRanges{
+				query_db.KeyRange{"", "bar"},
+				query_db.KeyRange{appendZeroByte("bar"), ""},
+			},
+		},
+		{
+			"select v from Customer where t = \"Foo.Bar\" and k >= \"100\" and k < \"200\" and v.foo > 50 and v.bar <= 1000 and v.baz <> -20.7",
+			&query_db.KeyRanges{
+				query_db.KeyRange{"100", "200"},
+			},
+		},
+		{
+			"select k, v from Customer where k = \"abc\" and k = \"def\"",
+			&query_db.KeyRanges{},
+		},
+		{
 			"select k, v from Customer where t = \"Foo.Bar\" and k like \"abc%\" limit 100 offset 200",
-			query_db.KeyRanges{
+			&query_db.KeyRanges{
 				query_db.KeyRange{"abc", "abd"},
 			},
 		},
 		{
 			"select  k,  v from \n  Customer where k like \"002%\" or k like \"001%\" or k like \"%\"",
-			query_db.KeyRanges{
-				query_db.KeyRange{string([]byte{0}), string([]byte{255})},
+			&query_db.KeyRanges{
+				query_db.KeyRange{"", ""},
 			},
 		},
 		{
 			"select k, v from Customer where k = \"Foo.Bar\" and k like \"abc%\" limit 100 offset 200",
-			query_db.KeyRanges{
-				query_db.KeyRange{"Foo.Bar", appendZeroByte("Foo.Bar")},
-				query_db.KeyRange{"abc", "abd"},
+			&query_db.KeyRanges{},
+		},
+		{
+			"select k, v from Customer where k like \"foo%\" and k like \"bar%\"",
+			&query_db.KeyRanges{},
+		},
+		{
+			"select k, v from Customer where k like \"foo%\" or k like \"bar%\"",
+			&query_db.KeyRanges{
+				query_db.KeyRange{"bar", "bas"},
+				query_db.KeyRange{"foo", "fop"},
 			},
 		},
 		{
 			// Note: 'like "Foo"' is optimized to '= "Foo"
 			"select k, v from Customer where k = \"Foo.Bar\" or k like \"Foo\" or k like \"abc%\" limit 100 offset 200",
-			query_db.KeyRanges{
+			&query_db.KeyRanges{
 				query_db.KeyRange{"Foo", appendZeroByte("Foo")},
 				query_db.KeyRange{"Foo.Bar", appendZeroByte("Foo.Bar")},
 				query_db.KeyRange{"abc", "abd"},
@@ -166,21 +230,21 @@
 		},
 		{
 			"select k, v from Customer where k like \"Foo\\%Bar\" or k like \"abc%\" limit 100 offset 200",
-			query_db.KeyRanges{
+			&query_db.KeyRanges{
 				query_db.KeyRange{"Foo%Bar", appendZeroByte("Foo%Bar")},
 				query_db.KeyRange{"abc", "abd"},
 			},
 		},
 		{
 			"select k, v from Customer where k like \"Foo\\\\%Bar\" or k like \"abc%\" limit 100 offset 200",
-			query_db.KeyRanges{
+			&query_db.KeyRanges{
 				query_db.KeyRange{"Foo\\", "Foo]"},
 				query_db.KeyRange{"abc", "abd"},
 			},
 		},
 		{
 			"select k, v from Customer where k like \"Foo\\\\\\%Bar\" or k like \"abc%\" limit 100 offset 200",
-			query_db.KeyRanges{
+			&query_db.KeyRanges{
 				query_db.KeyRange{"Foo\\%Bar", appendZeroByte("Foo\\%Bar")},
 				query_db.KeyRange{"abc", "abd"},
 			},
@@ -310,8 +374,8 @@
 		{"select v.z from Customer where k > v.y", syncql.NewErrKeyExpressionForm(db.GetContext(), 31)},
 		{"select v.z from Customer where k >= v.y", syncql.NewErrKeyExpressionForm(db.GetContext(), 31)},
 		{"select v.z from Customer where \"abc%\" = k", syncql.NewErrKeyExpressionForm(db.GetContext(), 31)},
-		{"select v from Customer where t = \"Foo.Bar\" and k >= \"100\" and k < \"200\" and v.foo > 50 and v.bar <= 1000 and v.baz <> -20.7", syncql.NewErrKeyExpressionForm(db.GetContext(), 47)},
 		{"select v.z from Customer where k like \"a\\bc%\"", syncql.NewErrInvalidEscapedChar(db.GetContext(), 38)},
+		{"select v.z from Customer where k not like \"foo\"", syncql.NewErrKeyExpressionForm(db.GetContext(), 31)},
 		{"select v from Customer where v.A > false", syncql.NewErrBoolInvalidExpression(db.GetContext(), 33)},
 		{"select v from Customer where true <= v.A", syncql.NewErrBoolInvalidExpression(db.GetContext(), 34)},
 		{"select v from Customer where Foo(\"2015/07/22\", true, 3.14157) = true", syncql.NewErrFunctionNotFound(db.GetContext(), 29, "Foo")},
diff --git a/v23/syncbase/nosql/internal/query/test/query_test.go b/v23/syncbase/nosql/internal/query/test/query_test.go
index 00498a6..a3d72ab 100644
--- a/v23/syncbase/nosql/internal/query/test/query_test.go
+++ b/v23/syncbase/nosql/internal/query/test/query_test.go
@@ -41,15 +41,25 @@
 	keyRangesCursor int
 }
 
+func compareKeyToLimit(key, limit string) int {
+	if limit == "" || key < limit {
+		return -1
+	} else if key == limit {
+		return 0
+	} else {
+		return 1
+	}
+}
+
 func (kvs *keyValueStreamImpl) Advance() bool {
 	for true {
 		kvs.cursor++ // initialized to -1
 		if kvs.cursor >= len(kvs.table.rows) {
 			return false
 		}
+		// does it match any keyRange
 		for kvs.keyRangesCursor < len(kvs.keyRanges) {
-			// does it match any keyRange (or is the keyRange the 0-255 wildcard)?
-			if (kvs.keyRanges[kvs.keyRangesCursor].Start == string([]byte{0}) && kvs.keyRanges[kvs.keyRangesCursor].Limit == string([]byte{255})) || (kvs.table.rows[kvs.cursor].key >= kvs.keyRanges[kvs.keyRangesCursor].Start && kvs.table.rows[kvs.cursor].key <= kvs.keyRanges[kvs.keyRangesCursor].Limit) {
+			if kvs.table.rows[kvs.cursor].key >= kvs.keyRanges[kvs.keyRangesCursor].Start && compareKeyToLimit(kvs.table.rows[kvs.cursor].key, kvs.keyRanges[kvs.keyRangesCursor].Limit) < 0 {
 				return true
 			}
 			// Keys and keyRanges are both sorted low to high, so we can increment
@@ -222,7 +232,7 @@
 
 type keyRangesTest struct {
 	query     string
-	keyRanges query_db.KeyRanges
+	keyRanges *query_db.KeyRanges
 	err       error
 }
 
@@ -545,6 +555,93 @@
 			},
 		},
 		{
+			"select k from Customer where k >= \"002001\" and k <= \"002002\"",
+			[]string{"k"},
+			[][]*vdl.Value{
+				[]*vdl.Value{vdl.ValueOf("002001")},
+				[]*vdl.Value{vdl.ValueOf("002002")},
+			},
+		},
+		{
+			"select k from Customer where k > \"002001\" and k <= \"002002\"",
+			[]string{"k"},
+			[][]*vdl.Value{
+				[]*vdl.Value{vdl.ValueOf("002002")},
+			},
+		},
+		{
+			"select k from Customer where k > \"002001\" and k < \"002002\"",
+			[]string{"k"},
+			[][]*vdl.Value{},
+		},
+		{
+			"select k from Customer where k > \"002001\" or k < \"002002\"",
+			[]string{"k"},
+			[][]*vdl.Value{
+				[]*vdl.Value{vdl.ValueOf("001")},
+				[]*vdl.Value{vdl.ValueOf("001001")},
+				[]*vdl.Value{vdl.ValueOf("001002")},
+				[]*vdl.Value{vdl.ValueOf("001003")},
+				[]*vdl.Value{vdl.ValueOf("002")},
+				[]*vdl.Value{vdl.ValueOf("002001")},
+				[]*vdl.Value{vdl.ValueOf("002002")},
+				[]*vdl.Value{vdl.ValueOf("002003")},
+				[]*vdl.Value{vdl.ValueOf("002004")},
+			},
+		},
+		{
+			"select k from Customer where k <> \"002\"",
+			[]string{"k"},
+			[][]*vdl.Value{
+				[]*vdl.Value{vdl.ValueOf("001")},
+				[]*vdl.Value{vdl.ValueOf("001001")},
+				[]*vdl.Value{vdl.ValueOf("001002")},
+				[]*vdl.Value{vdl.ValueOf("001003")},
+				[]*vdl.Value{vdl.ValueOf("002001")},
+				[]*vdl.Value{vdl.ValueOf("002002")},
+				[]*vdl.Value{vdl.ValueOf("002003")},
+				[]*vdl.Value{vdl.ValueOf("002004")},
+			},
+		},
+		{
+			"select k from Customer where k <> \"002\" or k like \"002\"",
+			[]string{"k"},
+			[][]*vdl.Value{
+				[]*vdl.Value{vdl.ValueOf("001")},
+				[]*vdl.Value{vdl.ValueOf("001001")},
+				[]*vdl.Value{vdl.ValueOf("001002")},
+				[]*vdl.Value{vdl.ValueOf("001003")},
+				[]*vdl.Value{vdl.ValueOf("002")},
+				[]*vdl.Value{vdl.ValueOf("002001")},
+				[]*vdl.Value{vdl.ValueOf("002002")},
+				[]*vdl.Value{vdl.ValueOf("002003")},
+				[]*vdl.Value{vdl.ValueOf("002004")},
+			},
+		},
+		{
+			"select k from Customer where k <> \"002\" and k like \"002\"",
+			[]string{"k"},
+			[][]*vdl.Value{},
+		},
+		{
+			"select k from Customer where k <> \"002\" and k like \"002%\"",
+			[]string{"k"},
+			[][]*vdl.Value{
+				[]*vdl.Value{vdl.ValueOf("002001")},
+				[]*vdl.Value{vdl.ValueOf("002002")},
+				[]*vdl.Value{vdl.ValueOf("002003")},
+				[]*vdl.Value{vdl.ValueOf("002004")},
+			},
+		},
+		{
+			"select k from Customer where k <> \"002\" and k like \"%002\"",
+			[]string{"k"},
+			[][]*vdl.Value{
+				[]*vdl.Value{vdl.ValueOf("001002")},
+				[]*vdl.Value{vdl.ValueOf("002002")},
+			},
+		},
+		{
 			// Select records where v.Address.City is "Collins" and v.InvoiceNum is not nil.
 			"select v from Customer where v.Address.City = \"Collins\" and v.InvoiceNum is not nil",
 			[]string{"v"},
@@ -1007,40 +1104,46 @@
 		{
 			// Need all keys
 			"select k, v from Customer",
-			query_db.KeyRanges{
-				query_db.KeyRange{string([]byte{0}), string([]byte{255})},
+			&query_db.KeyRanges{
+				query_db.KeyRange{"", ""},
 			},
 			nil,
 		},
 		{
-			// Keys 001 and 003
+			// Keys 001 or 003
 			"   select  k,  v from Customer where k = \"001\" or k = \"003\"",
-			query_db.KeyRanges{
+			&query_db.KeyRanges{
 				query_db.KeyRange{"001", appendZeroByte("001")},
 				query_db.KeyRange{"003", appendZeroByte("003")},
 			},
 			nil,
 		},
 		{
+			// Keys 001 and 003 (resulting in no keys)
+			"   select  k,  v from Customer where k = \"001\" and k = \"003\"",
+			&query_db.KeyRanges{},
+			nil,
+		},
+		{
 			// Need all keys
 			"select  k,  v from Customer where k like \"%\" or k like \"001%\" or k like \"002%\"",
-			query_db.KeyRanges{
-				query_db.KeyRange{string([]byte{0}), string([]byte{255})},
+			&query_db.KeyRanges{
+				query_db.KeyRange{"", ""},
 			},
 			nil,
 		},
 		{
 			// Need all keys, likes in where clause in different order
 			"select  k,  v from Customer where k like \"002%\" or k like \"001%\" or k like \"%\"",
-			query_db.KeyRanges{
-				query_db.KeyRange{string([]byte{0}), string([]byte{255})},
+			&query_db.KeyRanges{
+				query_db.KeyRange{"", ""},
 			},
 			nil,
 		},
 		{
 			// All selected rows will have key prefix of "abc".
 			"select k, v from Customer where t = \"Foo.Bar\" and k like \"abc%\"",
-			query_db.KeyRanges{
+			&query_db.KeyRanges{
 				query_db.KeyRange{"abc", plusOne("abc")},
 			},
 			nil,
@@ -1048,23 +1151,23 @@
 		{
 			// Need all keys
 			"select k, v from Customer where t = \"Foo.Bar\" or k like \"abc%\"",
-			query_db.KeyRanges{
-				query_db.KeyRange{string([]byte{0}), string([]byte{255})},
+			&query_db.KeyRanges{
+				query_db.KeyRange{"", ""},
 			},
 			nil,
 		},
 		{
 			// Need all keys
 			"select k, v from Customer where k like \"abc%\" or v.zip = \"94303\"",
-			query_db.KeyRanges{
-				query_db.KeyRange{string([]byte{0}), string([]byte{255})},
+			&query_db.KeyRanges{
+				query_db.KeyRange{"", ""},
 			},
 			nil,
 		},
 		{
 			// All selected rows will have key prefix of "foo".
 			"select k, v from Customer where t = \"Foo.Bar\" and k like \"foo_bar\"",
-			query_db.KeyRanges{
+			&query_db.KeyRanges{
 				query_db.KeyRange{"foo", plusOne("foo")},
 			},
 			nil,
@@ -1072,7 +1175,7 @@
 		{
 			// All selected rows will have key == "baz" or prefix of "foo".
 			"select k, v from Customer where k like \"foo_bar\" or k = \"baz\"",
-			query_db.KeyRanges{
+			&query_db.KeyRanges{
 				query_db.KeyRange{"baz", appendZeroByte("baz")},
 				query_db.KeyRange{"foo", plusOne("foo")},
 			},
@@ -1081,7 +1184,7 @@
 		{
 			// All selected rows will have key == "fo" or prefix of "foo".
 			"select k, v from Customer where k like \"foo_bar\" or k = \"fo\"",
-			query_db.KeyRanges{
+			&query_db.KeyRanges{
 				query_db.KeyRange{"fo", appendZeroByte("fo")},
 				query_db.KeyRange{"foo", plusOne("foo")},
 			},
@@ -1091,7 +1194,7 @@
 			// All selected rows will have prefix of "fo".
 			// k == foo is a subset of above prefix
 			"select k, v from Customer where k like \"fo_bar\" or k = \"foo\"",
-			query_db.KeyRanges{
+			&query_db.KeyRanges{
 				query_db.KeyRange{"fo", plusOne("fo")},
 			},
 			nil,
@@ -1099,7 +1202,7 @@
 		{
 			// All selected rows will have key prefix of "foo".
 			"select k, v from Customer where k like \"foo%bar\"",
-			query_db.KeyRanges{
+			&query_db.KeyRanges{
 				query_db.KeyRange{"foo", plusOne("foo")},
 			},
 			nil,
@@ -1107,7 +1210,7 @@
 		{
 			// Select "foo\bar" row.
 			"select k, v from Customer where k like \"foo\\\\bar\"",
-			query_db.KeyRanges{
+			&query_db.KeyRanges{
 				query_db.KeyRange{"foo\\bar", appendZeroByte("foo\\bar")},
 			},
 			nil,
@@ -1115,7 +1218,7 @@
 		{
 			// Select "foo%bar" row.
 			"select k, v from Customer where k like \"foo\\%bar\"",
-			query_db.KeyRanges{
+			&query_db.KeyRanges{
 				query_db.KeyRange{"foo%bar", appendZeroByte("foo%bar")},
 			},
 			nil,
@@ -1123,7 +1226,7 @@
 		{
 			// Select "foo\%bar" row.
 			"select k, v from Customer where k like \"foo\\\\\\%bar\"",
-			query_db.KeyRanges{
+			&query_db.KeyRanges{
 				query_db.KeyRange{"foo\\%bar", appendZeroByte("foo\\%bar")},
 			},
 			nil,
@@ -1131,23 +1234,23 @@
 		{
 			// Need all keys
 			"select k, v from Customer where k like \"%foo\"",
-			query_db.KeyRanges{
-				query_db.KeyRange{string([]byte{0}), string([]byte{255})},
+			&query_db.KeyRanges{
+				query_db.KeyRange{"", ""},
 			},
 			nil,
 		},
 		{
 			// Need all keys
 			"select k, v from Customer where k like \"_foo\"",
-			query_db.KeyRanges{
-				query_db.KeyRange{string([]byte{0}), string([]byte{255})},
+			&query_db.KeyRanges{
+				query_db.KeyRange{"", ""},
 			},
 			nil,
 		},
 		{
 			// Select "foo_bar" row.
 			"select k, v from Customer where k like \"foo\\_bar\"",
-			query_db.KeyRanges{
+			&query_db.KeyRanges{
 				query_db.KeyRange{"foo_bar", appendZeroByte("foo_bar")},
 			},
 			nil,
@@ -1155,7 +1258,7 @@
 		{
 			// Select "foobar%" row.
 			"select k, v from Customer where k like \"foobar\\%\"",
-			query_db.KeyRanges{
+			&query_db.KeyRanges{
 				query_db.KeyRange{"foobar%", appendZeroByte("foobar%")},
 			},
 			nil,
@@ -1163,7 +1266,7 @@
 		{
 			// Select "foobar_" row.
 			"select k, v from Customer where k like \"foobar\\_\"",
-			query_db.KeyRanges{
+			&query_db.KeyRanges{
 				query_db.KeyRange{"foobar_", appendZeroByte("foobar_")},
 			},
 			nil,
@@ -1171,7 +1274,7 @@
 		{
 			// Select "\%_" row.
 			"select k, v from Customer where k like \"\\\\\\%\\_\"",
-			query_db.KeyRanges{
+			&query_db.KeyRanges{
 				query_db.KeyRange{"\\%_", appendZeroByte("\\%_")},
 			},
 			nil,
@@ -1179,7 +1282,7 @@
 		{
 			// Select "%_abc\" row.
 			"select k, v from Customer where k = \"%_abc\\\"",
-			query_db.KeyRanges{
+			&query_db.KeyRanges{
 				query_db.KeyRange{"%_abc\\", appendZeroByte("%_abc\\")},
 			},
 			nil,
@@ -1199,7 +1302,7 @@
 			if semErr == nil {
 				switch sel := (*s).(type) {
 				case query_parser.SelectStatement:
-					keyRanges := query.CompileKeyRanges(sel.Where)
+					keyRanges := query_checker.CompileKeyRanges(sel.Where)
 					if !reflect.DeepEqual(test.keyRanges, keyRanges) {
 						t.Errorf("query: %s;\nGOT  %v\nWANT %v", test.query, keyRanges, test.keyRanges)
 					}
diff --git a/v23/syncbase/nosql/syncql/syncql.vdl b/v23/syncbase/nosql/syncql/syncql.vdl
index e8a3053..aeab6b4 100644
--- a/v23/syncbase/nosql/syncql/syncql.vdl
+++ b/v23/syncbase/nosql/syncql/syncql.vdl
@@ -71,7 +71,7 @@
 		"en": "[{off}]Select field must be 'k' or 'v[{.<ident>}...]'.",
 	}
 	KeyExpressionForm(off int64) {
-		"en": "[{off}]Key (i.e., 'k') expressions must be of form 'k like|= <string-literal>'.",
+		"en": "[{off}]Key (i.e., 'k') expressions must be of form 'k  [=|<>|>|>=|<|<=|like] <string-literal>'.",
 	}
 	KeyValueStreamError(off int64, err error) {
 		"en": "[{off}]KeyValueStream error: {err}.",
diff --git a/v23/syncbase/nosql/syncql/syncql.vdl.go b/v23/syncbase/nosql/syncql/syncql.vdl.go
index d227fbb..cbe6639 100644
--- a/v23/syncbase/nosql/syncql/syncql.vdl.go
+++ b/v23/syncbase/nosql/syncql/syncql.vdl.go
@@ -36,7 +36,7 @@
 	ErrIsIsNotRequireRhsNil            = verror.Register("v.io/syncbase/v23/syncbase/nosql/syncql.IsIsNotRequireRhsNil", verror.NoRetry, "{1:}{2:} [{3}]'Is/is not' expressions require right operand to be nil.")
 	ErrInvalidEscapedChar              = verror.Register("v.io/syncbase/v23/syncbase/nosql/syncql.InvalidEscapedChar", verror.NoRetry, "{1:}{2:} [{3}Expected backslash, percent, or underscore after backslash.]")
 	ErrInvalidSelectField              = verror.Register("v.io/syncbase/v23/syncbase/nosql/syncql.InvalidSelectField", verror.NoRetry, "{1:}{2:} [{3}]Select field must be 'k' or 'v[{.<ident>}...]'.")
-	ErrKeyExpressionForm               = verror.Register("v.io/syncbase/v23/syncbase/nosql/syncql.KeyExpressionForm", verror.NoRetry, "{1:}{2:} [{3}]Key (i.e., 'k') expressions must be of form 'k like|= <string-literal>'.")
+	ErrKeyExpressionForm               = verror.Register("v.io/syncbase/v23/syncbase/nosql/syncql.KeyExpressionForm", verror.NoRetry, "{1:}{2:} [{3}]Key (i.e., 'k') expressions must be of form 'k  [=|<>|>|>=|<|<=|like] <string-literal>'.")
 	ErrKeyValueStreamError             = verror.Register("v.io/syncbase/v23/syncbase/nosql/syncql.KeyValueStreamError", verror.NoRetry, "{1:}{2:} [{3}]KeyValueStream error: {4}.")
 	ErrLikeExpressionsRequireRhsString = verror.Register("v.io/syncbase/v23/syncbase/nosql/syncql.LikeExpressionsRequireRhsString", verror.NoRetry, "{1:}{2:} [{3}]Like expressions require right operand of type <string-literal>.")
 	ErrLimitMustBeGe0                  = verror.Register("v.io/syncbase/v23/syncbase/nosql/syncql.LimitMustBeGe0", verror.NoRetry, "{1:}{2:} [{3}]Limit must be > 0.")
@@ -72,7 +72,7 @@
 	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrIsIsNotRequireRhsNil.ID), "{1:}{2:} [{3}]'Is/is not' expressions require right operand to be nil.")
 	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrInvalidEscapedChar.ID), "{1:}{2:} [{3}Expected backslash, percent, or underscore after backslash.]")
 	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrInvalidSelectField.ID), "{1:}{2:} [{3}]Select field must be 'k' or 'v[{.<ident>}...]'.")
-	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrKeyExpressionForm.ID), "{1:}{2:} [{3}]Key (i.e., 'k') expressions must be of form 'k like|= <string-literal>'.")
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrKeyExpressionForm.ID), "{1:}{2:} [{3}]Key (i.e., 'k') expressions must be of form 'k  [=|<>|>|>=|<|<=|like] <string-literal>'.")
 	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrKeyValueStreamError.ID), "{1:}{2:} [{3}]KeyValueStream error: {4}.")
 	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrLikeExpressionsRequireRhsString.ID), "{1:}{2:} [{3}]Like expressions require right operand of type <string-literal>.")
 	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrLimitMustBeGe0.ID), "{1:}{2:} [{3}]Limit must be > 0.")
diff --git a/x/ref/services/syncbase/server/nosql/database.go b/x/ref/services/syncbase/server/nosql/database.go
index dd661ef..c78761a 100644
--- a/x/ref/services/syncbase/server/nosql/database.go
+++ b/x/ref/services/syncbase/server/nosql/database.go
@@ -406,30 +406,21 @@
 func (t *tableDb) Scan(keyRanges query_db.KeyRanges) (query_db.KeyValueStream, error) {
 	streams := []store.Stream{}
 	for _, keyRange := range keyRanges {
-		start := keyRange.Start
-		limit := keyRange.Limit
-		// 0-255 means examine all rows
-		if start == string([]byte{0}) && limit == string([]byte{255}) {
-			start = ""
-			limit = ""
-		}
 		// TODO(jkline): For now, acquire all of the streams at once to minimize the race condition.
 		//               Need a way to Scan multiple ranges at the same state of uncommitted changes.
-		streams = append(streams, t.qdb.st.Scan(util.ScanRangeArgs(util.JoinKeyParts(util.RowPrefix, t.req.name), start, limit)))
+		streams = append(streams, t.qdb.st.Scan(util.ScanRangeArgs(util.JoinKeyParts(util.RowPrefix, t.req.name), keyRange.Start, keyRange.Limit)))
 	}
 	return &kvs{
-		t:         t,
-		keyRanges: keyRanges,
-		curr:      0,
-		validRow:  false,
-		it:        streams,
-		err:       nil,
+		t:        t,
+		curr:     0,
+		validRow: false,
+		it:       streams,
+		err:      nil,
 	}, nil
 }
 
 type kvs struct {
 	t         *tableDb
-	keyRanges query_db.KeyRanges
 	curr      int
 	validRow  bool
 	currKey   string
@@ -442,7 +433,7 @@
 	if s.err != nil {
 		return false
 	}
-	for s.curr < len(s.keyRanges) {
+	for s.curr < len(s.it) {
 		if s.it[s.curr].Advance() {
 			// key
 			keyBytes := s.it[s.curr].Key(nil)
@@ -497,7 +488,7 @@
 		s.it = nil
 	}
 	// set curr to end of keyRanges so Advance will return false
-	s.curr = len(s.keyRanges)
+	s.curr = len(s.it)
 }
 
 ////////////////////////////////////////