storage: expose SplitWords in a separate package

Change-Id: I371274fad0b6ef8d79a1c5581917c847c3e94830
Reviewed-on: https://go-review.googlesource.com/35948
Reviewed-by: Russ Cox <rsc@golang.org>
diff --git a/storage/db/db.go b/storage/db/db.go
index c0aef58..5db5e0a 100644
--- a/storage/db/db.go
+++ b/storage/db/db.go
@@ -20,6 +20,7 @@
 
 	"golang.org/x/net/context"
 	"golang.org/x/perf/storage/benchfmt"
+	"golang.org/x/perf/storage/query"
 )
 
 // TODO(quentin): Add Context to every function when App Engine supports Go >=1.8.
@@ -365,7 +366,7 @@
 // key>value - value greater than (useful for dates)
 // key<value - value less than (also useful for dates)
 func (db *DB) Query(q string) *Query {
-	qparts := splitQueryWords(q)
+	qparts := query.SplitWords(q)
 
 	var args []interface{}
 	query := "SELECT r.Content FROM "
@@ -414,48 +415,6 @@
 	return &Query{rows: rows}
 }
 
-// splitQueryWords splits q into words using shell syntax (whitespace
-// can be escaped with double quotes or with a backslash).
-func splitQueryWords(q string) []string {
-	var words []string
-	word := make([]byte, len(q))
-	w := 0
-	quoting := false
-	for r := 0; r < len(q); r++ {
-		switch c := q[r]; {
-		case c == '"' && quoting:
-			quoting = false
-		case quoting:
-			if c == '\\' {
-				r++
-			}
-			if r < len(q) {
-				word[w] = q[r]
-				w++
-			}
-		case c == '"':
-			quoting = true
-		case c == ' ', c == '\t':
-			if w > 0 {
-				words = append(words, string(word[:w]))
-			}
-			w = 0
-		case c == '\\':
-			r++
-			fallthrough
-		default:
-			if r < len(q) {
-				word[w] = q[r]
-				w++
-			}
-		}
-	}
-	if w > 0 {
-		words = append(words, string(word[:w]))
-	}
-	return words
-}
-
 // Query is the result of a query.
 // Use Next to advance through the rows, making sure to call Close when done:
 //
diff --git a/storage/db/db_test.go b/storage/db/db_test.go
index 046622e..97c9382 100644
--- a/storage/db/db_test.go
+++ b/storage/db/db_test.go
@@ -11,7 +11,6 @@
 	"io/ioutil"
 	"os"
 	"os/exec"
-	"reflect"
 	"strings"
 	"testing"
 	"time"
@@ -23,24 +22,6 @@
 
 // Most of the db package is tested via the end-to-end-tests in perf/storage/app.
 
-func TestSplitQueryWords(t *testing.T) {
-	for _, test := range []struct {
-		q    string
-		want []string
-	}{
-		{"hello world", []string{"hello", "world"}},
-		{"hello\\ world", []string{"hello world"}},
-		{`"key:value two" and\ more`, []string{"key:value two", "and more"}},
-		{`one" two"\ three four`, []string{"one two three", "four"}},
-		{`"4'7\""`, []string{`4'7"`}},
-	} {
-		have := SplitQueryWords(test.q)
-		if !reflect.DeepEqual(have, test.want) {
-			t.Errorf("splitQueryWords(%q) = %+v, want %+v", test.q, have, test.want)
-		}
-	}
-}
-
 // TestUploadIDs verifies that NewUpload generates the correct sequence of upload IDs.
 func TestUploadIDs(t *testing.T) {
 	ctx := context.Background()
diff --git a/storage/db/export_test.go b/storage/db/export_test.go
index 5f465f0..da163da 100644
--- a/storage/db/export_test.go
+++ b/storage/db/export_test.go
@@ -9,8 +9,6 @@
 	"time"
 )
 
-var SplitQueryWords = splitQueryWords
-
 func DBSQL(db *DB) *sql.DB {
 	return db.sql
 }
diff --git a/storage/query/query.go b/storage/query/query.go
new file mode 100644
index 0000000..9b62b5a
--- /dev/null
+++ b/storage/query/query.go
@@ -0,0 +1,48 @@
+// Copyright 2017 The Go Authors.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package query provides tools for parsing a query.
+package query
+
+// SplitWords splits q into words using shell syntax (whitespace
+// can be escaped with double quotes or with a backslash).
+func SplitWords(q string) []string {
+	var words []string
+	word := make([]byte, len(q))
+	w := 0
+	quoting := false
+	for r := 0; r < len(q); r++ {
+		switch c := q[r]; {
+		case c == '"' && quoting:
+			quoting = false
+		case quoting:
+			if c == '\\' {
+				r++
+			}
+			if r < len(q) {
+				word[w] = q[r]
+				w++
+			}
+		case c == '"':
+			quoting = true
+		case c == ' ', c == '\t':
+			if w > 0 {
+				words = append(words, string(word[:w]))
+			}
+			w = 0
+		case c == '\\':
+			r++
+			fallthrough
+		default:
+			if r < len(q) {
+				word[w] = q[r]
+				w++
+			}
+		}
+	}
+	if w > 0 {
+		words = append(words, string(word[:w]))
+	}
+	return words
+}
diff --git a/storage/query/query_test.go b/storage/query/query_test.go
new file mode 100644
index 0000000..91bcaea
--- /dev/null
+++ b/storage/query/query_test.go
@@ -0,0 +1,28 @@
+// Copyright 2017 The Go Authors.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package query
+
+import (
+	"reflect"
+	"testing"
+)
+
+func TestSplitQueryWords(t *testing.T) {
+	for _, test := range []struct {
+		q    string
+		want []string
+	}{
+		{"hello world", []string{"hello", "world"}},
+		{"hello\\ world", []string{"hello world"}},
+		{`"key:value two" and\ more`, []string{"key:value two", "and more"}},
+		{`one" two"\ three four`, []string{"one two three", "four"}},
+		{`"4'7\""`, []string{`4'7"`}},
+	} {
+		have := SplitWords(test.q)
+		if !reflect.DeepEqual(have, test.want) {
+			t.Errorf("splitQueryWords(%q) = %+v, want %+v", test.q, have, test.want)
+		}
+	}
+}