From 3b5119f6efdebc518cdbf113914d86255eac0fcc Mon Sep 17 00:00:00 2001 From: kgs Date: Tue, 12 May 2009 13:43:05 +0000 Subject: [PATCH] Most recent unchunked output of JSONGrammar.html git-svn-id: svn://svn.open-ils.org/ILS/trunk@13141 dcc99617-32d9-48b4-a31d-7c20da2025e4 --- docs/TechRef/JSONGrammar.html | 434 ++++++++++++++++++++++++++++++++++ 1 file changed, 434 insertions(+) create mode 100644 docs/TechRef/JSONGrammar.html diff --git a/docs/TechRef/JSONGrammar.html b/docs/TechRef/JSONGrammar.html new file mode 100644 index 0000000000..ba6ac3b477 --- /dev/null +++ b/docs/TechRef/JSONGrammar.html @@ -0,0 +1,434 @@ + + + Grammar of JSON Queries

Grammar of JSON Queries


+ Scott McKellar +

Introduction

The format of this grammar approximates Extended Backus-Naur notation. However it is + intended as input to human beings, not to parser generators such as Lex or Yacc. Do not + expect formal rigor. Sometimes narrative text will explain things that are clumsy to + express in formal notation. More often, the text will restate or summarize the formal + productions.

Conventions:

  1. The grammar is a series of productions.

  2. A production consists of a name, followed by "::=", followed by a definition + for the name. The name identifies a grammatical construct that can appear on the + right side of another production.

  3. Literals (including punctuation) are enclosed in 'single quotes', or in + "double quotes" if case is not significant.

  4. A single quotation mark within a literal is escaped with a preceding + backslash: 'dog\'s tail'.

  5. If a construct can be defined more than one way, then the alternatives may + appear in separate productions; or, they may appear in the same production, + separated by pipe symbols. The choice between these representations is of only + cosmetic significance.

  6. A construct enclosed within square brackets is optional.

  7. A construct enclosed within curly braces may be repeated zero or more + times.

  8. JSON allows arbitrary white space between tokens. To avoid ugly clutter, this + grammar ignores the optional white space.

  9. In many cases a production defines a JSON object, i.e. a list of name-value + pairs, separated by commas. Since the order of these name/value pairs is not + significant, the grammar will not try to show all the possible sequences. In + general it will present the required pairs first, if any, followed by any + optional elements.

Since both EBNF and JSON use curly braces and square brackets, pay close attention to + whether these characters are in single quotes. If they're in single quotes, they are + literal elements of the JSON notation. Otherwise they are elements of the EBNF notation. +

Primitives

We'll start by defining some primitives, to get them out of the way. They're mostly + just what you would expect.

[1] string ::= '"' chars '"'  
[2] chars ::= any valid sequence of UTF-8 characters, with certain special characters + escaped according to JSON rules  
[3] integer_literal ::= [ sign ] digit { digit }  
[4] sign ::= '+' | '-'  
[5] digit ::=digit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' 
[6] integer_string ::= '"' integer_literal '"'  
[7] integer ::= integer_literal | integer_string  
[8] number ::= any valid character sequence that is numeric according to JSON rules  

When json_query requires an integral value, it will usually accept a quoted string + and convert it to an integer by brute force – to zero if necessary. Likewise it may + truncate a floating point number to an integral value. Scientific notation will be + accepted but may not give the intended results.

[9] boolean ::= 'true' | 'false' | string | number  

The preferred way to encode a boolean is with the JSON reserved word true or false, + in lower case without quotation marks. The string true, in upper, + lower, or mixed case, is another way to encode true. Any other string evaluates to + false.

As an accommodation to perl, numbers may be used as booleans. A numeric value of 1 + means true, and any other numeric value means false.

Any other valid JSON value, such as an array, will be accepted as a boolean but + interpreted as false.

The last couple of primitives aren't really very primitive, but we introduce them + here for convenience:

[10] class_name ::= string  

A class_name is a special case of a string: the name of a class as defined by the + IDL. The class may refer either to a database table or to a source_definition, which is + a subquery.

[11] field_name ::= string  

A field_name is another special case of a string: the name of a non-virtual field as + defined by the IDL. A field_name is also a column name for the table corresponding to + the relevant class.

Query

The following production applies not only to the main query but also to most + subqueries.

