syncbase: syncQL: support k not like "<wildcard-string>"

As pointed out by Ivan, since k(ey) <> "<string>" expressions
are now supported, there isn't a good reason to exclude k not
like expressions.

This change adds the support.  Every k/v pair except the range
implied by the wildcard expression will be considered for
inclusion.  Also, if the wildcard string contains no wildcards,
the expression is optimized to a k <> expression.

There are two bugfixes included in this change.  There was a bug
in the resultstream implementation of the test in query/test.
There was also a bug in the actual syncbase implementation of
the same resultstream.

Change-Id: If44650509fc8f570c58f55424cba524c9dcd1220
diff --git a/v23/syncbase/nosql/exec_test/exec_test.go b/v23/syncbase/nosql/exec_test/exec_test.go
index 59ffde4..a826e08 100644
--- a/v23/syncbase/nosql/exec_test/exec_test.go
+++ b/v23/syncbase/nosql/exec_test/exec_test.go
@@ -142,6 +142,13 @@
 		t.Fatalf("customerTable.Put() failed: %v", err)
 	}
 
+	k = "003"
+	c = Customer{"John Steed", 3, true, AddressInfo{"100 Queen St.", "New London", "CT", "06320"}, CreditReport{Agency: CreditAgencyExperian, Report: AgencyReportExperianReport{ExperianCreditReport{ExperianRatingGood}}}}
+	customerEntries = append(customerEntries, kv{k, vdl.ValueOf(c)})
+	if err := customerTable.Put(ctx, k, c); err != nil {
+		t.Fatalf("customerTable.Put() failed: %v", err)
+	}
+
 	k = "001"
 	n := Numbers{byte(12), uint16(1234), uint32(5678), uint64(999888777666), int16(9876), int32(876543), int64(128), float32(3.14159), float64(2.71828182846), complex64(123.0 + 7.0i), complex128(456.789 + 10.1112i)}
 	numbersEntries = append(numbersEntries, kv{k, vdl.ValueOf(n)})
