Documentation ¶
Overview ¶
Package path provides PostgreSQL 17-compatible SQL/JSON path parsing and execution. It supports both SQL-standard path expressions and PostgreSQL-specific predicate check expressions. See the README for a description of the SQL/JSON Path language.
Postgres Equivalents ¶
List of the PostgreSQL jsonpath functions and their path Package equivalents:
- @? Operator: Use Path.Exists with exec.WithSilent
- @@ Operator: Use Path.Match with exec.Silent
- jsonb_path_exists(): Use Path.Exists
- jsonb_path_match(): Use Path.Match
- jsonb_path_query() and jsonb_path_query_array(): Use Path.Query
- jsonb_path_query_first(): Use Path.First
- jsonb_path_exists_tz(): Use Path.Exists with exec.WithTZ
- jsonb_path_match_tz(): Use Path.Match with exec.WithTZ
- jsonb_path_query_tz() and jsonb_path_query_array_tz(): Use Path.Query with exec.WithTZ
- jsonb_path_query_first_tz(): Use Path.First with exec.WithTZ
Options ¶
The path query methods take an optional list of exec.Option arguments.
exec.WithVars provides named values to be substituted into the path expression. See the WithVars example for a demonstration.
exec.WithSilent suppresses exec.ErrVerbose errors, including missing object field or array element, unexpected JSON item type, and datetime and numeric errors. This behavior might be helpful when searching JSON entities of varying structure. See the WithSilent example for a demonstration.
exec.WithTZ allows comparisons of date and time values that require timezone-aware conversions. By default such conversions are made relative to UTC, but can be made relative to another (user-preferred) time zone by using types.ContextWithTZ to add it to the context passed to the query method. See the WithTZ example for a demonstration, and types for more comprehensive examples.
Two Types of Queries ¶
PostgreSQL supports two flavors of path expressions, and this package follows suit:
- SQL-standard path expressions hew to the SQL standard, which allows Boolean predicates only in ?() filter expressions, and can return any number of results.
- Boolean predicate check expressions are a PostgreSQL extension that allow path expression to be a Boolean predicate, which can return only true, false, and null.
This duality can sometimes cause confusion, especially when using Path.Exists and the Postgres @? operator, which only work with SQL standard expressions, and Path.Match and the Postgres @@ operator, which only work with predicate check expressions.
The path package provides a couple of additional features to help navigate this duality:
- Path.IsPredicate returns true if a Path is a predicate check expression
- Path.PgIndexOperator returns a string representing the appropriate Postgres operator to use when sending queries to the database: @? for SQL-standard expressions and @@ for predicate check expressions.
- Path.ExistsOrMatch dispatches to the appropriate function, Path.Exists or Path.Match, depending on whether the path is a SQL standard or predicate check expression.
Errors ¶
The path query methods return four types of errors:
- exec.ErrExecution: Errors executing the query, such as array index out of bounds and division by zero.
- exec.ErrVerbose Execution errors that can be suppressed by exec.WithSilent. Wraps exec.ErrExecution.
- exec.ErrInvalid: Usage errors due to flaws in the implementation, indicating a bug that needs fixing. Should be rare.
- exec.NULL: Special error value returned by Path.Exists and Path.Match when the result is unknown.
In addition, when context.Context.Done is closed in the context passed to a query function, the query will cease operation and return an exec.ErrExecution that wraps the context.Canceled and context.DeadlineExceeded error returned from context.Context.Err.
Examples ¶
Example (PredicateCheckPath) ¶
Boolean predicate check expressions are a PostgreSQL extension that allow path expression to be a Boolean predicate, which can return only true, false, and null.
PostgreSQL jsonb_path_query():
=> SELECT '{ "track": { "segments": [ { "location": [ 47.763, 13.4034 ], "start time": "2018-10-14 10:05:14", "HR": 73 }, { "location": [ 47.706, 13.2635 ], "start time": "2018-10-14 10:39:21", "HR": 135 } ] } }' AS json \gset => SELECT jsonb_path_query(:'json', '$.track.segments[*].HR > 130'); jsonb_path_query ------------------ true (1 row)
package main import ( "context" "encoding/json" "fmt" "log" "github.com/theory/sqljson/path" ) func main() { src := []byte(`{ "track": { "segments": [ { "location": [ 47.763, 13.4034 ], "start time": "2018-10-14 10:05:14", "HR": 73 }, { "location": [ 47.706, 13.2635 ], "start time": "2018-10-14 10:39:21", "HR": 135 } ] } }`) // Parse the JSON. var value any if err := json.Unmarshal(src, &value); err != nil { log.Fatal(err) } // Parse the Postgres predicate check jsonpath query. p, err := path.Parse(`$.track.segments[*].HR > 130`) if err != nil { log.Fatal(err) } // Execute the query against the JSON. matched, err := p.Match(context.Background(), value) if err != nil { log.Fatal(err) } // Print the results. fmt.Printf("%v\n", matched) }
Output: true
Example (SQLStandardPath) ¶
SQL-standard path expressions hew to the SQL standard, which allows Boolean predicates only in ?() filter expressions, and can return any number of results.
PostgreSQL jsonb_path_query():
=> SELECT '{ "track": { "segments": [ { "location": [ 47.763, 13.4034 ], "start time": "2018-10-14 10:05:14", "HR": 73 }, { "location": [ 47.706, 13.2635 ], "start time": "2018-10-14 10:39:21", "HR": 135 } ] } }' AS json \gset => SELECT jsonb_path_query(:'json', '$.track.segments[*] ? (@.HR > 130)."start time"'); jsonb_path_query ----------------------- "2018-10-14 10:39:21" (1 row)
package main import ( "context" "encoding/json" "fmt" "log" "github.com/theory/sqljson/path" ) func main() { src := []byte(`{ "track": { "segments": [ { "location": [ 47.763, 13.4034 ], "start time": "2018-10-14 10:05:14", "HR": 73 }, { "location": [ 47.706, 13.2635 ], "start time": "2018-10-14 10:39:21", "HR": 135 } ] } }`) // Parse the JSON. var value any if err := json.Unmarshal(src, &value); err != nil { log.Fatal(err) } // Parse the SQL-standard jsonpath query. p, err := path.Parse(`$.track.segments[*] ? (@.HR > 130)."start time"`) if err != nil { log.Fatal(err) } // Execute the query against the JSON. items, err := p.Query(context.Background(), value) if err != nil { log.Fatal(err) } // Print the results. fmt.Printf("%v\n", items) }
Output: [2018-10-14 10:39:21]
Example (WithSilent) ¶
exec.WithSilent suppresses exec.ErrVerbose errors, including missing object field or array element, unexpected JSON item type, and datetime and numeric errors. This behavior might be helpful when searching JSON entities of varying structure.
For example, this PostgreSQL jsonb_path_query() call raises an error because index 1 it out of bounds of the array, ["hi"], which has only one value, and so raises an error:
=> SELECT jsonb_path_query(target => '["hi"]', path => 'strict $[1]'); ERROR: jsonpath array subscript is out of bounds
Passing the silent parameter suppresses the error:
=> SELECT jsonb_path_query(target => '["hi"]', path => 'strict $[1]', silent => true); jsonb_path_query ------------------ (0 rows)
Here's the equivalent call to Path.Query without and then with the exec.WithSilent option:
package main import ( "context" "fmt" "github.com/theory/sqljson/path" "github.com/theory/sqljson/path/exec" ) func main() { // Execute query with array index out of bounds. p := path.MustParse("strict $[1]") ctx := context.Background() res, err := p.Query(ctx, []any{"hi"}) fmt.Printf("%v: %v\n", res, err) // WithSilent suppresses the error. res, err = p.Query(ctx, []any{"hi"}, exec.WithSilent()) fmt.Printf("%v: %v\n", res, err) }
Output: []: exec: jsonpath array subscript is out of bounds []: <nil>
Example (WithTZ) ¶
exec.WithTZ allows comparisons of date and time values that require timezone-aware conversions. By default such conversions are made relative to UTC, but can be made relative to another (user-preferred) time zone by using types.ContextWithTZ to add it to the context passed to the query method.
This is the equivalent to using the *_tz() PostgreSQL functions. For example, this call to jsonb_path_query_tz() converts "2015-08-02", which has no offset, to a timestamptz in UTC, to compare to the two values. It selects only "2015-08-02 23:00:00-05" because, once it converts to PDT, its value is "2015-08-02 21:00:00-07", while "2015-08-02 01:00:00-05" resolves to "2015-08-01 23:00:00-07", which is less than 2015-08-02:
=> SET time zone 'PST8PDT'; SET => SELECT jsonb_path_query_tz( '["2015-08-02 01:00:00-05", "2015-08-02 23:00:00-05"]', '$[*] ? (@.datetime() >= "2015-08-02".date())' ); jsonb_path_query_tz -------------------------- "2015-08-02 23:00:00-05"
Here's the equivalent using types.ContextWithTZ to set the time zone context in which Path.Query operates, and where exec.WithTZ allows conversion between timestamps with and without time zones:
package main import ( "context" "fmt" "log" "time" "github.com/theory/sqljson/path" "github.com/theory/sqljson/path/exec" "github.com/theory/sqljson/path/types" ) func main() { // Configure time zone to use when casting. loc, err := time.LoadLocation("PST8PDT") if err != nil { log.Fatal(err) } // Query in the context of that time zone. p := path.MustParse(`$[*] ? (@.datetime() >= "2015-08-02".date())`) res, err := p.Query( types.ContextWithTZ(context.Background(), loc), []any{"2015-08-01 02:00:00-05", "2015-08-02 23:00:00-05"}, exec.WithTZ(), ) if err != nil { log.Fatal(err) } fmt.Printf("%v\n", res) }
Output: [2015-08-02 23:00:00-05]
Example (WithVars) ¶
exec.WithVars provides named values to be substituted into the path expression. PostgreSQL jsonb_path_query() example:
=> SELECT jsonb_path_query('{"a":[1,2,3,4,5]}', '$.a[*] ? (@ >= $min && @ <= $max)', '{"min":2, "max":4}'); jsonb_path_query ------------------ 2 3 4 (3 rows)
Path.Query using exec.WithVars:
package main import ( "context" "encoding/json" "fmt" "log" "github.com/theory/sqljson/path" "github.com/theory/sqljson/path/exec" ) func main() { p := path.MustParse("$.a[*] ? (@ >= $min && @ <= $max)") var value any if err := json.Unmarshal([]byte(`{"a":[1,2,3,4,5]}`), &value); err != nil { log.Fatal(err) } res, err := p.Query( context.Background(), value, exec.WithVars(exec.Vars{"min": float64(2), "max": float64(4)}), ) if err != nil { log.Fatal(err) } fmt.Printf("%v\n", res) }
Output: [2 3 4]
Index ¶
- Variables
- func MustQuery(path string, json any, opt ...exec.Option) any
- type Path
- func (path *Path) Exists(ctx context.Context, json any, opt ...exec.Option) (bool, error)
- func (path *Path) ExistsOrMatch(ctx context.Context, json any, opt ...exec.Option) (bool, error)
- func (path *Path) First(ctx context.Context, json any, opt ...exec.Option) (any, error)
- func (path *Path) IsPredicate() bool
- func (path *Path) MarshalBinary() ([]byte, error)
- func (path *Path) MarshalText() ([]byte, error)
- func (path *Path) Match(ctx context.Context, json any, opt ...exec.Option) (bool, error)
- func (path *Path) MustQuery(ctx context.Context, json any, opt ...exec.Option) any
- func (path *Path) PgIndexOperator() string
- func (path *Path) Query(ctx context.Context, json any, opt ...exec.Option) (any, error)
- func (path *Path) Scan(src any) error
- func (path *Path) String() string
- func (path *Path) UnmarshalBinary(data []byte) error
- func (path *Path) UnmarshalText(data []byte) error
- func (path *Path) Value() (driver.Value, error)
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrPath wraps parsing and execution errors. ErrPath = errors.New("path") // ErrScan wraps scanning errors. ErrScan = errors.New("scan") )
Functions ¶
Types ¶
type Path ¶
Path provides SQL/JSON Path operations.
func MustParse ¶
MustParse is like Parse but panics on parse failure.
Example ¶
package main import ( "fmt" "github.com/theory/sqljson/path" ) func main() { p := path.MustParse("$.x [*] ? ( @ > 2 )") fmt.Printf("%v\n", p) }
Output: $."x"[*]?(@ > 2)
func New ¶
New creates and returns a new Path query defined by ast. Use parser.Parse to create ast.
Example ¶
package main import ( "fmt" "log" "github.com/theory/sqljson/path" "github.com/theory/sqljson/path/parser" ) func main() { ast, err := parser.Parse("$.x [*] ? ( @ > 2 )") if err != nil { log.Fatal(err) } p := path.New(ast) fmt.Printf("%v\n", p) }
Output: $."x"[*]?(@ > 2)
func Parse ¶
Parse parses path and returns the resulting Path. Returns an error on parse failure. Returns an ErrPath error on parse failure (wraps parser.ErrParse).
Example ¶
package main import ( "fmt" "log" "github.com/theory/sqljson/path" ) func main() { p, err := path.Parse("$.x [*] ? ( @ > 2 )") if err != nil { log.Fatal(err) } fmt.Printf("%v\n", p) }
Output: $."x"[*]?(@ > 2)
func (*Path) Exists ¶
Exists checks whether the path returns any item for json. (This is useful only with SQL-standard JSON path expressions (when Path.IsPredicate returns false), not predicate check expressions (when Path.IsPredicate returns true), which always return a value.)
While the PostgreSQL jsonb_path_exists() function can return true, false, or null (thanks to SQL's three-valued logic), Exists cannot return NULL when the result is unknown. In such cases, Exists returns false and also the exec.NULL error value. It's a good idea to check for this error explicitly when the result is likely to be unknown.
See the Options section for details on the optional exec.WithVars, exec.WithTZ, and exec.WithSilent options.
Example (NULL) ¶
package main import ( "context" "errors" "fmt" "log" "github.com/theory/sqljson/path" "github.com/theory/sqljson/path/exec" ) func main() { p := path.MustParse("strict $[1]") ctx := context.Background() res, err := p.Exists(ctx, []any{"hi"}, exec.WithSilent()) if err != nil { if errors.Is(err, exec.NULL) { // The outcome was actually unknown. fmt.Println("result was null") } else { // Some other error. log.Fatal(err) } } else { // Result is known. fmt.Printf("%v\n", res) } }
Output: result was null
func (*Path) ExistsOrMatch ¶
ExistsOrMatch dispatches SQL standard path expressions to [Exists] and predicate check expressions to [Match], reducing the need to know which to call. Results and options are the same as for those methods.
func (*Path) First ¶
First is like [Query], but returns the first JSON item returned by path for json, or nil if there are no results. See the Options section for details on the optional exec.WithVars, exec.WithTZ, and exec.WithSilent options.
func (*Path) IsPredicate ¶
IsPredicate returns true if path represents a PostgreSQL-style "predicate check" expression, and false if it's a SQL-standard path.
func (*Path) MarshalBinary ¶
MarshalBinary implements encoding.BinaryMarshaler.
func (*Path) MarshalText ¶
MarshalText implements encoding.TextMarshaler.
func (*Path) Match ¶
Match returns the result of predicate check for json. (This is useful only with predicate check expressions, not SQL-standard JSON path expressions (when Path.IsPredicate returns false), since it will either fail or return nil if the path result is not a single boolean value.)
While the PostgreSQL jsonb_path_match() function can return true, false, or null (thanks to SQL's [three-valued logic]), Match cannot return NULL when the result is unknown. In such cases, Match returns false and also the exec.NULL error value. It's a good idea to check for this error explicitly when the result is likely to be unknown.
See the Options section for details on the optional exec.WithVars, exec.WithTZ, and exec.WithSilent options.
func (*Path) MustQuery ¶
MustQuery is like [Query], but panics on error. Mostly provided mainly for use in documentation examples.
func (*Path) PgIndexOperator ¶
PgIndexOperator returns the indexable PostgreSQL operator used to compare a path to a JSON value. Returns "@?" for a SQL-standard paths and "@@" for a predicate check expressions.
Example ¶
package main import ( "fmt" "github.com/theory/sqljson/path" ) func main() { p := path.MustParse("$.x[*] ?(@ > 2)") fmt.Printf("SQL Standard: %v\n", p.PgIndexOperator()) p = path.MustParse("$.x[*] > 2") fmt.Printf("Predicate Check: %v\n", p.PgIndexOperator()) }
Output: SQL Standard: @? Predicate Check: @@
func (*Path) Query ¶
Query returns all JSON items returned by path for json. For SQL-standard JSON path expressions (when Path.IsPredicate returns false) it returns the values selected from json. For predicate check expressions (when Path.IsPredicate returns true) it returns the result of the predicate check: true, false, or nil (for an unknown result).
See the Options section for details on the optional exec.WithVars, exec.WithTZ, and exec.WithSilent options.
func (*Path) Scan ¶
Scan implements sql.Scanner so Paths can be read from databases transparently. Currently, database types that map to string and []byte are supported. Please consult database-specific driver documentation for matching types. Returns ErrScan on scan failure (and may wrap parser.ErrParse).
func (*Path) UnmarshalBinary ¶
UnmarshalBinary implements encoding.BinaryUnmarshaler. Returns ErrScan on scan failure (wraps parser.ErrParse).
func (*Path) UnmarshalText ¶
UnmarshalText implements encoding.TextUnmarshaler.
Directories ¶
Path | Synopsis |
---|---|
Package ast provides an abstract syntax tree for SQL/JSON paths.
|
Package ast provides an abstract syntax tree for SQL/JSON paths. |
Package exec provides the routines for SQL/JSON path execution.
|
Package exec provides the routines for SQL/JSON path execution. |
Package parser parses SQL/JSON paths.
|
Package parser parses SQL/JSON paths. |
Package types provides PostgresSQL-compatible data types for SQL/JSON Path execution.
|
Package types provides PostgresSQL-compatible data types for SQL/JSON Path execution. |