[12] query ::= '{'
'"from"' ':' from_list
[ ',' '"select"' ':' select_list + ]
[ ',' '"where"' ':' where_condition ]
[ ',' '"having"' ':' + where_condition ]
[ ',' '"order_by"' ':' order_by_list ]
[ ',' + '"limit"' ':' integer ]
[ ',' '"offset"' ':' integer ]
[ ',' + '"distinct"' ':' boolean ]
[ ',' '"no_i18n"' ':' boolean ]
'}' +
 

Except for the "distinct" and no_i18n entries, + each name/value pair represents a major clause of the SELECT statement. The name/value + pairs may appear in any order.

There is no name/value pair for the GROUP BY clause, because json_query generates it + automatically according to information encoded elsewhere.

The "distinct" entry, if present and true, tells json_query that + it may have to create a GROUP BY clause. If not present, it defaults to false.

The "no_i18n" entry, if present and true, tells json_query to + suppress internationalization. If not present, it defaults to false. (Note that + "no_i18n" contains the digit one, not the letter ell.)

The values for limit and offset provide the + arguments of the LIMIT and OFFSET clauses, respectively, of the SQL statement. Each + value should be non-negative, if present, or else the SQL won't work.

FROM Clause

+ The object identified by “from” encodes the FROM clause of + the SQL. The associated value may be a string, an array, or a JSON object. +

[13] from_list ::= class_name  

+ If from_list is a class_name, the + json_query inserts the corresponding table name or subquery into the FROM + clause, using the class_name as an alias for the table + or subquery. The class must be defined as non-virtual in the IDL. +

[14] from_list ::= '[' string { ',' parameter } ']'  
[15] parameter ::= string | number | 'null'  

+ If from_list is a JSON array, then it represents a table-like function from + which the SQL statement will select rows, using a SELECT clause consisting + of “SELECT *” (regardless of the select_list supplied by the method parameter). +

+ The first entry in the array is the name of the function. It must be a string + naming a stored function. Each subsequent entry is a function parameter. If + it is a string or a number, json_query will insert it into a comma-separated + parameter list, enclosed in quotes, with any special characters escaped as needed. + If it is the JSON reserved word null, json_query will insert + it into the parameter list as a null value. +

+ If from_list is a JSON object, it must contain exactly one entry. + The key of this entry must be the name of a non-virtual class defined in the IDL. + This class will be the top-level class of the FROM clause, the only one named + outside of a JOIN clause. +

[16] from_list ::= '{' class_name ':' join_list '}'  
[17] join_list ::= class_name  
[18] join_list ::= '{' join_def { ',' join_def } '}'  

+ If the associated data is a class_name, json_query will + construct an INNER JOIN clause joining the class to the top-level clause, + using the columns specified by the IDL for such a join. +

+ Otherwise, the associated data must be a JSON object with one or more entries, + each entry defining a join: +

[19] join_def ::= + class_name ':'
+ '{'
+ [ '”type”' ':' string ]
+ [ '”field”' ':' field_name ]
+ [ '”fkey”' ':' field_name ]
+ [ '”filter”' ':' where_condition ]
+ [ '”filter_op”' ':' string ]
+ [ '”join”' ':' join_list ]
+ '}' +
 

+ The data portion of the “join_type” entry tells json_query + whether to use a left join, right join, full join, or inner join. The values + “left”, “right”, and “full”, + in upper, lower, or mixed case, have the obvious meanings. If the + “join_type” entry has any other value, or is not present, + json_query constructs an inner join. +

+ The “field” and “fkey” attributes specify the + columns to be equated in the join condition. The “field” + attribute refers to the column in the joined table, i.e. the one named by the + join_def. The “fkey” attribute refers to the + corresponding column in the other table, i.e. the one named outside the + join_def – either the top-level table or a table named by some + other join_def. +

+ It may be tempting to suppose that “fkey” stands for “foreign key”, + and therefore refers to a column in the child table that points to the key of a + parent table. Resist the temptation; the labels are arbitrary. The json_query + method doesn't care which table is the parent and which is the child. +

+ These relationships are best explained with an example. The following + from_list: +

+	{
+	    "aou": {
+	        "asv": {
+	            "type" : "left",
+	            "fkey" : "id",
+	            "field" : "owner"
+	        }
+	    }
+	}
+			

+ ...turns into the following FROM clause: +

+	FROM
+	    actor.org_unit AS "aou"
+	        LEFT JOIN action.survey AS "asv"
+	            ON ( "asv".owner = "aou".id )
+			