@@ -217,6 +224,7 @@
 			[][]*vdl.Value{
 				[]*vdl.Value{customerEntries[0].value},
 				[]*vdl.Value{customerEntries[4].value},
+				[]*vdl.Value{customerEntries[9].value},
 			},
 		},
 		{
@@ -228,6 +236,7 @@
 			[][]*vdl.Value{
 				[]*vdl.Value{customerEntries[0].value},
 				[]*vdl.Value{customerEntries[4].value},
+				[]*vdl.Value{customerEntries[9].value},
 			},
 		},
 		{
@@ -247,6 +256,7 @@
 				[]*vdl.Value{customerEntries[6].value},
 				[]*vdl.Value{customerEntries[7].value},
 				[]*vdl.Value{customerEntries[8].value},
+				[]*vdl.Value{customerEntries[9].value},
 			},
 		},
 		{
@@ -286,6 +296,7 @@
 				[]*vdl.Value{customerEntries[6].value},
 				[]*vdl.Value{customerEntries[7].value},
 				[]*vdl.Value{customerEntries[8].value},
+				[]*vdl.Value{customerEntries[9].value},
 			},
 		},
 		{
@@ -296,6 +307,7 @@
 			[][]*vdl.Value{
 				[]*vdl.Value{customerEntries[0].value},
 				[]*vdl.Value{customerEntries[4].value},
+				[]*vdl.Value{customerEntries[9].value},
 			},
 		},
 		{
@@ -312,6 +324,7 @@
 			[][]*vdl.Value{
 				[]*vdl.Value{vdl.ValueOf(customerEntries[0].key), customerEntries[0].value},
 				[]*vdl.Value{vdl.ValueOf(customerEntries[4].key), customerEntries[4].value},
+				[]*vdl.Value{vdl.ValueOf(customerEntries[9].key), customerEntries[9].value},
 			},
 		},
 		{
@@ -321,6 +334,7 @@
 			[][]*vdl.Value{
 				[]*vdl.Value{vdl.ValueOf(customerEntries[0].key), vdl.ValueOf("John Smith")},
 				[]*vdl.Value{vdl.ValueOf(customerEntries[4].key), vdl.ValueOf("Bat Masterson")},
+				[]*vdl.Value{vdl.ValueOf(customerEntries[9].key), vdl.ValueOf("John Steed")},
 			},
 		},
 		{
@@ -339,6 +353,7 @@
 				[]*vdl.Value{vdl.ValueOf(nil), vdl.ValueOf(int64(2))},
 				[]*vdl.Value{vdl.ValueOf(nil), vdl.ValueOf(int64(2))},
 				[]*vdl.Value{vdl.ValueOf(nil), vdl.ValueOf(int64(2))},
+				[]*vdl.Value{vdl.ValueOf(int64(3)), vdl.ValueOf(nil)},
 			},
 		},
 		{
@@ -376,7 +391,7 @@
 			},
 		},
 		{
-			// Select keys & values for all records with a key prefix of "001".
+			// Select keys & values for all records with a key prefix of "002".
 			"select k, v from Customer where k like \"002%\"",
 			[]string{"k", "v"},
 			[][]*vdl.Value{
@@ -388,6 +403,35 @@
 			},
 		},
 		{
+			// Select keys & values for all records with NOT key prefix "002%".
+			"select k, v from Customer where k not like \"002%\"",
+			[]string{"k", "v"},
+			[][]*vdl.Value{
+				[]*vdl.Value{vdl.ValueOf(customerEntries[0].key), customerEntries[0].value},
+				[]*vdl.Value{vdl.ValueOf(customerEntries[1].key), customerEntries[1].value},
+				[]*vdl.Value{vdl.ValueOf(customerEntries[2].key), customerEntries[2].value},
+				[]*vdl.Value{vdl.ValueOf(customerEntries[3].key), customerEntries[3].value},
+				[]*vdl.Value{vdl.ValueOf(customerEntries[9].key), customerEntries[9].value},
+			},
+		},
+		{
+			// Select keys & values for all records with NOT key prefix "002".
+			// Will be optimized to k <> "002"
+			"select k, v from Customer where k not like \"002\"",
+			[]string{"k", "v"},
+			[][]*vdl.Value{
+				[]*vdl.Value{vdl.ValueOf(customerEntries[0].key), customerEntries[0].value},
+				[]*vdl.Value{vdl.ValueOf(customerEntries[1].key), customerEntries[1].value},
+				[]*vdl.Value{vdl.ValueOf(customerEntries[2].key), customerEntries[2].value},
+				[]*vdl.Value{vdl.ValueOf(customerEntries[3].key), customerEntries[3].value},
+				[]*vdl.Value{vdl.ValueOf(customerEntries[5].key), customerEntries[5].value},
+				[]*vdl.Value{vdl.ValueOf(customerEntries[6].key), customerEntries[6].value},
+				[]*vdl.Value{vdl.ValueOf(customerEntries[7].key), customerEntries[7].value},
+				[]*vdl.Value{vdl.ValueOf(customerEntries[8].key), customerEntries[8].value},
+				[]*vdl.Value{vdl.ValueOf(customerEntries[9].key), customerEntries[9].value},
+			},
+		},
+		{
 			// Select keys & values for all records with a key prefix of "001".
 			// or a key prefix of "002".
 			"select k, v from Customer where k like \"001%\" or k like \"002%\"",
@@ -451,6 +495,7 @@
 				[]*vdl.Value{vdl.ValueOf(customerEntries[6].key), customerEntries[6].value},
 				[]*vdl.Value{vdl.ValueOf(customerEntries[7].key), customerEntries[7].value},
 				[]*vdl.Value{vdl.ValueOf(customerEntries[8].key), customerEntries[8].value},
+				[]*vdl.Value{vdl.ValueOf(customerEntries[9].key), customerEntries[9].value},
 			},
 		},
 		{
@@ -633,6 +678,7 @@
 				[]*vdl.Value{customerEntries[6].value},
 				[]*vdl.Value{customerEntries[7].value},
 				[]*vdl.Value{customerEntries[8].value},
+				[]*vdl.Value{customerEntries[9].value},
 			},
 		},
 		{
@@ -742,6 +788,7 @@
 				[]*vdl.Value{vdl.ValueOf(t2015_07_01)},
 				[]*vdl.Value{vdl.ValueOf(t2015_07_01)},
 				[]*vdl.Value{vdl.ValueOf(t2015_07_01)},
+				[]*vdl.Value{vdl.ValueOf(t2015_07_01)},
 			},
 		},
 		// DateTime function
@@ -758,6 +805,7 @@
 				[]*vdl.Value{vdl.ValueOf(t2015_07_01_01_23_45)},
 				[]*vdl.Value{vdl.ValueOf(t2015_07_01_01_23_45)},
 				[]*vdl.Value{vdl.ValueOf(t2015_07_01_01_23_45)},
+				[]*vdl.Value{vdl.ValueOf(t2015_07_01_01_23_45)},
 			},
 		},
 		// LowerCase function
@@ -767,6 +815,7 @@
 			[][]*vdl.Value{
 				[]*vdl.Value{vdl.ValueOf("john smith")},
 				[]*vdl.Value{vdl.ValueOf("bat masterson")},
+				[]*vdl.Value{vdl.ValueOf("john steed")},
 			},
 		},
 		// UpperCase function
