23f644976a
Move all the query system to its own package. The reason is to avoid it to rely on unexported methods and structures, and move it out of the main package since this is really not a core feature. It is still tied to the toml.TomlTree and toml.Position structures for now. * Move query mechanism to its own subpackage * Rename QueryResult to Result to avoid stutter * Add query.CompileAndExecute Fixes #116
483 lines
9.9 KiB
Go
483 lines
9.9 KiB
Go
package query
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"sort"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
"github.com/pelletier/go-toml"
|
|
)
|
|
|
|
type queryTestNode struct {
|
|
value interface{}
|
|
position toml.Position
|
|
}
|
|
|
|
func valueString(root interface{}) string {
|
|
result := "" //fmt.Sprintf("%T:", root)
|
|
switch node := root.(type) {
|
|
case *Result:
|
|
items := []string{}
|
|
for i, v := range node.Values() {
|
|
items = append(items, fmt.Sprintf("%s:%s",
|
|
node.Positions()[i].String(), valueString(v)))
|
|
}
|
|
sort.Strings(items)
|
|
result = "[" + strings.Join(items, ", ") + "]"
|
|
case queryTestNode:
|
|
result = fmt.Sprintf("%s:%s",
|
|
node.position.String(), valueString(node.value))
|
|
case []interface{}:
|
|
items := []string{}
|
|
for _, v := range node {
|
|
items = append(items, valueString(v))
|
|
}
|
|
sort.Strings(items)
|
|
result = "[" + strings.Join(items, ", ") + "]"
|
|
case *toml.TomlTree:
|
|
// workaround for unreliable map key ordering
|
|
items := []string{}
|
|
for _, k := range node.Keys() {
|
|
v := node.GetPath([]string{k})
|
|
items = append(items, k+":"+valueString(v))
|
|
}
|
|
sort.Strings(items)
|
|
result = "{" + strings.Join(items, ", ") + "}"
|
|
case map[string]interface{}:
|
|
// workaround for unreliable map key ordering
|
|
items := []string{}
|
|
for k, v := range node {
|
|
items = append(items, k+":"+valueString(v))
|
|
}
|
|
sort.Strings(items)
|
|
result = "{" + strings.Join(items, ", ") + "}"
|
|
case int64:
|
|
result += fmt.Sprintf("%d", node)
|
|
case string:
|
|
result += "'" + node + "'"
|
|
case float64:
|
|
result += fmt.Sprintf("%f", node)
|
|
case bool:
|
|
result += fmt.Sprintf("%t", node)
|
|
case time.Time:
|
|
result += fmt.Sprintf("'%v'", node)
|
|
}
|
|
return result
|
|
}
|
|
|
|
func assertValue(t *testing.T, result, ref interface{}) {
|
|
pathStr := valueString(result)
|
|
refStr := valueString(ref)
|
|
if pathStr != refStr {
|
|
t.Errorf("values do not match")
|
|
t.Log("test:", pathStr)
|
|
t.Log("ref: ", refStr)
|
|
}
|
|
}
|
|
|
|
func assertQueryPositions(t *testing.T, tomlDoc string, query string, ref []interface{}) {
|
|
tree, err := toml.Load(tomlDoc)
|
|
if err != nil {
|
|
t.Errorf("Non-nil toml parse error: %v", err)
|
|
return
|
|
}
|
|
q, err := Compile(query)
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
results := q.Execute(tree)
|
|
assertValue(t, results, ref)
|
|
}
|
|
|
|
func TestQueryRoot(t *testing.T) {
|
|
assertQueryPositions(t,
|
|
"a = 42",
|
|
"$",
|
|
[]interface{}{
|
|
queryTestNode{
|
|
map[string]interface{}{
|
|
"a": int64(42),
|
|
}, toml.Position{1, 1},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestQueryKey(t *testing.T) {
|
|
assertQueryPositions(t,
|
|
"[foo]\na = 42",
|
|
"$.foo.a",
|
|
[]interface{}{
|
|
queryTestNode{
|
|
int64(42), toml.Position{2, 1},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestQueryKeyString(t *testing.T) {
|
|
assertQueryPositions(t,
|
|
"[foo]\na = 42",
|
|
"$.foo['a']",
|
|
[]interface{}{
|
|
queryTestNode{
|
|
int64(42), toml.Position{2, 1},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestQueryIndex(t *testing.T) {
|
|
assertQueryPositions(t,
|
|
"[foo]\na = [1,2,3,4,5,6,7,8,9,0]",
|
|
"$.foo.a[5]",
|
|
[]interface{}{
|
|
queryTestNode{
|
|
int64(6), toml.Position{2, 1},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestQuerySliceRange(t *testing.T) {
|
|
assertQueryPositions(t,
|
|
"[foo]\na = [1,2,3,4,5,6,7,8,9,0]",
|
|
"$.foo.a[0:5]",
|
|
[]interface{}{
|
|
queryTestNode{
|
|
int64(1), toml.Position{2, 1},
|
|
},
|
|
queryTestNode{
|
|
int64(2), toml.Position{2, 1},
|
|
},
|
|
queryTestNode{
|
|
int64(3), toml.Position{2, 1},
|
|
},
|
|
queryTestNode{
|
|
int64(4), toml.Position{2, 1},
|
|
},
|
|
queryTestNode{
|
|
int64(5), toml.Position{2, 1},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestQuerySliceStep(t *testing.T) {
|
|
assertQueryPositions(t,
|
|
"[foo]\na = [1,2,3,4,5,6,7,8,9,0]",
|
|
"$.foo.a[0:5:2]",
|
|
[]interface{}{
|
|
queryTestNode{
|
|
int64(1), toml.Position{2, 1},
|
|
},
|
|
queryTestNode{
|
|
int64(3), toml.Position{2, 1},
|
|
},
|
|
queryTestNode{
|
|
int64(5), toml.Position{2, 1},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestQueryAny(t *testing.T) {
|
|
assertQueryPositions(t,
|
|
"[foo.bar]\na=1\nb=2\n[foo.baz]\na=3\nb=4",
|
|
"$.foo.*",
|
|
[]interface{}{
|
|
queryTestNode{
|
|
map[string]interface{}{
|
|
"a": int64(1),
|
|
"b": int64(2),
|
|
}, toml.Position{1, 1},
|
|
},
|
|
queryTestNode{
|
|
map[string]interface{}{
|
|
"a": int64(3),
|
|
"b": int64(4),
|
|
}, toml.Position{4, 1},
|
|
},
|
|
})
|
|
}
|
|
func TestQueryUnionSimple(t *testing.T) {
|
|
assertQueryPositions(t,
|
|
"[foo.bar]\na=1\nb=2\n[baz.foo]\na=3\nb=4\n[gorf.foo]\na=5\nb=6",
|
|
"$.*[bar,foo]",
|
|
[]interface{}{
|
|
queryTestNode{
|
|
map[string]interface{}{
|
|
"a": int64(1),
|
|
"b": int64(2),
|
|
}, toml.Position{1, 1},
|
|
},
|
|
queryTestNode{
|
|
map[string]interface{}{
|
|
"a": int64(3),
|
|
"b": int64(4),
|
|
}, toml.Position{4, 1},
|
|
},
|
|
queryTestNode{
|
|
map[string]interface{}{
|
|
"a": int64(5),
|
|
"b": int64(6),
|
|
}, toml.Position{7, 1},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestQueryRecursionAll(t *testing.T) {
|
|
assertQueryPositions(t,
|
|
"[foo.bar]\na=1\nb=2\n[baz.foo]\na=3\nb=4\n[gorf.foo]\na=5\nb=6",
|
|
"$..*",
|
|
[]interface{}{
|
|
queryTestNode{
|
|
map[string]interface{}{
|
|
"foo": map[string]interface{}{
|
|
"bar": map[string]interface{}{
|
|
"a": int64(1),
|
|
"b": int64(2),
|
|
},
|
|
},
|
|
"baz": map[string]interface{}{
|
|
"foo": map[string]interface{}{
|
|
"a": int64(3),
|
|
"b": int64(4),
|
|
},
|
|
},
|
|
"gorf": map[string]interface{}{
|
|
"foo": map[string]interface{}{
|
|
"a": int64(5),
|
|
"b": int64(6),
|
|
},
|
|
},
|
|
}, toml.Position{1, 1},
|
|
},
|
|
queryTestNode{
|
|
map[string]interface{}{
|
|
"bar": map[string]interface{}{
|
|
"a": int64(1),
|
|
"b": int64(2),
|
|
},
|
|
}, toml.Position{1, 1},
|
|
},
|
|
queryTestNode{
|
|
map[string]interface{}{
|
|
"a": int64(1),
|
|
"b": int64(2),
|
|
}, toml.Position{1, 1},
|
|
},
|
|
queryTestNode{
|
|
int64(1), toml.Position{2, 1},
|
|
},
|
|
queryTestNode{
|
|
int64(2), toml.Position{3, 1},
|
|
},
|
|
queryTestNode{
|
|
map[string]interface{}{
|
|
"foo": map[string]interface{}{
|
|
"a": int64(3),
|
|
"b": int64(4),
|
|
},
|
|
}, toml.Position{4, 1},
|
|
},
|
|
queryTestNode{
|
|
map[string]interface{}{
|
|
"a": int64(3),
|
|
"b": int64(4),
|
|
}, toml.Position{4, 1},
|
|
},
|
|
queryTestNode{
|
|
int64(3), toml.Position{5, 1},
|
|
},
|
|
queryTestNode{
|
|
int64(4), toml.Position{6, 1},
|
|
},
|
|
queryTestNode{
|
|
map[string]interface{}{
|
|
"foo": map[string]interface{}{
|
|
"a": int64(5),
|
|
"b": int64(6),
|
|
},
|
|
}, toml.Position{7, 1},
|
|
},
|
|
queryTestNode{
|
|
map[string]interface{}{
|
|
"a": int64(5),
|
|
"b": int64(6),
|
|
}, toml.Position{7, 1},
|
|
},
|
|
queryTestNode{
|
|
int64(5), toml.Position{8, 1},
|
|
},
|
|
queryTestNode{
|
|
int64(6), toml.Position{9, 1},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestQueryRecursionUnionSimple(t *testing.T) {
|
|
assertQueryPositions(t,
|
|
"[foo.bar]\na=1\nb=2\n[baz.foo]\na=3\nb=4\n[gorf.foo]\na=5\nb=6",
|
|
"$..['foo','bar']",
|
|
[]interface{}{
|
|
queryTestNode{
|
|
map[string]interface{}{
|
|
"bar": map[string]interface{}{
|
|
"a": int64(1),
|
|
"b": int64(2),
|
|
},
|
|
}, toml.Position{1, 1},
|
|
},
|
|
queryTestNode{
|
|
map[string]interface{}{
|
|
"a": int64(3),
|
|
"b": int64(4),
|
|
}, toml.Position{4, 1},
|
|
},
|
|
queryTestNode{
|
|
map[string]interface{}{
|
|
"a": int64(1),
|
|
"b": int64(2),
|
|
}, toml.Position{1, 1},
|
|
},
|
|
queryTestNode{
|
|
map[string]interface{}{
|
|
"a": int64(5),
|
|
"b": int64(6),
|
|
}, toml.Position{7, 1},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestQueryFilterFn(t *testing.T) {
|
|
buff, err := ioutil.ReadFile("../example.toml")
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
|
|
assertQueryPositions(t, string(buff),
|
|
"$..[?(int)]",
|
|
[]interface{}{
|
|
queryTestNode{
|
|
int64(8001), toml.Position{13, 1},
|
|
},
|
|
queryTestNode{
|
|
int64(8001), toml.Position{13, 1},
|
|
},
|
|
queryTestNode{
|
|
int64(8002), toml.Position{13, 1},
|
|
},
|
|
queryTestNode{
|
|
int64(5000), toml.Position{14, 1},
|
|
},
|
|
})
|
|
|
|
assertQueryPositions(t, string(buff),
|
|
"$..[?(string)]",
|
|
[]interface{}{
|
|
queryTestNode{
|
|
"TOML Example", toml.Position{3, 1},
|
|
},
|
|
queryTestNode{
|
|
"Tom Preston-Werner", toml.Position{6, 1},
|
|
},
|
|
queryTestNode{
|
|
"GitHub", toml.Position{7, 1},
|
|
},
|
|
queryTestNode{
|
|
"GitHub Cofounder & CEO\nLikes tater tots and beer.",
|
|
toml.Position{8, 1},
|
|
},
|
|
queryTestNode{
|
|
"192.168.1.1", toml.Position{12, 1},
|
|
},
|
|
queryTestNode{
|
|
"10.0.0.1", toml.Position{21, 3},
|
|
},
|
|
queryTestNode{
|
|
"eqdc10", toml.Position{22, 3},
|
|
},
|
|
queryTestNode{
|
|
"10.0.0.2", toml.Position{25, 3},
|
|
},
|
|
queryTestNode{
|
|
"eqdc10", toml.Position{26, 3},
|
|
},
|
|
})
|
|
|
|
assertQueryPositions(t, string(buff),
|
|
"$..[?(float)]",
|
|
[]interface{}{
|
|
// no float values in document
|
|
})
|
|
|
|
tv, _ := time.Parse(time.RFC3339, "1979-05-27T07:32:00Z")
|
|
assertQueryPositions(t, string(buff),
|
|
"$..[?(tree)]",
|
|
[]interface{}{
|
|
queryTestNode{
|
|
map[string]interface{}{
|
|
"name": "Tom Preston-Werner",
|
|
"organization": "GitHub",
|
|
"bio": "GitHub Cofounder & CEO\nLikes tater tots and beer.",
|
|
"dob": tv,
|
|
}, toml.Position{5, 1},
|
|
},
|
|
queryTestNode{
|
|
map[string]interface{}{
|
|
"server": "192.168.1.1",
|
|
"ports": []interface{}{int64(8001), int64(8001), int64(8002)},
|
|
"connection_max": int64(5000),
|
|
"enabled": true,
|
|
}, toml.Position{11, 1},
|
|
},
|
|
queryTestNode{
|
|
map[string]interface{}{
|
|
"alpha": map[string]interface{}{
|
|
"ip": "10.0.0.1",
|
|
"dc": "eqdc10",
|
|
},
|
|
"beta": map[string]interface{}{
|
|
"ip": "10.0.0.2",
|
|
"dc": "eqdc10",
|
|
},
|
|
}, toml.Position{17, 1},
|
|
},
|
|
queryTestNode{
|
|
map[string]interface{}{
|
|
"ip": "10.0.0.1",
|
|
"dc": "eqdc10",
|
|
}, toml.Position{20, 3},
|
|
},
|
|
queryTestNode{
|
|
map[string]interface{}{
|
|
"ip": "10.0.0.2",
|
|
"dc": "eqdc10",
|
|
}, toml.Position{24, 3},
|
|
},
|
|
queryTestNode{
|
|
map[string]interface{}{
|
|
"data": []interface{}{
|
|
[]interface{}{"gamma", "delta"},
|
|
[]interface{}{int64(1), int64(2)},
|
|
},
|
|
}, toml.Position{28, 1},
|
|
},
|
|
})
|
|
|
|
assertQueryPositions(t, string(buff),
|
|
"$..[?(time)]",
|
|
[]interface{}{
|
|
queryTestNode{
|
|
tv, toml.Position{9, 1},
|
|
},
|
|
})
|
|
|
|
assertQueryPositions(t, string(buff),
|
|
"$..[?(bool)]",
|
|
[]interface{}{
|
|
queryTestNode{
|
|
true, toml.Position{15, 1},
|
|
},
|
|
})
|
|
}
|