+ Note in this example that “fkey” refers to a column of the + class “aou”, and “field” refers to a + column of the class “asv”. +

+ If you specify only one of the two columns, json_query will try to identify the + other one from the IDL. However, if you specify only the column from the parent + table, this attempt will probably fail. +

+ If you specify both columns, json_query will use the column names you specify, + without verifying them with a lookup in the IDL. By this means you can perform + a join using a linkage that the IDL doesn't define. Of course, if the columns + don't exist in the database, the query will fail when json_query tries to execute it. +

+ Using the columns specified, either explicitly or implicitly, the json_query + method constructs a join condition. With raw SQL it is possible (though + rarely useful) to join two tables by an inequality. However the json_query + method always uses a simple equality condition. +

+ Using a “filter” entry in the join_def, you can apply one + or more additional conditions to the JOIN clause, typically to restrict the + join to certain rows of the joined table. The data associated with the + “filter” key is the same sort of + where_condition that you use for a WHERE clause + (discussed below). +

+ If the string associated with the “filter_op” entry is + “OR” in upper, lower, or mixed case, then the json_query + method uses OR to connect the standard join condition to any additional + conditions supplied by a “filter” entry. +

+ (Note that if the where_condition supplies multiple + conditions, they will be connected by AND. You will probably want to move + them down a layer – enclose them in parentheses, in effect – to avoid a + confusing mixture of ANDs and ORs.) +

+ If the “filter_op” entry carries any other value, or if + it is absent, then the json_query method uses AND. In the absence of a + “filter” entry, “filter_op” has no effect. +

+ A “join” entry in a join_def specifies + another layer of join. The class named in the subjoin is joined to the class + named by the join_def to which it is subordinate. By this + means you can encode multiple joins in a hierarchy. +

SELECT Clause

+ If a query does not contain an entry for “select”, json_query + will construct a default SELECT clause. The default includes every non-virtual + field from the top-level class of the FROM clause, as defined by the IDL. The + result is similar to SELECT *, except: +

  • The default includes only the fields defined in the IDL.

  • The columns will appear in the same order in which they appear in the IDL, + regardless of the order in which the database defines them.

+ There are other ways to specify a default SELECT list, as shown below. +

+ If a "select" entry is present, the associated value must + be a JSON object, keyed on class names: +

[20] select_list ::= '{' class_name ':' field_list { ',' class_name ':' field_list } '}'  

+ The class_name must identify either the top-level class or + a class belonging to one of the joins. Otherwise json_query will silently + ignore the select_list. +

[21] field_list ::= 'null' | '”*”'  

+ If a field_list is either the JSON reserved word null + (in lower case) or an asterisk in double quotes, json_query constructs a + default SELECT list – provided that the class is the top-level class of the + query. If the class belongs to a join somewhere, json_query ignores the + field_list. +

+ More commonly, the field_list is a JSON array of zero or + more field specifications: +

[22] field_list ::= '[' [ field_spec { ',' field_spec } ] ']'  

+ If the array is empty, json_query will construct a default SELECT list for + the class – again, provided that the class is the top-level class in the query. +

+ In the simplest case, a field specification may name a non-virtual field + defined in the IDL: +

[23] field_spec ::= field_name  

+ In some cases json_query constructs a call to the + oils_i18n_xlate function to internationalize the value of the + selected column. Specifically, it does so if all the following are true: +

  • the settings file defines a locale;

  • in the field definition for the field in the IDL, the tag + “il8n” is present and true;

  • the query does not include the + "no_il8n" tag (or includes it with a value of false).

+ A field specification may be a JSON object: +

[24] field_spec ::= + '{'
+ '”column”' ':'
+ [ ',' '”alias”' ':' string ]
+ [ ',' '”aggregate”' ':' boolean ]
+ [ ',' transform_spec ]
+ '}' +
 

+ The “column” entry provides the column name, which must + be defined as non-virtual in the IDL. +

+ The “alias” entry provides a column alias. If no alias + is specified, json_query uses the column name as its own alias. +

+ The “aggregate” entry has no effect on the SELECT clause + itself. Rather, it affects the construction of a GROUP BY class. If there + is an “aggregate” entry for any field, then json_query builds + a GROUP BY clause listing every column that is not tagged + for aggregation (or that carries an “aggregate” entry with + a value of false). If all columns are tagged for + aggregation, then json_query omits the GROUP BY clause. +

