Query interface with callback functions

* Added public Query interface
* Added filter function callback support
* Added "script" function callback support

Queries are generated via Compile(), which then may be run via Execute()
as many times as needed.  Much like compiling a regex, this is done to
elide the need to re-parse and build the funciton tree for each
execution.

The distinction between 'filter' and 'script' is borrowed from their
syntactic equivalents in jsonpath.  Right now, these accept no arguments
in the query, and instead merely pass the current node to the callback.
Filters return a bool and determine if the node is kept or culled out.
'Scripts' return a string or an in64, which is in turn used in an index
or key filter (respectively) on the current node's data.

A few callbacks are provided by default, with the ability to add
additional callbacks before calling Execute() on a compiled query.
This commit is contained in:
eanderton
2014-09-08 22:08:28 -04:00
parent c81f1892c2
commit 12e974f892
5 changed files with 330 additions and 117 deletions
+38 -9
View File
@@ -1,6 +1,7 @@
package jpath
import (
. "github.com/pelletier/go-toml"
"fmt"
"math"
"testing"
@@ -8,7 +9,7 @@ import (
// dump path tree to a string
func pathString(root PathFn) string {
result := fmt.Sprintf("%T:")
result := fmt.Sprintf("%T:", root)
switch fn := root.(type) {
case *terminatingFn:
result += "{}"
@@ -28,21 +29,27 @@ func pathString(root PathFn) string {
case *matchUnionFn:
result += "{["
for _, v := range fn.Union {
result += pathString(v)
result += pathString(v) + ", "
}
result += "]}"
case *matchRecursiveFn:
result += "{}"
result += pathString(fn.next)
case *matchFilterFn:
result += fmt.Sprintf("{%s}", fn.Name)
result += pathString(fn.next)
case *matchScriptFn:
result += fmt.Sprintf("{%s}", fn.Name)
result += pathString(fn.next)
}
return result
}
func assertPathMatch(t *testing.T, path, ref *QueryPath) bool {
func assertPathMatch(t *testing.T, path, ref *Query) bool {
pathStr := pathString(path.root)
refStr := pathString(ref.root)
if pathStr != refStr {
t.Errorf("paths do not match: %v vs %v")
t.Errorf("paths do not match")
t.Log("test:", pathStr)
t.Log("ref: ", refStr)
return false
@@ -50,18 +57,18 @@ func assertPathMatch(t *testing.T, path, ref *QueryPath) bool {
return true
}
func assertPath(t *testing.T, query string, ref *QueryPath) {
func assertPath(t *testing.T, query string, ref *Query) {
_, flow := lex(query)
path := parse(flow)
assertPathMatch(t, path, ref)
}
func buildPath(parts... PathFn) *QueryPath {
path := newQueryPath()
func buildPath(parts... PathFn) *Query {
query := newQuery()
for _, v := range parts {
path.Append(v)
query.appendPath(v)
}
return path
return query
}
func TestPathRoot(t *testing.T) {
@@ -187,3 +194,25 @@ func TestPathRecurse(t *testing.T) {
newMatchRecursiveFn(),
))
}
func TestPathFilterExpr(t *testing.T) {
assertPath(t,
"$[?('foo'),?(bar)]",
buildPath(
&matchUnionFn{ []PathFn {
newMatchFilterFn("foo", Position{}),
newMatchFilterFn("bar", Position{}),
}},
))
}
func TestPathScriptExpr(t *testing.T) {
assertPath(t,
"$[('foo'),(bar)]",
buildPath(
&matchUnionFn{ []PathFn {
newMatchScriptFn("foo", Position{}),
newMatchScriptFn("bar", Position{}),
}},
))
}