@@ -776,6 +825,7 @@
 			[][]*vdl.Value{
 				[]*vdl.Value{vdl.ValueOf("JOHN SMITH")},
 				[]*vdl.Value{vdl.ValueOf("BAT MASTERSON")},
+				[]*vdl.Value{vdl.ValueOf("JOHN STEED")},
 			},
 		},
 		// YMDHMS function
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 e2c6e1c..ef112c3 100644
--- a/v23/syncbase/nosql/internal/query/query_checker/query_checker.go
+++ b/v23/syncbase/nosql/internal/query/query_checker/query_checker.go
@@ -104,7 +104,7 @@
 	}
 
 	// Like expressions require operand2 to be a string literal that must be validated.
-	if e.Operator.Type == query_parser.Like {
+	if e.Operator.Type == query_parser.Like || e.Operator.Type == query_parser.NotLike {
 		if e.Operand2.Type != query_parser.TypStr {
 			return syncql.NewErrLikeExpressionsRequireRhsString(db.GetContext(), e.Off)
 		}
@@ -119,9 +119,13 @@
 		if err != nil {
 			return err
 		}
-		// Optimization: If like argument contains no wildcards, convert the expression to equals.
+		// Optimization: If like/not like argument contains no wildcards, convert the expression to equals/not equals.
 		if !foundWildcard {
-			e.Operator.Type = query_parser.Equal
+			if e.Operator.Type == query_parser.Like {
+				e.Operator.Type = query_parser.Equal
+			} else { // not like
+				e.Operator.Type = query_parser.NotEqual
+			}
 			// Since this is no longer a like expression, we need to unescape
 			// any escaped chars (i.e., "\\", "\_" and "\%" become
 			// "\", "_" and "%", respectively).
@@ -147,7 +151,7 @@
 	}
 
 	// k as an operand must be the first operand, the operator must be
-	// = | <> | > | >= | < | <= | like and the 2nd operand must be a string literal.
+	// = | <> | > | >= | < | <= | like | not 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 &&
@@ -155,7 +159,8 @@
 			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.Operator.Type != query_parser.NotEqual &&
+			e.Operator.Type != query_parser.NotLike) ||
 			e.Operand2.Type != query_parser.TypStr)) || IsKey(e.Operand2) {
 		return syncql.NewErrKeyExpressionForm(db.GetContext(), e.Off)
 	}
@@ -365,10 +370,7 @@
 	return o.Type == query_parser.TypExpr
 }
 
-func computeKeyRangeForPrefix(prefix string) query_db.KeyRange {
-	if prefix == "" {
-		return KeyRangeAll
-	}
+func afterPrefix(prefix string) string {
 	// Copied from syncbase.
 	limit := []byte(prefix)
 	for len(limit) > 0 {
@@ -379,7 +381,24 @@
 			break                    // no carry
 		}
 	}
-	return query_db.KeyRange{prefix, string(limit)}
+	return string(limit)
+}
+
+func computeKeyRangeForLike(prefix string) query_db.KeyRange {
+	if prefix == "" {
+		return KeyRangeAll
+	}
+	return query_db.KeyRange{prefix, afterPrefix(prefix)}
+}
+
+func computeKeyRangesForNotLike(prefix string) *query_db.KeyRanges {
+	if prefix == "" {
+		return &query_db.KeyRanges{KeyRangeAll}
+	}
+	return &query_db.KeyRanges{
+		query_db.KeyRange{"", prefix},
+		query_db.KeyRange{afterPrefix(prefix), ""},
+	}
 }
 
 // The limit for a single value range is simply a zero byte appended.
@@ -466,7 +485,9 @@
 		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)}
+			return &query_db.KeyRanges{computeKeyRangeForLike(expr.Operand2.Prefix)}
+		case query_parser.NotLike:
+			return computeKeyRangesForNotLike(expr.Operand2.Prefix)
 		case query_parser.LessThan:
 			return &query_db.KeyRanges{query_db.KeyRange{"", expr.Operand2.Str}}
 		case query_parser.LessThanOrEqual:
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 5537b48..dc7fa12 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
@@ -92,6 +92,8 @@
 		{"select k, v.name from Customer where k = \"foo\""},
 		{"select v from Customer where t = \"Foo.Bar\""},
 		{"select k, v from Customer where t = \"Foo.Bar\" and k like \"abc%\" limit 100 offset 200"},
+		{"select v.z from Customer where k not like \"foo\""},
+		{"select v.z from Customer where k not like \"foo%\""},
 		{"select v from Customer where v.A = true"},
 		{"select v from Customer where v.A <> true"},
 		{"select v from Customer where false = v.A"},