[25] transform_spec ::= + '”transform”' ':' string ]
+ [ ',' '”result_field” ':' string ]
+ [ ',' '”params” ':' param_list ] +
 

+ When a transform_spec is present, json_query selects the + return value of a function instead of selecting the column directly. The entry + for “transform” provides the name of the function, and the + column name (as specified by the “column” tag), qualified by + the class name, is the argument to the function. For example, you might use such + a function to format a date or time, or otherwise transform a column value. + You might also use an aggregate function such as SUM, COUNT, or MAX (possibly + together with the “aggregate” tag). +

+ The “result_field” entry, when present, specifies a subcolumn + of the function's return value. The resulting SQL encloses the function call + in parentheses, and follows it with a period and the subcolumn name. +

+ The “params” entry, if present, provides a possibly empty + array of additional parameter values, either strings, numbers, or nulls: +

[26] param_list ::= '[' [ parameter { ',' parameter } ] ']'  

+ Such parameter values are enclosed in single quotes, with any special characters + escaped as needed, and inserted after the column name as additional parameters + to the function. You might, for example, use an additional parameter to provide + a format string for a reformatting function. +

WHERE Clause

There are two types of where_condition: objects and arrays. + Of these, the object type is the more fundamental, and occurs at some level in every + where_condition. The array type is mainly a way of circumventing + a limitation of the object type.

The object type of where_condition is a comma-separated list + of one or more conditions:

[27] where_condition ::= '{' condition { ',' condition } '}'  

The generated SQL will include a code fragment for each condition, + joined by AND (or in some cases by OR, as described below).

As usual for entries in a JSON object, each condition consists + of a unique string to serve as a key, a colon, and an associated value.

The key string may be the name of a column belonging to the relevant table, or + it may be an operator string. In order to distinguish it from any possible column + name, an operator string always begins with a plus sign or minus sign.

JSON requires that every key string be unique within an object. This requirement + imposes some awkward limitations on a JSON query. For example, you might want to + express two conditions for the same column: id > 10 and id != 25. Since each of + those conditions would have the same key string, namely “id”, you can't put them + into the same JSON object.

The solution is to put such conflicting conditions in separate JSON objects, and + put the objects into an array:

[28] where_condition ::= '[' where_condition { ',' where_condition } ']'  

The resulting SQL encloses each subordinate set of conditions + in parentheses, and connects the sets with AND (or in some cases OR, as described + below). It's possible to put only a single where_condition in + the array; the result is to add a layer of parentheses around the condition.

There are two kinds of condition where the operator begins + with a plus sign. In the simpler case, the associated data is simply a column name: +

[29] condition ::= plus_class ':' field_name  

A plus_class is a string that begins with a plus sign. + The rest of the string, after the plus sign, must be the class name for the table + to which the column belongs.

If the column is a boolean, then the resulting SQL uses it (as qualified by the + class name) as a stand-alone condition.

Otherwise, this kind of syntax provides a way to place a column on the right side + of a comparison operator. For example:

+	{
+	    "from":"aou",
+	    "select": { "aou":[ "id", "name" ] },
+	    "where": {
+	        "id": {
+	            ">": { "+aou":"parent_ou" }
+	        }
+	    }
+	}
+			

The resulting SQL:

+	SELECT
+	    "aou".id AS "id",
+	    "aou".name AS "name"
+	FROM
+	    actor.org_unit AS "aou"
+	WHERE
+	    (
+	        "aou".id > (  "aou".parent_ou  )
+	    );
+			

The other type of condition that uses a + plus_class applies a specified class name to a + where_condition:

[30] condition ::= plus_class ':' where_condition  

The resulting SQL is enclosed in parentheses, and qualifies the columns with + the specified class name. This syntax provides a mechanism to shift the class + context – i.e. to refer to one class in a context that would otherwise refer to + a different class.

Ordinarily the class name must be a valid non-virtual class defined in the IDL, + and applicable to the associated where_condition. There is at + least one peculiar exception. The JSON fragment:

+	"+abc": { "+xyz":"frobozz" }
+			

...is rendered as:

+	(  "xyz".frobozz  )
+			

...even though neither “abc”, nor “xyz”, + nor “frobozz” is defined in the IDL. The class name + “abc” isn't used at all because the “+xyz” + operator overrides it. Such a query won't fail until json_query tries + to execute it in the database.

The other operators that may occur at this level all begin with a minus sign, + and they all represent familiar SQL operators. For example, the + “-or” operator joins the conditions within a + where_condition by OR (instead of the default AND), and + encloses them all in parentheses:

[31] condition ::= '”-or”' ':' where_condition  

In fact the “-or” operator is the only way to get OR into + the WHERE clause.

The “-and” operator is similar, except that it uses AND:

[32] condition ::= '”-and”' ':' where_condition  

Arguably the “-and” operator is redundant, because you can + get the same effect by wrapping the subordinate where_condition + in a JSON array. Either technique merely adds a layer of parentheses, since AND + connects successive conditions by default.

The “-not” operator expands the subordinate + where_condition within parentheses, and prefaces the result + with NOT:

[33] condition ::= '”-not”' ':' where_condition  

The “-exists” or “-not-exists” operator + constructs a subquery within an EXISTS or NOT EXISTS clause, respectively:

[34] condition ::= '”-exists”' ':' query  
[35] condition ::= '”-not-exists”' ':' query  

The remaining kinds of condition all have a + field_name on the left and some kind of predicate + on the right. A predicate places a constraint on the value of + the column – or, in some cases, on the value of the column as transformed by some + function call:

[36] condition ::= field_name ':' predicate  

The simplest such constraint is to require that the column have a specific value, + or be null:

[37] predicate ::= lit_value | 'null'  
[38] lit_value ::= string | number  

You can also compare a column to a literal value using some kind of inequality. + However it's a bit more complicated because you have to specify what kind of comparison + to make:

[39] predicate ::= '{' compare_op ':' lit_value '}'  
[40] compare_op ::= string  

A compare_op is a string that defines a comparison operator. + Valid values include the following:

+	=    <>   !=
+	<    >    <=   >=
+	~    ~*   !~   !~*
+	like      ilike
+	similar to
+		

Strictly speaking, json_query accepts any compare_op + that doesn't contain semicolons or white space (or + “similar to” as a special exception). As a result, it + is possible – and potentially useful – to use a custom operator like + “>100*” in order to insert an expression that would + otherwise be difficult or impossible to create through a JSON query. The ban + on semicolons and white space prevents certain kinds of SQL injection.

Note that json_query does not accept two operators that + PostgreSQL does accept: “is distinct from” + and “is not distinct from”.

You can also compare a column to a null value:

[41] predicate ::= '{' compare_op ':' 'null' '}'  

The equality operator “=” turns into IS NULL. Any other + operator turns into IS NOT NULL.

When a compare_op is paired with an array, it defines a + function call:

[42] predicate ::= '{' compare_op ':' '[' string { ',' parameter } ']' '}'  

The first entry in the array is the function's name. Subsequent entries in + the array, if any, represent the parameters of the function call. They may be + strings, numbers, or nulls. In the generated SQL, the function call appears on + the right of the comparison.

The “between” operator creates a BETWEEN clause:

[43] predicate ::= '{' “between” ':' '[' lit_value ',' lit_value ']' '}'  

Although PostgreSQL allows a null value in a BETWEEN clause, json_query + requires literal non-null values. It isn't sensible to use null values in a + BETWEEN clause. A few experiments show that the results of the comparison are + peculiar and erratic.

There are two ways to create an IN list of allowed values. The simplest is + to put literal values into a JSON array:

[44] predicate ::= '[' lit_value { ',' lit_value } ']'  

As with BETWEEN clauses, json_query does not accept null values in an IN list, + even though PostgreSQL does allow them. Nulls are not sensible in this context + because they never match anything.

Having Clause

For the HAVING clause, json_query accepts exactly the same syntax as it accepts for + the WHERE clause.

The other way to create an IN list is to use an explicit + “in” operator with an array of literal values. This format + also works for the “not in” operator:

[45] predicate ::= '{' in_operator ';' '[' lit_value [ ',' lit_value ] ']' '}'  
[46] in_operator ::= “in” | “not in”  

Another kind of IN or NOT IN clause uses a subquery instead of a list of + values:

[47] predicate ::= '{' in_operator ':' query '}'  

The remaining types of predicate can put a function call on + the left of the comparison, by using a transform_spec together + with a “value” tag. The transform_spec is + optional, and if you don't need it, the same SQL would in many cases be easier to + express by other means.

The transform_spec construct was described earlier in + connection with the SELECT clause, but here it is again:

[48] transform_spec ::= + '”transform”' ':' string ]
+ [ ',' '”result_field” ':' string ]
+ [ ',' '”params” ':' param_list ] +
 

As in the SELECT clause, the “transform” string names the + function. The first parameter is always the column identified by the field_name. + Additional parameters, if any, appear in the param_list. The + “result_field” string, if present, identifies one column of a + multicolumn return value.

Here's a second way to compare a value to a literal value (but not to a null + value):

[49] predicate ::= '{' compare_op ':' '{' '”value”' ':' lit_value
+ [ transform_spec ] '}' '}'
 

...and a way to compare a value to a boolean expression:

[50] predicate ::= '{' compare_op ':' '{' '”value”' ':' '{'
+ condition { ',' condition } [ transform_spec ] '}' '}'
 

The final predicate is another way to put a function call on the right side + of the comparison:

[51] predicate ::= '{' compare_op ':' '{' '”value”' ':' '['
+ string { ',' parameter } ']' [ transform_spec ] '}' '}'
 

This format is available for the sake of consistency, but offers no advantage + over the simpler version.

ORDER BY Clause

There are two ways to encode an ORDER BY clause: as an array, or as a list. + Either may be empty, in which case the generated SQL will not include an ORDER BY + clause:

[52] order_by_list ::= '[' ']' | '{' '}'  

If not empty, the array contains one or more objects, each defining a sort + field:

[53] order_by_list ::= '{' sort_field_def { ',' sort_field_def } '}'  
[54] sort_field_def ::= '{'
+ '”class”' ':' class_name
+ ',' '”field”' ':' field_name
+ [ ',' '”direction”' ':' lit_value ]
+ [ ',' transform_spec ]
+ '}'
 

The “class” and “field” entries are + required, and of course the field must belong to the class. Furthermore, at + least one field from the class must appear in the SELECT clause.

The “direction” entry, if present, specifies whether the + sort will be ascending or descending for the column in question. If the associated + value begins with “D” or “d”, the sort will be descending; otherwise the sort will + be ascending. If the value is a number, it will be treated as a string that does not + start with “D” or “d”, resulting in an ascending sort.

In the absence of a “direction” entry, the sort will be + ascending.

The transform_spec works here the same way it works in the + SELECT clause and the WHERE clause, enabling you to pass the column through a + transforming function before the sort:

[55] transform_spec ::= + '”transform”' ':' string ]
+ [ ',' '”result_field” ':' string ]
+ [ ',' '”params” ':' param_list ] +
 

When the order_by_list is an object instead of an array, + the syntax is less verbose, but also less flexible. The keys for the object are + class names:

[56] order_by_list ::= '{' class_name ':' sort_class_def
+ { ',' class_name ':' sort_class_def } '}'
 

Each class must be referenced in the SELECT clause.

As in the SELECT clause, all the fields for a given class must be grouped + together. You can't sort by a column from one table, then a column from a second + table, then another column from the first table. If you need this kind of sort, + you must encode the ORDER BY clause as an array instead of an object.

The data associated with a class_name may be either an array + or an object. If an array, it's simply a list of field names, and each field must + belong to the class:

[57] sort_class_def ::= '[' field_name { ',' field_name } ']'  

With this syntax, the direction of sorting will always be ascending.

If the data is an object, the keys are field names, and as usual the fields + must belong to the class:

[58] sort_class_def ::= '{' field_name ':' sort_class_subdef
+ { ',' field_name ':' sort_class_subdef } '}'
 

Since the field_name is the key for the object, it cannot + appear more than once. As a result, some kinds of sorts are not possible with this + syntax. For example, one might want to sort by UPPER( family_name ), and then by + family_name with case unchanged, to make sure that “diBona” comes before “Dibona”. + For situations like this, you must encode the ORDER BY clause as an array rather + than an object.

The data associated with each field_name may take either of + two forms. In the simplest case, it's a literal value to specify the direction + of sorting:

[59] sort_class_subdef ::= lit_value  

If the literal is a string starting with “D” or “d”, json_query sorts the field + in descending order. Otherwise it sorts the field in ascending order.

In other cases, the field_name may be paired with an object + to specify more details:

[60] sort_class_subdef ::= '{'
+ [ '”direction”' ':' lit_value ]
+ [ transform_spec ]
+ '}'
 

As before, the value tagged as “direction” specifies the + direction of the sort, depending on the first character. If not otherwise + specified, the sort direction defaults to ascending.

Also as before, the transform_spec may specify a function + through which to pass the column.

Since both the “direction” tag and the + transform_spec are optional, the object may be empty:

[61] sort_class_subdef ::= '{' '}'  
\ No newline at end of file -- 2.43.2