Merge "veyron/services/store/memstore/query: Implement the "hidden" keyword."
diff --git a/services/store/memstore/query/eval.go b/services/store/memstore/query/eval.go
index 48e991a..73fa455 100644
--- a/services/store/memstore/query/eval.go
+++ b/services/store/memstore/query/eval.go
@@ -63,6 +63,12 @@
 	results <-chan *store.QueryResult
 }
 
+// hiddenResult wraps a value so evalIterator can elide it from
+// storage.QueryResult.Fields that are sent to the client.
+type hiddenResult struct {
+	value vdlutil.Any
+}
+
 // Next implements the QueryStream method.
 func (it *evalIterator) Next() bool {
 	it.mu.Lock()
@@ -102,17 +108,23 @@
 // the result.Fields key to append them in reverse alphabetical order.
 // We use reverse alphabetical order because it.results is a stack--
 // we want to process them in alphabetical order.
+//
+// enqueueNestedChannels also removes any result.Fields that are of the
+// type hiddenResult.
 func (it *evalIterator) enqueueNestedChannels(result *store.QueryResult) {
 	if result.Fields == nil {
 		return
 	}
 	var nestingKeys []string
 	for key, val := range result.Fields {
-		// TODO(kash): If a stored value happens to be a store.QueryResult, we'll
-		// do the wrong thing here.  Once we store vom.Value instead of raw structs,
-		// this should not be a problem.
-		if _, ok := val.(chan *store.QueryResult); ok {
+		switch val.(type) {
+		case chan *store.QueryResult:
 			nestingKeys = append(nestingKeys, key)
+		case hiddenResult:
+			// If a field is "hidden", the value will be wrapped in the type
+			// hiddenResult to make it possible for evalIterator to elide it
+			// from the results sent to the client.
+			delete(result.Fields, key)
 		}
 	}
 	// Figure out the store.NestedResult values based on alphabetical order of
@@ -504,7 +516,6 @@
 	alias string
 	// hidden is true if this field in the selection should not be included
 	// in the results sent to the client.
-	// TODO(kash): hidden is currently ignored during evaluation.
 	hidden bool
 }
 
@@ -579,6 +590,12 @@
 		}
 
 		if a.alias != "" {
+			if a.hidden {
+				// If a field is "hidden", the value will be wrapped in the type
+				// hiddenResult to make it possible for evalIterator to elide it
+				// from the results sent to the client.
+				value = hiddenResult{value}
+			}
 			sel.Fields[a.alias] = value
 		} else {
 			sel.Fields[a.evaluator.name()] = value
@@ -1021,6 +1038,12 @@
 				e.name, result.Name, mapKeys(result.Fields))
 			return nil
 		}
+		// If a field is "hidden", the value will be wrapped in the type
+		// hiddenResult to make it possible for evalIterator to elide it
+		// from the results sent to the client.
+		if v, ok := val.(hiddenResult); ok {
+			return v.value
+		}
 		return val
 	}
 	fullpath := naming.Join(result.Name, e.name)
diff --git a/services/store/memstore/query/eval_test.go b/services/store/memstore/query/eval_test.go
index 2985265..6367a9f 100644
--- a/services/store/memstore/query/eval_test.go
+++ b/services/store/memstore/query/eval_test.go
@@ -358,6 +358,19 @@
 			},
 		},
 		{
+			"", "'teams/cardinals' | {Name as myname hidden, Location as myloc} | ? myname == 'cardinals'",
+			[]*store.QueryResult{
+				&store.QueryResult{
+					0,
+					"teams/cardinals",
+					map[string]vdlutil.Any{
+						"myloc": "CA",
+					},
+					nil,
+				},
+			},
+		},
+		{
 			"",
 			"'teams/*' | type team | {" +
 				"    Name as myname," +
@@ -434,7 +447,7 @@
 				break
 			}
 			if got, want := result, test.expectedResults[i]; !reflect.DeepEqual(got, want) {
-				t.Errorf("query: %s;\nGOT  %s\nWANT %s", test.query, got, want)
+				t.Errorf("query: %s;\nGOT  %v\nWANT %v", test.query, got, want)
 			}
 			i++
 		}