@@ -249,6 +251,13 @@
 				query_db.KeyRange{"abc", "abd"},
 			},
 		},
+		{
+			"select k, v from Customer where k not like \"002%\"",
+			&query_db.KeyRanges{
+				query_db.KeyRange{"", "002"},
+				query_db.KeyRange{"003", ""},
+			},
+		},
 	}
 
 	for _, test := range basic {
@@ -375,7 +384,6 @@
 		{"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.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 7acf8ec..8f24388 100644
--- a/v23/syncbase/nosql/internal/query/test/query_test.go
+++ b/v23/syncbase/nosql/internal/query/test/query_test.go
@@ -64,7 +64,7 @@
 			}
 			// Keys and keyRanges are both sorted low to high, so we can increment
 			// keyRangesCursor if the keyRange.Limit is < the key.
-			if kvs.keyRanges[kvs.keyRangesCursor].Limit < kvs.table.rows[kvs.cursor].key {
+			if compareKeyToLimit(kvs.table.rows[kvs.cursor].key, kvs.keyRanges[kvs.keyRangesCursor].Limit) > 0 {
 				kvs.keyRangesCursor++
 				if kvs.keyRangesCursor >= len(kvs.keyRanges) {
 					return false
@@ -608,7 +608,7 @@
 			},
 		},
 		{
-			// Select keys & values for all records with a key prefix of "001".
+			// Select keys & values for all records with a key prefix of "002".
 			"select k, v from Customer where k like \"002%\"",
 			[]string{"k", "v"},
 			[][]*vdl.Value{
@@ -620,6 +620,35 @@
 			},
 		},
 		{
+			// Select keys & values for all records with a key prefix NOT LIKE "002%".
+			"select k, v from Customer where k not like \"002%\"",
+			[]string{"k", "v"},
+			[][]*vdl.Value{
+				[]*vdl.Value{vdl.ValueOf(custTable.rows[0].key), custTable.rows[0].value},
+				[]*vdl.Value{vdl.ValueOf(custTable.rows[1].key), custTable.rows[1].value},
+				[]*vdl.Value{vdl.ValueOf(custTable.rows[2].key), custTable.rows[2].value},
+				[]*vdl.Value{vdl.ValueOf(custTable.rows[3].key), custTable.rows[3].value},
+				[]*vdl.Value{vdl.ValueOf(custTable.rows[9].key), custTable.rows[9].value},
+			},
+		},
+		{
+			// Select keys & values for all records with a key prefix NOT LIKE "002".
+			// will be optimized to k <> "002"
+			"select k, v from Customer where k not like \"002\"",
+			[]string{"k", "v"},
+			[][]*vdl.Value{
+				[]*vdl.Value{vdl.ValueOf(custTable.rows[0].key), custTable.rows[0].value},
+				[]*vdl.Value{vdl.ValueOf(custTable.rows[1].key), custTable.rows[1].value},
+				[]*vdl.Value{vdl.ValueOf(custTable.rows[2].key), custTable.rows[2].value},
+				[]*vdl.Value{vdl.ValueOf(custTable.rows[3].key), custTable.rows[3].value},
+				[]*vdl.Value{vdl.ValueOf(custTable.rows[5].key), custTable.rows[5].value},
+				[]*vdl.Value{vdl.ValueOf(custTable.rows[6].key), custTable.rows[6].value},
+				[]*vdl.Value{vdl.ValueOf(custTable.rows[7].key), custTable.rows[7].value},
+				[]*vdl.Value{vdl.ValueOf(custTable.rows[8].key), custTable.rows[8].value},
+				[]*vdl.Value{vdl.ValueOf(custTable.rows[9].key), custTable.rows[9].value},
+			},
+		},
+		{
 			// Select keys & values for all records with a key prefix of "001".
 			// or a key prefix of "002".
 			"select k, v from Customer where k like \"001%\" or k like \"002%\"",
diff --git a/v23/syncbase/nosql/syncql/syncql.vdl b/v23/syncbase/nosql/syncql/syncql.vdl
index a57e55a..75b58e2 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|not 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 f49a3af..81531e6 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|not 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|not 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 2943a52..5443660 100644
--- a/x/ref/services/syncbase/server/nosql/database.go
+++ b/x/ref/services/syncbase/server/nosql/database.go
@@ -509,8 +509,8 @@
 		}
 		// We've reached the end of the iterator for this keyRange.
 		// Jump to the next one.
+		s.it[s.curr] = nil
 		s.curr++
-		s.it = nil
 		s.validRow = false
 	}
 	// There are no more prefixes to scan.