├── .gitignore ├── .travis.yml ├── LICENSE ├── META.json ├── Makefile ├── README.md ├── data └── test_jsquery.data ├── expected ├── jsquery.out └── jsquery_1.out ├── jsonb_gin_ops.c ├── jsquery--1.0--1.1.sql ├── jsquery--1.1.sql ├── jsquery.control ├── jsquery.h ├── jsquery_constr.c ├── jsquery_extract.c ├── jsquery_gram.y ├── jsquery_io.c ├── jsquery_op.c ├── jsquery_scan.l ├── jsquery_support.c ├── meson.build ├── sql └── jsquery.sql └── travis ├── dep-ubuntu-llvm.sh ├── dep-ubuntu-postgres.sh ├── llvm-snapshot.gpg.key ├── pg-travis-test.sh └── postgresql.gpg.key /.gitignore: -------------------------------------------------------------------------------- 1 | .deps 2 | /log/ 3 | /results/ 4 | /tmp_check/ 5 | *.so 6 | *.o 7 | jsquery_gram.c 8 | jsquery_scan.c 9 | jsquery_gram.h 10 | regression.diffs 11 | regression.out 12 | results 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: 2 | - linux 3 | 4 | sudo: required 5 | dist: trusty 6 | 7 | language: c 8 | 9 | compiler: 10 | - clang 11 | - gcc 12 | 13 | before_install: 14 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get -y install -qq wget ca-certificates; fi 15 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then source ./travis/dep-ubuntu-postgres.sh; fi 16 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then source ./travis/dep-ubuntu-llvm.sh; fi 17 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get update -qq; fi 18 | 19 | env: 20 | global: 21 | - LLVM_VER=4.0 22 | matrix: 23 | - PG_VER=11 CHECK_TYPE=normal 24 | - PG_VER=11 CHECK_TYPE=static 25 | - PG_VER=10 CHECK_TYPE=normal 26 | - PG_VER=10 CHECK_TYPE=static 27 | - PG_VER=10.5 CHECK_TYPE=valgrind 28 | - PG_VER=9.6 CHECK_TYPE=normal 29 | - PG_VER=9.6 CHECK_TYPE=static 30 | - PG_VER=9.5 CHECK_TYPE=normal 31 | - PG_VER=9.5 CHECK_TYPE=static 32 | - PG_VER=9.4 CHECK_TYPE=normal 33 | - PG_VER=9.4 CHECK_TYPE=static 34 | 35 | script: bash ./travis/pg-travis-test.sh 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | JsQuery is released under the PostgreSQL License, a liberal Open Source license, similar to the BSD or MIT licenses. 2 | 3 | Copyright (c) 2014-2018, Postgres Professional 4 | Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group 5 | Portions Copyright (c) 1994, The Regents of the University of California 6 | 7 | Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is hereby granted, provided that the above copyright notice and this paragraph and the following two paragraphs appear in all copies. 8 | 9 | IN NO EVENT SHALL POSTGRES PROFESSIONAL BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF POSTGRES PROFESSIONAL HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 10 | 11 | POSTGRES PROFESSIONAL SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND POSTGRES PROFESSIONAL HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. 12 | -------------------------------------------------------------------------------- /META.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "JsQuery", 3 | "abstract": "JSON Query Language with GIN indexing support", 4 | "description": "JsQuery provides additional functionality for JSONB, such as a simple and effective way to search in nested objects and arrays, and more comparison operators with index support. It does this by implementing a specialized search syntax, the @@ operator, and the jsquery type for search strings.", 5 | "version": "1.1.1", 6 | "maintainer": [ 7 | "Teodor Sigaev ", 8 | "Alexander Korotkov ", 9 | "Oleg Bartunov " 10 | ], 11 | "license": { 12 | "PostgreSQL": "http://www.postgresql.org/about/licence" 13 | }, 14 | "prereqs": { 15 | "runtime": { 16 | "requires": { 17 | "PostgreSQL": "9.4" 18 | }, 19 | "recommends": { 20 | "PostgreSQL": "10.0" 21 | } 22 | } 23 | }, 24 | "provides": { 25 | "jsquery": { 26 | "file": "sql/jsquery.sql", 27 | "docfile": "README.md", 28 | "version": "1.1.1", 29 | "abstract": "JSON query language with GIN indexing support" 30 | } 31 | }, 32 | "resources": { 33 | "homepage": "https://github.com/postgrespro/jsquery", 34 | "bugtracker": { 35 | "web": "https://github.com/postgrespro/jsquery/issues" 36 | }, 37 | "repository": { 38 | "url": "https://github.com/postgrespro/jsquery.git", 39 | "web": "https://github.com/postgrespro/jsquery", 40 | "type": "git" 41 | } 42 | }, 43 | "generated_by": "Josh Berkus", 44 | "meta-spec": { 45 | "version": "1.1.1", 46 | "url": "http://pgxn.org/meta/spec.txt" 47 | }, 48 | "tags": [ 49 | "JSON", 50 | "index", 51 | "search" 52 | ] 53 | } 54 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # contrib/jsquery/Makefile 2 | 3 | MODULE_big = jsquery 4 | OBJS = jsonb_gin_ops.o jsquery_constr.o jsquery_extract.o \ 5 | jsquery_gram.o jsquery_io.o jsquery_op.o jsquery_support.o 6 | 7 | EXTENSION = jsquery 8 | DATA = jsquery--1.1.sql jsquery--1.0--1.1.sql 9 | INCLUDES = jsquery.h 10 | 11 | REGRESS = jsquery 12 | # We need a UTF8 database 13 | ENCODING = UTF8 14 | 15 | EXTRA_CLEAN = y.tab.c y.tab.h \ 16 | jsquery_gram.c jsquery_scan.c jsquery_gram.h 17 | 18 | ifdef USE_PGXS 19 | PG_CONFIG ?= pg_config 20 | PGXS := $(shell $(PG_CONFIG) --pgxs) 21 | include $(PGXS) 22 | else 23 | subdir = contrib/jsquery 24 | top_builddir = ../.. 25 | include $(top_builddir)/src/Makefile.global 26 | include $(top_srcdir)/contrib/contrib-global.mk 27 | endif 28 | 29 | ifdef USE_ASSERT_CHECKING 30 | override CFLAGS += -DUSE_ASSERT_CHECKING 31 | endif 32 | 33 | jsquery_gram.o: jsquery_scan.c 34 | 35 | jsquery_gram.c: BISONFLAGS += -d 36 | 37 | distprep: jsquery_gram.c jsquery_scan.c 38 | 39 | maintainer-clean: 40 | rm -f jsquery_gram.c jsquery_scan.c jsquery_gram.h 41 | 42 | install: installincludes 43 | 44 | installincludes: 45 | $(INSTALL_DATA) $(addprefix $(srcdir)/, $(INCLUDES)) '$(DESTDIR)$(includedir_server)/' 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/postgrespro/jsquery.svg?branch=master)](https://travis-ci.org/postgrespro/jsquery) 2 | [![codecov](https://codecov.io/gh/postgrespro/jsquery/branch/master/graph/badge.svg)](https://codecov.io/gh/postgrespro/jsquery) 3 | [![GitHub license](https://img.shields.io/badge/license-PostgreSQL-blue.svg)](https://raw.githubusercontent.com/postgrespro/jsquery/master/LICENSE) 4 | 5 | JsQuery – json query language with GIN indexing support 6 | ======================================================= 7 | 8 | Introduction 9 | ------------ 10 | 11 | JsQuery – is a language to query jsonb data type, introduced in PostgreSQL 12 | release 9.4. 13 | 14 | It's primary goal is to provide an additional functionality to jsonb 15 | (currently missing in PostgreSQL), such as a simple and effective way 16 | to search in nested objects and arrays, more comparison operators with 17 | indexes support. We hope, that jsquery will be eventually a part of 18 | PostgreSQL. 19 | 20 | Jsquery is released as jsquery data type (similar to tsquery) and @@ 21 | match operator for jsonb. 22 | 23 | Authors 24 | ------- 25 | 26 | * Teodor Sigaev , Postgres Professional, Moscow, Russia 27 | * Alexander Korotkov , Postgres Professional, Moscow, Russia 28 | * Oleg Bartunov , Postgres Professional, Moscow, Russia 29 | 30 | Availability 31 | ------------ 32 | 33 | JsQuery is realized as an extension and not available in default PostgreSQL 34 | installation. It is available from 35 | [github](https://github.com/postgrespro/jsquery) 36 | under the same license as 37 | [PostgreSQL](https://www.postgresql.org/about/licence/) 38 | and supports PostgreSQL 9.4+. 39 | 40 | Regards 41 | ------- 42 | 43 | Development was sponsored by [Wargaming.net](http://wargaming.net). 44 | 45 | Installation 46 | ------------ 47 | 48 | JsQuery is PostgreSQL extension which requires PostgreSQL 9.4 or higher. 49 | Before build and install you should ensure following: 50 | 51 | * PostgreSQL version is 9.4 or higher. 52 | * You have development package of PostgreSQL installed or you built 53 | PostgreSQL from source. 54 | * You have flex and bison installed on your system. JsQuery was tested on 55 | flex 2.5.37-2.5.39, bison 2.7.12. 56 | * Your PATH variable is configured so that pg\_config command available, or set PG_CONFIG variable. 57 | 58 | Typical installation procedure may look like this: 59 | 60 | $ git clone https://github.com/postgrespro/jsquery.git 61 | $ cd jsquery 62 | $ make USE_PGXS=1 63 | $ sudo make USE_PGXS=1 install 64 | $ make USE_PGXS=1 installcheck 65 | $ psql DB -c "CREATE EXTENSION jsquery;" 66 | 67 | JSON query language 68 | ------------------- 69 | 70 | JsQuery extension contains `jsquery` datatype which represents whole JSON query 71 | as a single value (like `tsquery` does for fulltext search). The query is an 72 | expression on JSON-document values. 73 | 74 | Simple expression is specified as `path binary_operator value` or 75 | `path unary_operator`. See following examples. 76 | 77 | * `x = "abc"` – value of key "x" is equal to "abc"; 78 | * `$ @> [4, 5, "zzz"]` – the JSON document is an array containing values 79 | 4, 5 and "zzz"; 80 | * `"abc xyz" >= 10` – value of key "abc xyz" is greater than or equal to 10; 81 | * `volume IS NUMERIC` – type of key "volume" is numeric. 82 | * `$ = true` – the whole JSON document is just a true. 83 | * `similar_ids.@# > 5` – similar\_ids is an array or object of length greater 84 | than 5; 85 | * `similar_product_ids.# = "0684824396"` – array "similar\_product\_ids" 86 | contains string "0684824396". 87 | * `*.color = "red"` – there is object somewhere which key "color" has value "red". 88 | * `foo = *` – key "foo" exists in object. 89 | 90 | Path selects a set of JSON values to be checked using given operators. In 91 | the simplest case path is just a key name. In general path is key names and 92 | placeholders combined by dot signs. Path can use the following placeholders: 93 | 94 | * `#` – any index of an array; 95 | * `#N` – N-th index of an array; 96 | * `%` – any key of an object; 97 | * `*` – any sequence of array indexes and object keys; 98 | * `@#` – length of array or object, may only be used as the last component of 99 | a path; 100 | * `$` – the whole JSON document as single value, may only be the whole path. 101 | 102 | Expression is true when operator is true against at least one value selected 103 | by path. 104 | 105 | Key names could be given either with or without double quotes. Key names 106 | without double quotes may not contain spaces, start with a number or match 107 | a jsquery keyword. 108 | 109 | The supported binary operators are: 110 | 111 | * Equality operator: `=`; 112 | * Numeric comparison operators: `>`, `>=`, `<`, `<=`; 113 | * Search in the list of scalar values using `IN` operator; 114 | * Array comparison operators: `&&` (overlap), `@>` (contains), 115 | `<@` (contained in). 116 | 117 | The supported unary operators are: 118 | 119 | * Check for existence operator: `= *`; 120 | * Check for type operators: `IS ARRAY`, `IS NUMERIC`, `IS OBJECT`, `IS STRING` 121 | and `IS BOOLEAN`. 122 | 123 | Expressions can be complex. Complex expression is a set of expressions 124 | combined by logical operators (`AND`, `OR`, `NOT`) and grouped using braces. 125 | 126 | Examples of complex expressions: 127 | 128 | * `a = 1 AND (b = 2 OR c = 3) AND NOT d = 1` 129 | * `x.% = true OR x.# = true` 130 | 131 | Prefix expressions are expressions given in the form `path (subexpression)`. 132 | In this case path selects JSON values to be checked using the given subexpression. 133 | Check results are aggregated in the same way as in simple expressions. 134 | 135 | * `#(a = 1 AND b = 2)` – exists element of array which a key is 1 and b key is 2 136 | * `%($ >= 10 AND $ <= 20)` – exists object key which values is between 10 and 20 137 | 138 | Path can also contain the following special placeholders with "every" semantics: 139 | 140 | * `#:` – every index of an array; 141 | * `%:` – every key of an object; 142 | * `*:` – every sequence of array indexes and object keys. 143 | 144 | Consider following example. 145 | 146 | %.#:($ >= 0 AND $ <= 1) 147 | 148 | This example could be read as following: there is at least one key whose value 149 | is an array of numerics between 0 and 1. 150 | 151 | We can rewrite this example in the following form with extra braces: 152 | 153 | %(#:($ >= 0 AND $ <= 1)) 154 | 155 | The first placeholder `%` checks that the expression in braces is true for at least 156 | one value in the object. The second placeholder `#:` checks if the value is an array 157 | and that all its elements satisfy the expressions in braces. 158 | 159 | We can rewrite this example without the `#:` placeholder as follows: 160 | 161 | %(NOT #(NOT ($ >= 0 AND $ <= 1)) AND $ IS ARRAY) 162 | 163 | In this example we transform the assertion that every element of array satisfy some 164 | condition to an assertion that there are no elements which don't satisfy the same 165 | condition. 166 | 167 | Some examples of using paths: 168 | 169 | * `numbers.#: IS NUMERIC` – every element of "numbers" array is numeric. 170 | * `*:($ IS OBJECT OR $ IS BOOLEAN)` – JSON is a structure of nested objects 171 | with booleans as leaf values. 172 | * `#:.%:($ >= 0 AND $ <= 1)` – each element of array is an object containing 173 | only numeric values between 0 and 1. 174 | * `documents.#:.% = *` – "documents" is an array of objects containing at least 175 | one key. 176 | * `%.#: ($ IS STRING)` – JSON object contains at least one array of strings. 177 | * `#.% = true` – at least one array element is an object which contains at least 178 | one "true" value. 179 | 180 | The use of path operators and braces need some further explanation. When the same path 181 | operators are used multiple times, they may refer to different values. If you want them 182 | to always refer to the same value, you must use braces and the `$` operator. For example: 183 | 184 | * `# < 10 AND # > 20` – an element less than 10 exists, and another element 185 | greater than 20 exists. 186 | * `#($ < 10 AND $ > 20)` – an element which is both less than 10 and greater 187 | than 20 exists (impossible). 188 | * `#($ >= 10 AND $ <= 20)` – an element between 10 and 20 exists. 189 | * `# >= 10 AND # <= 20` – an element greater or equal to 10 exists, and another 190 | element less or equal to 20 exists. Please note that this query also can be 191 | satisfied by an array with no elements between 10 and 20, for instance [0,30]. 192 | 193 | Same rules apply when searching inside objects and branch structures. 194 | 195 | Type checking operators and "every" placeholders are useful for document 196 | schema validation. JsQuery matchig operator `@@` is immutable and can be used 197 | in CHECK constraint. See following example. 198 | 199 | ```sql 200 | CREATE TABLE js ( 201 | id serial, 202 | data jsonb, 203 | CHECK (data @@ ' 204 | name IS STRING AND 205 | similar_ids.#: IS NUMERIC AND 206 | points.#:(x IS NUMERIC AND y IS NUMERIC)'::jsquery)); 207 | ``` 208 | 209 | In this example the check constraint validates that in the "data" jsonb column 210 | the value of the "name" key is a string, the value of the "similar_ids" key is an array of numerics, 211 | and the value of the "points" key is an array of objects which contain numeric values in 212 | "x" and "y" keys. 213 | 214 | See our 215 | [pgconf.eu presentation](http://www.sai.msu.su/~megera/postgres/talks/pgconfeu-2014-jsquery.pdf) 216 | for more examples. 217 | 218 | GIN indexes 219 | ----------- 220 | 221 | JsQuery extension contains two operator classes (opclasses) for GIN which 222 | provide different kinds of query optimization. 223 | 224 | * jsonb\_path\_value\_ops 225 | * jsonb\_value\_path\_ops 226 | 227 | In each of two GIN opclasses jsonb documents are decomposed into entries. Each 228 | entry is associated with a particular value and its path. The difference between 229 | opclasses is in the entry representation, comparison and usage for search 230 | optimization. 231 | 232 | For example, the jsonb document 233 | `{"a": [{"b": "xyz", "c": true}, 10], "d": {"e": [7, false]}}` 234 | would be decomposed into following entries: 235 | 236 | * "a".#."b"."xyz" 237 | * "a".#."c".true 238 | * "a".#.10 239 | * "d"."e".#.7 240 | * "d"."e".#.false 241 | 242 | Since JsQuery doesn't support searching in a particular array index, we consider 243 | all array elements to be equivalent. Thus, each array element is marked with 244 | the same `#` sign in its path. 245 | 246 | Major problem in the entries representation is its size. In the given example 247 | the key "a" is presented three times. In large branchy documents with long 248 | keys sizes of naive entries, the representation becomes unreasonably large. 249 | Both opclasses address this issue, but in slightly different ways. 250 | 251 | ### jsonb\_path\_value\_ops 252 | 253 | jsonb\_path\_value\_ops represents entry as pair of path hash and value. 254 | Following pseudocode illustrates it: 255 | 256 | (hash(path_item_1.path_item_2. ... .path_item_n); value) 257 | 258 | When comparison entries, the path hash is the higher part of entry and the value is 259 | the lower part. This determines the features of this opclass. Since the path 260 | is hashed and it's the higher part of the entry, we need to know the full path to 261 | a value in order to use the it for searching. However, once the path is specified 262 | we can use both exact and range searches very efficiently. 263 | 264 | ### jsonb\_value\_path\_ops 265 | 266 | jsonb\_value\_path\_ops represents entry as pair of the value and a bloom filter 267 | of paths: 268 | 269 | (value; bloom(path_item_1) | bloom(path_item_2) | ... | bloom(path_item_n)) 270 | 271 | In comparison of entries value is the higher part of entry and bloom filter of 272 | path is its lower part. This determines the features of this opclass. Since 273 | the value is the higher part of an entry, we can only perform exact value search 274 | effectively. A search over a range of values is possible as well, but we have to 275 | filter all the the different paths where matching values occur. The Bloom filter 276 | over path items allows the index to be used for conditions containing `%` and `*` in 277 | their paths. 278 | 279 | ### Query optimization 280 | 281 | JsQuery opclasses perform complex query optimization. It's valuable for a 282 | developer or administrator to see the result of such optimization. 283 | Unfortunately, opclasses aren't allowed to put any custom output in an 284 | EXPLAIN. That's why JsQuery provides these functions to let you see 285 | how particular opclass optimizes given query: 286 | 287 | * gin\_debug\_query\_path\_value(jsquery) – for jsonb\_path\_value\_ops 288 | * gin\_debug\_query\_value\_path(jsquery) – for jsonb\_value\_path\_ops 289 | 290 | The result of these functions is a textual representation of the query tree 291 | where leaves are GIN search entries. Following examples show different results of 292 | query optimization by different opclasses: 293 | 294 | # SELECT gin_debug_query_path_value('x = 1 AND (*.y = 1 OR y = 2)'); 295 | gin_debug_query_path_value 296 | ---------------------------- 297 | x = 1 , entry 0 + 298 | 299 | # SELECT gin_debug_query_value_path('x = 1 AND (*.y = 1 OR y = 2)'); 300 | gin_debug_query_value_path 301 | ---------------------------- 302 | AND + 303 | x = 1 , entry 0 + 304 | OR + 305 | *.y = 1 , entry 1 + 306 | y = 2 , entry 2 + 307 | 308 | Unfortunately, jsonb have no statistics yet. That's why JsQuery optimizer has 309 | to do imperative decision while selecting conditions to be evaluated using 310 | index. This decision is made by assuming that some condition types are less 311 | selective than others. The optimizer divides conditions into following selectivity 312 | classes (listed in descending order of selectivity): 313 | 314 | 1. Equality (x = c) 315 | 2. Range (c1 < x < c2) 316 | 3. Inequality (x > c) 317 | 4. Is (x is type) 318 | 5. Any (x = \*) 319 | 320 | The optimizer avoids index evaluation of less selective conditions when possible. 321 | For example, in the `x = 1 AND y > 0` query `x = 1` is assumed to be more 322 | selective than `y > 0`. That's why the index isn't used for evaluation of `y > 0`. 323 | 324 | # SELECT gin_debug_query_path_value('x = 1 AND y > 0'); 325 | gin_debug_query_path_value 326 | ---------------------------- 327 | x = 1 , entry 0 + 328 | 329 | With the lack of statistics, decisions made by optimizer can be inaccurate. That's 330 | why JsQuery supports hints. The comments `/*-- index */` or `/*-- noindex */` 331 | placed in the conditions force the optimizer to use or not use an index 332 | correspondingly: 333 | 334 | SELECT gin_debug_query_path_value('x = 1 AND y /*-- index */ > 0'); 335 | gin_debug_query_path_value 336 | ---------------------------- 337 | AND + 338 | x = 1 , entry 0 + 339 | y > 0 , entry 1 + 340 | 341 | SELECT gin_debug_query_path_value('x /*-- noindex */ = 1 AND y > 0'); 342 | gin_debug_query_path_value 343 | ---------------------------- 344 | y > 0 , entry 0 + 345 | 346 | Contribution 347 | ------------ 348 | 349 | Please note that JsQuery is still under development. While it's 350 | stable and tested, it may contain some bugs. Don't hesitate to create 351 | [issues at github](https://github.com/postgrespro/jsquery/issues) with your 352 | bug reports. 353 | 354 | If there's some functionality you'd like to see added to JsQuery and you feel 355 | like you can implement it, then you're welcome to make pull requests. 356 | 357 | -------------------------------------------------------------------------------- /jsquery--1.0--1.1.sql: -------------------------------------------------------------------------------- 1 | CREATE FUNCTION json_jsquery_filter(jsonb, jsquery) 2 | RETURNS jsonb 3 | AS 'MODULE_PATHNAME' 4 | LANGUAGE C STRICT IMMUTABLE; 5 | 6 | CREATE OPERATOR ~~ ( 7 | LEFTARG = jsonb, 8 | RIGHTARG = jsquery, 9 | PROCEDURE = json_jsquery_filter 10 | ); 11 | 12 | -------------------------------------------------------------------------------- /jsquery--1.1.sql: -------------------------------------------------------------------------------- 1 | -- complain if script is sourced in psql, rather than via CREATE EXTENSION 2 | \echo Use "CREATE EXTENSION jsquery" to load this file. \quit 3 | 4 | CREATE TYPE jsquery; 5 | 6 | CREATE FUNCTION jsquery_in(cstring) 7 | RETURNS jsquery 8 | AS 'MODULE_PATHNAME' 9 | LANGUAGE C STRICT IMMUTABLE; 10 | 11 | CREATE FUNCTION jsquery_out(jsquery) 12 | RETURNS cstring 13 | AS 'MODULE_PATHNAME' 14 | LANGUAGE C STRICT IMMUTABLE; 15 | 16 | CREATE TYPE jsquery ( 17 | INTERNALLENGTH = -1, 18 | INPUT = jsquery_in, 19 | OUTPUT = jsquery_out, 20 | STORAGE = extended 21 | ); 22 | 23 | CREATE FUNCTION jsquery_json_exec(jsquery, jsonb) 24 | RETURNS bool 25 | AS 'MODULE_PATHNAME' 26 | LANGUAGE C STRICT IMMUTABLE; 27 | 28 | CREATE FUNCTION json_jsquery_exec(jsonb, jsquery) 29 | RETURNS bool 30 | AS 'MODULE_PATHNAME' 31 | LANGUAGE C STRICT IMMUTABLE; 32 | 33 | CREATE OPERATOR @@ ( 34 | LEFTARG = jsquery, 35 | RIGHTARG = jsonb, 36 | PROCEDURE = jsquery_json_exec, 37 | COMMUTATOR = '@@', 38 | RESTRICT = contsel, 39 | JOIN = contjoinsel 40 | ); 41 | 42 | CREATE OPERATOR @@ ( 43 | LEFTARG = jsonb, 44 | RIGHTARG = jsquery, 45 | PROCEDURE = json_jsquery_exec, 46 | COMMUTATOR = '@@', 47 | RESTRICT = contsel, 48 | JOIN = contjoinsel 49 | ); 50 | 51 | CREATE FUNCTION json_jsquery_filter(jsonb, jsquery) 52 | RETURNS jsonb 53 | AS 'MODULE_PATHNAME' 54 | LANGUAGE C STRICT IMMUTABLE; 55 | 56 | CREATE OPERATOR ~~ ( 57 | LEFTARG = jsonb, 58 | RIGHTARG = jsquery, 59 | PROCEDURE = json_jsquery_filter 60 | ); 61 | 62 | CREATE FUNCTION jsquery_join_and(jsquery, jsquery) 63 | RETURNS jsquery 64 | AS 'MODULE_PATHNAME' 65 | LANGUAGE C STRICT IMMUTABLE; 66 | 67 | CREATE OPERATOR & ( 68 | LEFTARG = jsquery, 69 | RIGHTARG = jsquery, 70 | PROCEDURE = jsquery_join_and, 71 | COMMUTATOR = '&' 72 | ); 73 | 74 | CREATE FUNCTION jsquery_join_or(jsquery, jsquery) 75 | RETURNS jsquery 76 | AS 'MODULE_PATHNAME' 77 | LANGUAGE C STRICT IMMUTABLE; 78 | 79 | CREATE OPERATOR | ( 80 | LEFTARG = jsquery, 81 | RIGHTARG = jsquery, 82 | PROCEDURE = jsquery_join_or, 83 | COMMUTATOR = '|' 84 | ); 85 | 86 | CREATE FUNCTION jsquery_not(jsquery) 87 | RETURNS jsquery 88 | AS 'MODULE_PATHNAME' 89 | LANGUAGE C STRICT IMMUTABLE; 90 | 91 | CREATE OPERATOR ! ( 92 | RIGHTARG = jsquery, 93 | PROCEDURE = jsquery_not 94 | ); 95 | 96 | CREATE FUNCTION jsquery_lt(jsquery, jsquery) 97 | RETURNS bool 98 | AS 'MODULE_PATHNAME' 99 | LANGUAGE C STRICT IMMUTABLE; 100 | 101 | CREATE FUNCTION jsquery_le(jsquery, jsquery) 102 | RETURNS bool 103 | AS 'MODULE_PATHNAME' 104 | LANGUAGE C STRICT IMMUTABLE; 105 | 106 | CREATE FUNCTION jsquery_eq(jsquery, jsquery) 107 | RETURNS bool 108 | AS 'MODULE_PATHNAME' 109 | LANGUAGE C STRICT IMMUTABLE; 110 | 111 | CREATE FUNCTION jsquery_ne(jsquery, jsquery) 112 | RETURNS bool 113 | AS 'MODULE_PATHNAME' 114 | LANGUAGE C STRICT IMMUTABLE; 115 | 116 | CREATE FUNCTION jsquery_ge(jsquery, jsquery) 117 | RETURNS bool 118 | AS 'MODULE_PATHNAME' 119 | LANGUAGE C STRICT IMMUTABLE; 120 | 121 | CREATE FUNCTION jsquery_gt(jsquery, jsquery) 122 | RETURNS bool 123 | AS 'MODULE_PATHNAME' 124 | LANGUAGE C STRICT IMMUTABLE; 125 | 126 | CREATE OPERATOR < ( 127 | LEFTARG = jsquery, 128 | RIGHTARG = jsquery, 129 | PROCEDURE = jsquery_lt, 130 | COMMUTATOR = '>', 131 | NEGATOR = '>=', 132 | RESTRICT = scalarltsel, 133 | JOIN = scalarltjoinsel 134 | ); 135 | 136 | CREATE OPERATOR <= ( 137 | LEFTARG = jsquery, 138 | RIGHTARG = jsquery, 139 | PROCEDURE = jsquery_le, 140 | COMMUTATOR = '>=', 141 | NEGATOR = '>', 142 | RESTRICT = scalarltsel, 143 | JOIN = scalarltjoinsel 144 | ); 145 | 146 | CREATE OPERATOR = ( 147 | LEFTARG = jsquery, 148 | RIGHTARG = jsquery, 149 | PROCEDURE = jsquery_eq, 150 | COMMUTATOR = '=', 151 | NEGATOR = '<>', 152 | RESTRICT = eqsel, 153 | JOIN = eqjoinsel, 154 | HASHES, 155 | MERGES 156 | ); 157 | 158 | CREATE OPERATOR <> ( 159 | LEFTARG = jsquery, 160 | RIGHTARG = jsquery, 161 | PROCEDURE = jsquery_ne, 162 | COMMUTATOR = '<>', 163 | NEGATOR = '=', 164 | RESTRICT = neqsel, 165 | JOIN = neqjoinsel 166 | ); 167 | 168 | CREATE OPERATOR >= ( 169 | LEFTARG = jsquery, 170 | RIGHTARG = jsquery, 171 | PROCEDURE = jsquery_ge, 172 | COMMUTATOR = '<=', 173 | NEGATOR = '<', 174 | RESTRICT = scalargtsel, 175 | JOIN = scalargtjoinsel 176 | ); 177 | 178 | CREATE OPERATOR > ( 179 | LEFTARG = jsquery, 180 | RIGHTARG = jsquery, 181 | PROCEDURE = jsquery_gt, 182 | COMMUTATOR = '<', 183 | NEGATOR = '<=', 184 | RESTRICT = scalargtsel, 185 | JOIN = scalargtjoinsel 186 | ); 187 | 188 | CREATE FUNCTION jsquery_cmp(jsquery, jsquery) 189 | RETURNS int4 190 | AS 'MODULE_PATHNAME' 191 | LANGUAGE C STRICT IMMUTABLE; 192 | 193 | CREATE OPERATOR CLASS jsquery_ops 194 | DEFAULT FOR TYPE jsquery USING btree AS 195 | OPERATOR 1 < , 196 | OPERATOR 2 <= , 197 | OPERATOR 3 = , 198 | OPERATOR 4 >= , 199 | OPERATOR 5 >, 200 | FUNCTION 1 jsquery_cmp(jsquery, jsquery); 201 | 202 | CREATE FUNCTION jsquery_hash(jsquery) 203 | RETURNS int4 204 | AS 'MODULE_PATHNAME' 205 | LANGUAGE C STRICT IMMUTABLE; 206 | 207 | CREATE OPERATOR CLASS jsquery_ops 208 | DEFAULT FOR TYPE jsquery USING hash AS 209 | OPERATOR 1 =, 210 | FUNCTION 1 jsquery_hash(jsquery); 211 | 212 | CREATE OR REPLACE FUNCTION gin_compare_jsonb_value_path(bytea, bytea) 213 | RETURNS integer 214 | AS 'MODULE_PATHNAME' 215 | LANGUAGE C STRICT IMMUTABLE; 216 | 217 | CREATE OR REPLACE FUNCTION gin_compare_partial_jsonb_value_path(bytea, bytea, smallint, internal) 218 | RETURNS integer 219 | AS 'MODULE_PATHNAME' 220 | LANGUAGE C STRICT IMMUTABLE; 221 | 222 | CREATE OR REPLACE FUNCTION gin_extract_jsonb_value_path(internal, internal, internal) 223 | RETURNS internal 224 | AS 'MODULE_PATHNAME' 225 | LANGUAGE C STRICT IMMUTABLE; 226 | 227 | CREATE OR REPLACE FUNCTION gin_extract_jsonb_query_value_path(anyarray, internal, smallint, internal, internal, internal, internal) 228 | RETURNS internal 229 | AS 'MODULE_PATHNAME' 230 | LANGUAGE C STRICT IMMUTABLE; 231 | 232 | CREATE OR REPLACE FUNCTION gin_consistent_jsonb_value_path(internal, smallint, anyarray, integer, internal, internal, internal, internal) 233 | RETURNS boolean 234 | AS 'MODULE_PATHNAME' 235 | LANGUAGE C STRICT IMMUTABLE; 236 | 237 | CREATE OR REPLACE FUNCTION gin_triconsistent_jsonb_value_path(internal, smallint, anyarray, integer, internal, internal, internal) 238 | RETURNS boolean 239 | AS 'MODULE_PATHNAME' 240 | LANGUAGE C STRICT IMMUTABLE; 241 | 242 | CREATE OPERATOR CLASS jsonb_value_path_ops 243 | FOR TYPE jsonb USING gin AS 244 | OPERATOR 7 @>, 245 | OPERATOR 14 @@ (jsonb, jsquery), 246 | FUNCTION 1 gin_compare_jsonb_value_path(bytea, bytea), 247 | FUNCTION 2 gin_extract_jsonb_value_path(internal, internal, internal), 248 | FUNCTION 3 gin_extract_jsonb_query_value_path(anyarray, internal, smallint, internal, internal, internal, internal), 249 | FUNCTION 4 gin_consistent_jsonb_value_path(internal, smallint, anyarray, integer, internal, internal, internal, internal), 250 | FUNCTION 5 gin_compare_partial_jsonb_value_path(bytea, bytea, smallint, internal), 251 | FUNCTION 6 gin_triconsistent_jsonb_value_path(internal, smallint, anyarray, integer, internal, internal, internal), 252 | STORAGE bytea; 253 | 254 | CREATE OR REPLACE FUNCTION gin_compare_jsonb_path_value(bytea, bytea) 255 | RETURNS integer 256 | AS 'MODULE_PATHNAME' 257 | LANGUAGE C STRICT IMMUTABLE; 258 | 259 | CREATE OR REPLACE FUNCTION gin_compare_partial_jsonb_path_value(bytea, bytea, smallint, internal) 260 | RETURNS integer 261 | AS 'MODULE_PATHNAME' 262 | LANGUAGE C STRICT IMMUTABLE; 263 | 264 | CREATE OR REPLACE FUNCTION gin_extract_jsonb_path_value(internal, internal, internal) 265 | RETURNS internal 266 | AS 'MODULE_PATHNAME' 267 | LANGUAGE C STRICT IMMUTABLE; 268 | 269 | CREATE OR REPLACE FUNCTION gin_extract_jsonb_query_path_value(anyarray, internal, smallint, internal, internal, internal, internal) 270 | RETURNS internal 271 | AS 'MODULE_PATHNAME' 272 | LANGUAGE C STRICT IMMUTABLE; 273 | 274 | CREATE OR REPLACE FUNCTION gin_consistent_jsonb_path_value(internal, smallint, anyarray, integer, internal, internal, internal, internal) 275 | RETURNS boolean 276 | AS 'MODULE_PATHNAME' 277 | LANGUAGE C STRICT IMMUTABLE; 278 | 279 | CREATE OR REPLACE FUNCTION gin_triconsistent_jsonb_path_value(internal, smallint, anyarray, integer, internal, internal, internal) 280 | RETURNS boolean 281 | AS 'MODULE_PATHNAME' 282 | LANGUAGE C STRICT IMMUTABLE; 283 | 284 | CREATE OPERATOR CLASS jsonb_path_value_ops 285 | FOR TYPE jsonb USING gin AS 286 | OPERATOR 7 @>, 287 | OPERATOR 14 @@ (jsonb, jsquery), 288 | FUNCTION 1 gin_compare_jsonb_path_value(bytea, bytea), 289 | FUNCTION 2 gin_extract_jsonb_path_value(internal, internal, internal), 290 | FUNCTION 3 gin_extract_jsonb_query_path_value(anyarray, internal, smallint, internal, internal, internal, internal), 291 | FUNCTION 4 gin_consistent_jsonb_path_value(internal, smallint, anyarray, integer, internal, internal, internal, internal), 292 | FUNCTION 5 gin_compare_partial_jsonb_path_value(bytea, bytea, smallint, internal), 293 | FUNCTION 6 gin_triconsistent_jsonb_path_value(internal, smallint, anyarray, integer, internal, internal, internal), 294 | STORAGE bytea; 295 | 296 | CREATE OR REPLACE FUNCTION gin_debug_query_value_path(jsquery) 297 | RETURNS text 298 | AS 'MODULE_PATHNAME' 299 | LANGUAGE C STRICT IMMUTABLE; 300 | 301 | CREATE OR REPLACE FUNCTION gin_debug_query_path_value(jsquery) 302 | RETURNS text 303 | AS 'MODULE_PATHNAME' 304 | LANGUAGE C STRICT IMMUTABLE; 305 | -------------------------------------------------------------------------------- /jsquery.control: -------------------------------------------------------------------------------- 1 | # jsquery extension 2 | comment = 'data type for jsonb inspection' 3 | default_version = '1.1' 4 | module_pathname = '$libdir/jsquery' 5 | relocatable = true 6 | 7 | -------------------------------------------------------------------------------- /jsquery.h: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * jsquery.h 4 | * Definitions of jsquery datatype 5 | * 6 | * Copyright (c) 2014, PostgreSQL Global Development Group 7 | * Portions Copyright (c) 2017-2021, Postgres Professional 8 | * Author: Teodor Sigaev 9 | * 10 | * IDENTIFICATION 11 | * contrib/jsquery/jsquery.h 12 | * 13 | *------------------------------------------------------------------------- 14 | */ 15 | 16 | #ifndef __JSQUERY_H__ 17 | #define __JSQUERY_H__ 18 | 19 | #include "access/gin.h" 20 | #include "fmgr.h" 21 | #include "utils/numeric.h" 22 | #include "utils/jsonb.h" 23 | 24 | typedef struct 25 | { 26 | int32 vl_len_; /* varlena header (do not touch directly!) */ 27 | } JsQuery; 28 | 29 | #define DatumGetJsQueryP(d) ((JsQuery*) PG_DETOAST_DATUM(d)) 30 | #define PG_GETARG_JSQUERY(x) DatumGetJsQueryP(PG_GETARG_DATUM(x)) 31 | #define PG_RETURN_JSQUERY(p) PG_RETURN_POINTER(p) 32 | 33 | typedef enum JsQueryItemType { 34 | jqiNull = jbvNull, 35 | jqiString = jbvString, 36 | jqiNumeric = jbvNumeric, 37 | jqiBool = jbvBool, 38 | jqiArray = jbvArray, 39 | jqiAnd, 40 | jqiOr, 41 | jqiNot, 42 | jqiEqual, 43 | jqiLess, 44 | jqiGreater, 45 | jqiLessOrEqual, 46 | jqiGreaterOrEqual, 47 | jqiContains, 48 | jqiContained, 49 | jqiOverlap, 50 | jqiAny, 51 | jqiAnyArray, 52 | jqiAnyKey, 53 | jqiAll, 54 | jqiAllArray, 55 | jqiAllKey, 56 | jqiKey, 57 | jqiCurrent, 58 | jqiLength, 59 | jqiIn, 60 | jqiIs, 61 | jqiIndexArray, 62 | jqiFilter 63 | } JsQueryItemType; 64 | 65 | /* 66 | * JsQueryHint is stored in the same byte as JsQueryItemType so 67 | * JsQueryItemType should not use three high bits 68 | */ 69 | typedef enum JsQueryHint { 70 | jsqIndexDefault = 0x00, 71 | jsqForceIndex = 0x80, 72 | jsqNoIndex = 0x40 73 | } JsQueryHint; 74 | 75 | #define JSQ_HINT_MASK (jsqIndexDefault | jsqForceIndex | jsqNoIndex) 76 | 77 | /* 78 | * Support functions to parse/construct binary value. 79 | * Unlike many other representation of expression the first/main 80 | * node is not an operation but left operand of expression. That 81 | * allows to implement cheep follow-path descending in jsonb 82 | * structure and then execute operator with right operand which 83 | * is always a constant. 84 | */ 85 | 86 | typedef struct JsQueryItem { 87 | JsQueryItemType type; 88 | JsQueryHint hint; 89 | uint32 nextPos; 90 | char *base; 91 | 92 | union { 93 | struct { 94 | char *data; /* for bool, numeric and string/key */ 95 | int datalen; /* filled only for string/key */ 96 | } value; 97 | 98 | struct { 99 | int32 left; 100 | int32 right; 101 | } args; 102 | 103 | int32 arg; 104 | 105 | struct { 106 | int nelems; 107 | int current; 108 | int32 *arrayPtr; 109 | } array; 110 | 111 | uint32 arrayIndex; 112 | }; 113 | } JsQueryItem; 114 | 115 | extern void jsqInit(JsQueryItem *v, JsQuery *js); 116 | extern void jsqInitByBuffer(JsQueryItem *v, char *base, int32 pos); 117 | extern bool jsqGetNext(JsQueryItem *v, JsQueryItem *a); 118 | extern void jsqGetArg(JsQueryItem *v, JsQueryItem *a); 119 | extern void jsqGetLeftArg(JsQueryItem *v, JsQueryItem *a); 120 | extern void jsqGetRightArg(JsQueryItem *v, JsQueryItem *a); 121 | extern Numeric jsqGetNumeric(JsQueryItem *v); 122 | extern bool jsqGetBool(JsQueryItem *v); 123 | extern int32 jsqGetIsType(JsQueryItem *v); 124 | extern char * jsqGetString(JsQueryItem *v, int32 *len); 125 | extern void jsqIterateInit(JsQueryItem *v); 126 | extern bool jsqIterateArray(JsQueryItem *v, JsQueryItem *e); 127 | extern void jsqIterateDestroy(JsQueryItem *v); 128 | 129 | void alignStringInfoInt(StringInfo buf); 130 | 131 | /* 132 | * Parsing 133 | */ 134 | 135 | typedef struct JsQueryParseItem JsQueryParseItem; 136 | 137 | struct JsQueryParseItem { 138 | JsQueryItemType type; 139 | JsQueryHint hint; 140 | JsQueryParseItem *next; /* next in path */ 141 | bool filter; 142 | 143 | union { 144 | struct { 145 | JsQueryParseItem *left; 146 | JsQueryParseItem *right; 147 | } args; 148 | 149 | JsQueryParseItem *arg; 150 | int8 isType; /* jbv* values */ 151 | 152 | Numeric numeric; 153 | bool boolean; 154 | struct { 155 | uint32 len; 156 | char *val; /* could not be not null-terminated */ 157 | } string; 158 | 159 | struct { 160 | int nelems; 161 | JsQueryParseItem **elems; 162 | } array; 163 | 164 | uint32 arrayIndex; 165 | }; 166 | }; 167 | 168 | extern JsQueryParseItem* parsejsquery(const char *str, int len); 169 | 170 | /* jsquery_extract.c */ 171 | 172 | typedef enum 173 | { 174 | iAny = jqiAny, 175 | iAnyArray = jqiAnyArray, 176 | iKey = jqiKey, 177 | iAnyKey = jqiAnyKey, 178 | iIndexArray = jqiIndexArray 179 | } PathItemType; 180 | 181 | typedef struct PathItem PathItem; 182 | struct PathItem 183 | { 184 | PathItemType type; 185 | int len; 186 | int arrayIndex; 187 | char *s; 188 | PathItem *parent; 189 | }; 190 | 191 | typedef enum 192 | { 193 | eExactValue = 1, 194 | eEmptyArray, 195 | eInequality, 196 | eIs, 197 | eAny, 198 | eAnd = jqiAnd, 199 | eOr = jqiOr, 200 | } ExtractedNodeType; 201 | 202 | typedef enum 203 | { 204 | sEqual = 1, 205 | sRange, 206 | sInequal, 207 | sIs, 208 | sAny 209 | } SelectivityClass; 210 | 211 | typedef struct ExtractedNode ExtractedNode; 212 | struct ExtractedNode 213 | { 214 | ExtractedNodeType type; 215 | JsQueryHint hint; 216 | PathItem *path; 217 | bool indirect; 218 | SelectivityClass sClass; 219 | bool forceIndex; 220 | int number; 221 | int entryNum; 222 | union 223 | { 224 | struct 225 | { 226 | ExtractedNode **items; 227 | int count; 228 | } args; 229 | struct 230 | { 231 | bool leftInclusive; 232 | bool rightInclusive; 233 | JsQueryItem *leftBound; 234 | JsQueryItem *rightBound; 235 | } bounds; 236 | JsQueryItem *exactValue; 237 | int32 isType; 238 | }; 239 | }; 240 | 241 | typedef int (*MakeEntryHandler)(ExtractedNode *node, Pointer extra); 242 | typedef bool (*CheckEntryHandler)(ExtractedNode *node, Pointer extra); 243 | bool isLogicalNodeType(ExtractedNodeType type); 244 | 245 | ExtractedNode *extractJsQuery(JsQuery *jq, MakeEntryHandler makeHandler, 246 | CheckEntryHandler checkHandler, Pointer extra); 247 | char *debugJsQuery(JsQuery *jq, MakeEntryHandler makeHandler, 248 | CheckEntryHandler checkHandler, Pointer extra); 249 | bool queryNeedRecheck(ExtractedNode *node); 250 | bool execRecursive(ExtractedNode *node, bool *check); 251 | bool execRecursiveTristate(ExtractedNode *node, GinTernaryValue *check); 252 | 253 | #ifndef PG_RETURN_JSONB_P 254 | #define PG_RETURN_JSONB_P(x) PG_RETURN_JSONB(x) 255 | #endif 256 | 257 | #ifndef PG_GETARG_JSONB_P 258 | #define PG_GETARG_JSONB_P(x) PG_GETARG_JSONB(x) 259 | #endif 260 | 261 | #endif 262 | -------------------------------------------------------------------------------- /jsquery_constr.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * jsquery_constr.c 4 | * Functions and operations to manipulate jsquery 5 | * 6 | * Copyright (c) 2014, PostgreSQL Global Development Group 7 | * Portions Copyright (c) 2017-2021, Postgres Professional 8 | * Author: Teodor Sigaev 9 | * 10 | * IDENTIFICATION 11 | * contrib/jsquery/jsquery_constr.c 12 | * 13 | *------------------------------------------------------------------------- 14 | */ 15 | 16 | #include "postgres.h" 17 | 18 | #include "miscadmin.h" 19 | #include "utils/builtins.h" 20 | 21 | #include "jsquery.h" 22 | 23 | static int32 24 | copyJsQuery(StringInfo buf, JsQueryItem *jsq) 25 | { 26 | JsQueryItem elem; 27 | int32 next, chld; 28 | int32 resPos = buf->len - VARHDRSZ; /* position from begining of jsquery data */ 29 | 30 | check_stack_depth(); 31 | 32 | Assert((jsq->type & jsq->hint) == 0); 33 | Assert((jsq->type & JSQ_HINT_MASK) == 0); 34 | 35 | appendStringInfoChar(buf, (char)(jsq->type | jsq->hint)); 36 | alignStringInfoInt(buf); 37 | 38 | next = (jsqGetNext(jsq, NULL)) ? buf->len : 0; 39 | appendBinaryStringInfo(buf, (char*)&next /* fake value */, sizeof(next)); 40 | 41 | switch(jsq->type) 42 | { 43 | case jqiKey: 44 | case jqiString: 45 | { 46 | int32 len; 47 | char *s; 48 | 49 | s = jsqGetString(jsq, &len); 50 | appendBinaryStringInfo(buf, (char*)&len, sizeof(len)); 51 | appendBinaryStringInfo(buf, s, len + 1 /* \0 */); 52 | } 53 | break; 54 | case jqiNumeric: 55 | { 56 | Numeric n = jsqGetNumeric(jsq); 57 | 58 | appendBinaryStringInfo(buf, (char*)n, VARSIZE_ANY(n)); 59 | } 60 | break; 61 | case jqiBool: 62 | { 63 | bool v = jsqGetBool(jsq); 64 | 65 | appendBinaryStringInfo(buf, (char*)&v, 1); 66 | } 67 | break; 68 | case jqiArray: 69 | { 70 | int32 i, arrayStart; 71 | 72 | appendBinaryStringInfo(buf, (char*)&jsq->array.nelems, 73 | sizeof(jsq->array.nelems)); 74 | 75 | arrayStart = buf->len; 76 | 77 | /* reserve place for "pointers" to array's elements */ 78 | for(i=0; iarray.nelems; i++) 79 | appendBinaryStringInfo(buf, (char*)&i /* fake value */, sizeof(i)); 80 | 81 | while(jsqIterateArray(jsq, &elem)) 82 | { 83 | chld = copyJsQuery(buf, &elem); 84 | *(int32*)(buf->data + arrayStart + i * sizeof(i)) = chld; 85 | i++; 86 | } 87 | } 88 | break; 89 | case jqiAnd: 90 | case jqiOr: 91 | { 92 | int32 leftOut, rightOut; 93 | 94 | leftOut = buf->len; 95 | appendBinaryStringInfo(buf, (char*)&leftOut /* fake value */, sizeof(leftOut)); 96 | rightOut = buf->len; 97 | appendBinaryStringInfo(buf, (char*)&rightOut /* fake value */, sizeof(rightOut)); 98 | 99 | jsqGetLeftArg(jsq, &elem); 100 | chld = copyJsQuery(buf, &elem); 101 | *(int32*)(buf->data + leftOut) = chld; 102 | 103 | jsqGetRightArg(jsq, &elem); 104 | chld = copyJsQuery(buf, &elem); 105 | *(int32*)(buf->data + rightOut) = chld; 106 | } 107 | break; 108 | case jqiEqual: 109 | case jqiIn: 110 | case jqiLess: 111 | case jqiGreater: 112 | case jqiLessOrEqual: 113 | case jqiGreaterOrEqual: 114 | case jqiContains: 115 | case jqiContained: 116 | case jqiOverlap: 117 | case jqiNot: 118 | { 119 | int32 argOut = buf->len; 120 | 121 | appendBinaryStringInfo(buf, (char*)&argOut /* fake value */, sizeof(argOut)); 122 | 123 | jsqGetArg(jsq, &elem); 124 | chld = copyJsQuery(buf, &elem); 125 | *(int32*)(buf->data + argOut) = chld; 126 | } 127 | break; 128 | case jqiIndexArray: 129 | appendBinaryStringInfo(buf, (char*)&jsq->arrayIndex, 130 | sizeof(jsq->arrayIndex)); 131 | break; 132 | case jqiNull: 133 | case jqiCurrent: 134 | case jqiLength: 135 | case jqiAny: 136 | case jqiAnyArray: 137 | case jqiAnyKey: 138 | case jqiAll: 139 | case jqiAllArray: 140 | case jqiAllKey: 141 | case jqiFilter: 142 | break; 143 | default: 144 | elog(ERROR, "Unknown type: %d", jsq->type); 145 | } 146 | 147 | if (jsqGetNext(jsq, &elem)) 148 | *(int32*)(buf->data + next) = copyJsQuery(buf, &elem); 149 | 150 | return resPos; 151 | } 152 | 153 | static JsQuery* 154 | joinJsQuery(JsQueryItemType type, JsQuery *jq1, JsQuery *jq2) 155 | { 156 | JsQuery *out; 157 | StringInfoData buf; 158 | int32 left, right, chld; 159 | JsQueryItem v; 160 | 161 | initStringInfo(&buf); 162 | enlargeStringInfo(&buf, VARSIZE_ANY(jq1) + VARSIZE_ANY(jq2) + 4 * sizeof(int32) + VARHDRSZ); 163 | 164 | appendStringInfoSpaces(&buf, VARHDRSZ); 165 | 166 | /* form jqiAnd/jqiOr header */ 167 | appendStringInfoChar(&buf, (char)type); 168 | alignStringInfoInt(&buf); 169 | 170 | /* nextPos field of header*/ 171 | chld = 0; /* actual value, not a fake */ 172 | appendBinaryStringInfo(&buf, (char*)&chld, sizeof(chld)); 173 | 174 | left = buf.len; 175 | appendBinaryStringInfo(&buf, (char*)&left /* fake value */, sizeof(left)); 176 | right = buf.len; 177 | appendBinaryStringInfo(&buf, (char*)&right /* fake value */, sizeof(right)); 178 | 179 | /* dump left and right subtree */ 180 | jsqInit(&v, jq1); 181 | chld = copyJsQuery(&buf, &v); 182 | *(int32*)(buf.data + left) = chld; 183 | jsqInit(&v, jq2); 184 | chld = copyJsQuery(&buf, &v); 185 | *(int32*)(buf.data + right) = chld; 186 | 187 | out = (JsQuery*)buf.data; 188 | SET_VARSIZE(out, buf.len); 189 | 190 | return out; 191 | } 192 | 193 | PG_FUNCTION_INFO_V1(jsquery_join_and); 194 | Datum 195 | jsquery_join_and(PG_FUNCTION_ARGS) 196 | { 197 | JsQuery *jq1 = PG_GETARG_JSQUERY(0); 198 | JsQuery *jq2 = PG_GETARG_JSQUERY(1); 199 | JsQuery *out; 200 | 201 | out = joinJsQuery(jqiAnd, jq1, jq2); 202 | 203 | PG_FREE_IF_COPY(jq1, 0); 204 | PG_FREE_IF_COPY(jq2, 1); 205 | 206 | PG_RETURN_JSQUERY(out); 207 | } 208 | 209 | PG_FUNCTION_INFO_V1(jsquery_join_or); 210 | Datum 211 | jsquery_join_or(PG_FUNCTION_ARGS) 212 | { 213 | JsQuery *jq1 = PG_GETARG_JSQUERY(0); 214 | JsQuery *jq2 = PG_GETARG_JSQUERY(1); 215 | JsQuery *out; 216 | 217 | out = joinJsQuery(jqiOr, jq1, jq2); 218 | 219 | PG_FREE_IF_COPY(jq1, 0); 220 | PG_FREE_IF_COPY(jq2, 1); 221 | 222 | PG_RETURN_JSQUERY(out); 223 | } 224 | 225 | PG_FUNCTION_INFO_V1(jsquery_not); 226 | Datum 227 | jsquery_not(PG_FUNCTION_ARGS) 228 | { 229 | JsQuery *jq = PG_GETARG_JSQUERY(0); 230 | JsQuery *out; 231 | StringInfoData buf; 232 | int32 arg, chld; 233 | JsQueryItem v; 234 | 235 | initStringInfo(&buf); 236 | enlargeStringInfo(&buf, VARSIZE_ANY(jq) + 4 * sizeof(int32) + VARHDRSZ); 237 | 238 | appendStringInfoSpaces(&buf, VARHDRSZ); 239 | 240 | /* form jsquery header */ 241 | appendStringInfoChar(&buf, (char)jqiNot); 242 | alignStringInfoInt(&buf); 243 | 244 | /* nextPos field of header*/ 245 | chld = 0; /* actual value, not a fake */ 246 | appendBinaryStringInfo(&buf, (char*)&chld, sizeof(chld)); 247 | 248 | arg = buf.len; 249 | appendBinaryStringInfo(&buf, (char*)&arg /* fake value */, sizeof(arg)); 250 | 251 | jsqInit(&v, jq); 252 | chld = copyJsQuery(&buf, &v); 253 | *(int32*)(buf.data + arg) = chld; 254 | 255 | out = (JsQuery*)buf.data; 256 | SET_VARSIZE(out, buf.len); 257 | 258 | PG_FREE_IF_COPY(jq, 0); 259 | 260 | PG_RETURN_JSQUERY(out); 261 | } 262 | 263 | -------------------------------------------------------------------------------- /jsquery_extract.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * jsquery_extract.c 4 | * Functions and operations to support jsquery in indexes 5 | * 6 | * Copyright (c) 2014, PostgreSQL Global Development Group 7 | * Portions Copyright (c) 2017-2021, Postgres Professional 8 | * Author: Alexander Korotkov 9 | * 10 | * IDENTIFICATION 11 | * contrib/jsquery/jsquery_extract.c 12 | * 13 | *------------------------------------------------------------------------- 14 | */ 15 | 16 | #include "postgres.h" 17 | 18 | #include "access/gin.h" 19 | #include "utils/builtins.h" 20 | #include "utils/jsonb.h" 21 | 22 | #include "miscadmin.h" 23 | #include "jsquery.h" 24 | 25 | static ExtractedNode *recursiveExtract(JsQueryItem *jsq, bool not, bool indirect, PathItem *path); 26 | static ExtractedNode *makeAnyNode(bool not, bool indirect, PathItem *path); 27 | static int coundChildren(ExtractedNode *node, ExtractedNodeType type, bool first, bool *found); 28 | static void fillChildren(ExtractedNode *node, ExtractedNodeType type, bool first, ExtractedNode **items, int *i); 29 | static void flatternTree(ExtractedNode *node); 30 | static int comparePathItems(PathItem *i1, PathItem *i2); 31 | static int compareNodes(const void *a1, const void *a2); 32 | static int compareJsQueryItem(JsQueryItem *v1, JsQueryItem *v2); 33 | static void processGroup(ExtractedNode *node, int start, int end); 34 | static void simplifyRecursive(ExtractedNode *node); 35 | static SelectivityClass getScalarSelectivityClass(ExtractedNode *node); 36 | static ExtractedNode *makeEntries(ExtractedNode *node, MakeEntryHandler handler, Pointer extra); 37 | static void setSelectivityClass(ExtractedNode *node, CheckEntryHandler checkHandler, Pointer extra); 38 | static void debugPath(StringInfo buf, PathItem *path); 39 | static void debugValue(StringInfo buf, JsQueryItem *v); 40 | static void debugRecursive(StringInfo buf, ExtractedNode *node, int shift); 41 | 42 | /* 43 | * Recursive function that turns jsquery into tree of ExtractedNode items. 44 | */ 45 | static ExtractedNode * 46 | recursiveExtract(JsQueryItem *jsq, bool not, bool indirect, PathItem *path) 47 | { 48 | ExtractedNode *leftNode, *rightNode, *result; 49 | PathItem *pathItem; 50 | ExtractedNodeType type; 51 | JsQueryItem elem, e; 52 | 53 | e.hint = false; 54 | check_stack_depth(); 55 | 56 | switch(jsq->type) 57 | { 58 | case jqiAnd: 59 | case jqiOr: 60 | case jqiFilter: 61 | type = ((jsq->type == jqiAnd || jsq->type == jqiFilter) == not) ? eOr : eAnd; 62 | 63 | if (jsq->type == jqiFilter) 64 | { 65 | jsqGetArg(jsq, &elem); 66 | leftNode = recursiveExtract(&elem, not, false, path); 67 | if (jsqGetNext(jsq, &elem)) 68 | rightNode = recursiveExtract(&elem, not, false, path); 69 | else 70 | rightNode = makeAnyNode(not, false, path); 71 | } 72 | else 73 | { 74 | jsqGetLeftArg(jsq, &elem); 75 | leftNode = recursiveExtract(&elem, not, false, path); 76 | jsqGetRightArg(jsq, &elem); 77 | rightNode = recursiveExtract(&elem, not, false, path); 78 | } 79 | 80 | if (!leftNode || !rightNode) 81 | { 82 | if (type == eOr || (!leftNode && !rightNode)) 83 | return NULL; 84 | if (leftNode) 85 | { 86 | leftNode->indirect = leftNode->indirect || indirect; 87 | return leftNode; 88 | } 89 | else 90 | { 91 | rightNode->indirect = rightNode->indirect || indirect; 92 | return rightNode; 93 | } 94 | } 95 | 96 | result = (ExtractedNode *)palloc(sizeof(ExtractedNode)); 97 | result->type = type; 98 | result->path = path; 99 | result->indirect = indirect; 100 | result->args.items = (ExtractedNode **)palloc(2 * sizeof(ExtractedNode *)); 101 | result->args.items[0] = leftNode; 102 | result->args.items[1] = rightNode; 103 | result->args.count = 2; 104 | return result; 105 | case jqiNot: 106 | jsqGetArg(jsq, &elem); 107 | return recursiveExtract(&elem, !not, indirect, path); 108 | case jqiKey: 109 | pathItem = (PathItem *)palloc(sizeof(PathItem)); 110 | pathItem->type = iKey; 111 | pathItem->s = jsqGetString(jsq, &pathItem->len); 112 | pathItem->parent = path; 113 | if (!jsqGetNext(jsq, &elem)) 114 | return makeAnyNode(not, indirect, pathItem); 115 | return recursiveExtract(&elem, not, indirect, pathItem); 116 | case jqiAny: 117 | case jqiAll: 118 | if ((not && jsq->type == jqiAny) || (!not && jsq->type == jqiAll)) 119 | return NULL; 120 | pathItem = (PathItem *)palloc(sizeof(PathItem)); 121 | pathItem->type = iAny; 122 | pathItem->parent = path; 123 | if (!jsqGetNext(jsq, &elem)) 124 | return makeAnyNode(not, indirect, pathItem); 125 | return recursiveExtract(&elem, not, true, pathItem); 126 | case jqiIndexArray: 127 | pathItem = (PathItem *)palloc(sizeof(PathItem)); 128 | pathItem->type = iIndexArray; 129 | pathItem->arrayIndex = jsq->arrayIndex; 130 | pathItem->parent = path; 131 | if (!jsqGetNext(jsq, &elem)) 132 | return makeAnyNode(not, indirect, pathItem); 133 | return recursiveExtract(&elem, not, true, pathItem); 134 | case jqiAnyArray: 135 | case jqiAllArray: 136 | if ((not && jsq->type == jqiAnyArray) || (!not && jsq->type == jqiAllArray)) 137 | return NULL; 138 | pathItem = (PathItem *)palloc(sizeof(PathItem)); 139 | pathItem->type = iAnyArray; 140 | pathItem->parent = path; 141 | if (!jsqGetNext(jsq, &elem)) 142 | return makeAnyNode(not, indirect, pathItem); 143 | return recursiveExtract(&elem, not, true, pathItem); 144 | case jqiAnyKey: 145 | case jqiAllKey: 146 | if ((not && jsq->type == jqiAnyKey) || (!not && jsq->type == jqiAllKey)) 147 | return NULL; 148 | pathItem = (PathItem *)palloc(sizeof(PathItem)); 149 | pathItem->type = iAnyKey; 150 | pathItem->parent = path; 151 | if (!jsqGetNext(jsq, &elem)) 152 | return makeAnyNode(not, indirect, pathItem); 153 | return recursiveExtract(&elem, not, true, pathItem); 154 | case jqiCurrent: 155 | if (!jsqGetNext(jsq, &elem)) 156 | return makeAnyNode(not, indirect, path); 157 | return recursiveExtract(&elem, not, indirect, path); 158 | case jqiEqual: 159 | if (not) 160 | return NULL; 161 | jsqGetArg(jsq, &e); 162 | if (e.type == jqiAny) 163 | { 164 | result = (ExtractedNode *)palloc(sizeof(ExtractedNode)); 165 | result->type = eAny; 166 | result->hint = jsq->hint; 167 | result->path = path; 168 | result->indirect = indirect; 169 | return result; 170 | } 171 | else if (e.type != jqiArray) 172 | { 173 | result = (ExtractedNode *)palloc(sizeof(ExtractedNode)); 174 | result->type = eExactValue; 175 | result->hint = jsq->hint; 176 | result->path = path; 177 | result->indirect = indirect; 178 | result->exactValue = (JsQueryItem *)palloc(sizeof(JsQueryItem)); 179 | *result->exactValue = e; 180 | return result; 181 | } 182 | /* fall through */ 183 | /* jqiEqual with jqiArray follows */ 184 | case jqiIn: 185 | case jqiOverlap: 186 | case jqiContains: 187 | case jqiContained: 188 | if (not) 189 | return NULL; 190 | result = (ExtractedNode *)palloc(sizeof(ExtractedNode)); 191 | result->type = (jsq->type == jqiContains || jsq->type == jqiEqual) ? eAnd : eOr; 192 | jsqGetArg(jsq, &elem); 193 | Assert(elem.type == jqiArray); 194 | result->path = path; 195 | result->indirect = indirect; 196 | result->args.items = (ExtractedNode **)palloc((elem.array.nelems + 1) * sizeof(ExtractedNode *)); 197 | result->args.count = 0; 198 | if (jsq->type == jqiContains || jsq->type == jqiOverlap || 199 | jsq->type == jqiContained || jsq->type == jqiEqual) 200 | { 201 | pathItem = (PathItem *)palloc(sizeof(PathItem)); 202 | pathItem->type = iAnyArray; 203 | pathItem->parent = path; 204 | } 205 | else 206 | { 207 | pathItem = path; 208 | } 209 | 210 | if (jsq->type == jqiContained || 211 | (jsq->type == jqiEqual && elem.array.nelems == 0)) 212 | { 213 | ExtractedNode *item = (ExtractedNode *)palloc(sizeof(ExtractedNode)); 214 | 215 | item->hint = e.hint; 216 | item->type = eEmptyArray; 217 | item->path = pathItem->parent; 218 | item->indirect = false; 219 | item->hint = jsq->hint; 220 | 221 | result->args.items[result->args.count] = item; 222 | result->args.count++; 223 | } 224 | 225 | while(jsqIterateArray(&elem, &e)) 226 | { 227 | ExtractedNode *item = (ExtractedNode *)palloc(sizeof(ExtractedNode)); 228 | 229 | item->hint = e.hint; 230 | item->type = eExactValue; 231 | item->path = pathItem; 232 | item->indirect = true; 233 | item->hint = jsq->hint; 234 | 235 | item->exactValue = (JsQueryItem *)palloc(sizeof(JsQueryItem)); 236 | *item->exactValue = e; 237 | result->args.items[result->args.count] = item; 238 | result->args.count++; 239 | } 240 | return result; 241 | case jqiLess: 242 | case jqiGreater: 243 | case jqiLessOrEqual: 244 | case jqiGreaterOrEqual: 245 | if (not) 246 | return NULL; 247 | result = (ExtractedNode *)palloc(sizeof(ExtractedNode)); 248 | result->type = eInequality; 249 | result->hint = jsq->hint; 250 | result->path = path; 251 | result->indirect = indirect; 252 | 253 | if (jsq->type == jqiGreater || jsq->type == jqiGreaterOrEqual) 254 | { 255 | result->bounds.leftBound = (JsQueryItem *)palloc(sizeof(JsQueryItem)); 256 | result->bounds.leftInclusive = (jsq->type == jqiGreaterOrEqual); 257 | result->bounds.rightBound = NULL; 258 | result->bounds.rightInclusive = false; 259 | jsqGetArg(jsq, result->bounds.leftBound); 260 | } 261 | else 262 | { 263 | result->bounds.rightBound = (JsQueryItem *)palloc(sizeof(JsQueryItem)); 264 | result->bounds.rightInclusive = (jsq->type == jqiLessOrEqual); 265 | result->bounds.leftBound = NULL; 266 | result->bounds.leftInclusive = false; 267 | jsqGetArg(jsq, result->bounds.rightBound); 268 | } 269 | return result; 270 | case jqiIs: 271 | if (not) 272 | return NULL; 273 | result = (ExtractedNode *)palloc(sizeof(ExtractedNode)); 274 | result->type = eIs; 275 | result->hint = jsq->hint; 276 | result->path = path; 277 | result->indirect = indirect; 278 | result->isType = jsqGetIsType(jsq); 279 | return result; 280 | case jqiLength: 281 | return NULL; 282 | default: 283 | elog(ERROR,"Wrong state: %d", jsq->type); 284 | } 285 | 286 | return NULL; 287 | } 288 | 289 | /* 290 | * Make node for checking existence of path. 291 | */ 292 | static ExtractedNode * 293 | makeAnyNode(bool not, bool indirect, PathItem *path) 294 | { 295 | ExtractedNode *result; 296 | 297 | if (not) 298 | return NULL; 299 | 300 | result = (ExtractedNode *) palloc(sizeof(ExtractedNode)); 301 | result->type = eAny; 302 | result->hint = false; 303 | result->path = path; 304 | result->indirect = indirect; 305 | return result; 306 | } 307 | 308 | /* 309 | * Count number of children connected with nodes of same type. 310 | */ 311 | static int 312 | coundChildren(ExtractedNode *node, ExtractedNodeType type, 313 | bool first, bool *found) 314 | { 315 | if ((node->indirect || node->type != type) && !first) 316 | { 317 | return 1; 318 | } 319 | else 320 | { 321 | int i, total = 0; 322 | if (!first) 323 | *found = true; 324 | for (i = 0; i < node->args.count; i++) 325 | total += coundChildren(node->args.items[i], type, false, found); 326 | return total; 327 | } 328 | } 329 | 330 | /* 331 | * Fill array of children connected with nodes of same type. 332 | */ 333 | static void 334 | fillChildren(ExtractedNode *node, ExtractedNodeType type, bool first, 335 | ExtractedNode **items, int *i) 336 | { 337 | if ((node->indirect || node->type != type) && !first) 338 | { 339 | items[*i] = node; 340 | (*i)++; 341 | } 342 | else 343 | { 344 | int j; 345 | for (j = 0; j < node->args.count; j++) 346 | fillChildren(node->args.items[j], type, false, items, i); 347 | } 348 | } 349 | 350 | /* 351 | * Turn tree into "flat" form, turning nested binary AND/OR operators into 352 | * single n-ary AND/OR operators. 353 | */ 354 | static void 355 | flatternTree(ExtractedNode *node) 356 | { 357 | if (node->type == eAnd || node->type == eOr) 358 | { 359 | int count; 360 | bool found = false; 361 | 362 | count = coundChildren(node, node->type, true, &found); 363 | 364 | if (found) 365 | { 366 | int i = 0; 367 | ExtractedNode **items = (ExtractedNode **)palloc(count * sizeof(ExtractedNode *)); 368 | fillChildren(node, node->type, true, items, &i); 369 | node->args.items = items; 370 | node->args.count = count; 371 | } 372 | } 373 | if (node->type == eAnd || node->type == eOr) 374 | { 375 | int i; 376 | for (i = 0; i < node->args.count; i++) 377 | flatternTree(node->args.items[i]); 378 | } 379 | } 380 | 381 | /* 382 | * Compare path items chains from child to parent. 383 | */ 384 | static int 385 | comparePathItems(PathItem *i1, PathItem *i2) 386 | { 387 | int cmp; 388 | 389 | if (i1 == i2) 390 | return 0; 391 | 392 | if (!i1) 393 | return -1; 394 | if (!i2) 395 | return 1; 396 | 397 | if (i1->type != i2->type) 398 | { 399 | return (i1->type < i2->type) ? -1 : 1; 400 | } 401 | 402 | if (i1->type == iKey) 403 | cmp = memcmp(i1->s, i2->s, Min(i1->len, i2->len)); 404 | else 405 | cmp = 0; 406 | 407 | if (cmp == 0) 408 | { 409 | if (i1->len != i2->len) 410 | return (i1->len < i2->len) ? -1 : 1; 411 | return comparePathItems(i1->parent, i2->parent); 412 | } 413 | else 414 | { 415 | return cmp; 416 | } 417 | } 418 | 419 | bool 420 | isLogicalNodeType(ExtractedNodeType type) 421 | { 422 | if (type == eAnd || type == eOr) 423 | return true; 424 | else 425 | return false; 426 | } 427 | 428 | /* 429 | * Compare nodes in the order where conditions to the same fields are located 430 | * together. 431 | */ 432 | static int 433 | compareNodes(const void *a1, const void *a2) 434 | { 435 | ExtractedNode *n1 = *((ExtractedNode **)a1); 436 | ExtractedNode *n2 = *((ExtractedNode **)a2); 437 | 438 | if (n1->indirect != n2->indirect) 439 | { 440 | if (n1->indirect) 441 | return 1; 442 | if (n2->indirect) 443 | return -1; 444 | } 445 | 446 | if (n1->type != n2->type) 447 | return (n1->type < n2->type) ? -1 : 1; 448 | 449 | if (!isLogicalNodeType(n1->type)) 450 | { 451 | int cmp = comparePathItems(n1->path, n2->path); 452 | if (cmp) return cmp; 453 | } 454 | 455 | if (n1->number != n2->number) 456 | return (n1->number < n2->number) ? -1 : 1; 457 | 458 | return 0; 459 | } 460 | 461 | /* 462 | * Compare json values represented by JsQueryItems. 463 | */ 464 | static int 465 | compareJsQueryItem(JsQueryItem *v1, JsQueryItem *v2) 466 | { 467 | char *s1, *s2; 468 | int32 len1, len2, cmp; 469 | 470 | if (v1->type != v2->type) 471 | return (v1->type < v2->type) ? -1 : 1; 472 | 473 | switch(v1->type) 474 | { 475 | case jqiNull: 476 | return 0; 477 | case jqiString: 478 | s1 = jsqGetString(v1, &len1); 479 | s2 = jsqGetString(v2, &len2); 480 | cmp = memcmp(s1, s2, Min(len1, len2)); 481 | if (cmp != 0 || len1 == len2) 482 | return cmp; 483 | return (len1 < len2) ? -1 : 1; 484 | case jqiBool: 485 | len1 = jsqGetBool(v1); 486 | len2 = jsqGetBool(v2); 487 | 488 | return (len1 - len2); 489 | case jqiNumeric: 490 | return DatumGetInt32(DirectFunctionCall2(numeric_cmp, 491 | PointerGetDatum(jsqGetNumeric(v1)), 492 | PointerGetDatum(jsqGetNumeric(v2)))); 493 | default: 494 | elog(ERROR, "Wrong state"); 495 | } 496 | 497 | return 0; /* make compiler happy */ 498 | } 499 | 500 | /* 501 | * Process group of nodes representing conditions for the same field. After 502 | * processing group of nodes is replaced with one node. 503 | */ 504 | static void 505 | processGroup(ExtractedNode *node, int start, int end) 506 | { 507 | int i; 508 | JsQueryItem *leftBound = NULL, 509 | *rightBound = NULL, 510 | *exactValue = NULL; 511 | bool leftInclusive = false, 512 | rightInclusive = false, 513 | first = true; 514 | ExtractedNode *child; 515 | ExtractedNodeType type = eAny; 516 | JsQueryItemType isType = jqiNull; 517 | 518 | if (end - start < 2) 519 | return; 520 | 521 | for (i = start; i < end; i++) 522 | { 523 | int cmp; 524 | 525 | child = node->args.items[i]; 526 | 527 | if (first || child->type <= type) 528 | { 529 | type = child->type; 530 | first = false; 531 | Assert(!isLogicalNodeType(type)); 532 | switch(type) 533 | { 534 | case eAny: 535 | case eEmptyArray: 536 | break; 537 | case eIs: 538 | isType = child->isType; 539 | break; 540 | case eInequality: 541 | if (child->bounds.leftBound) 542 | { 543 | if (!leftBound) 544 | { 545 | leftBound = child->bounds.leftBound; 546 | leftInclusive = child->bounds.leftInclusive; 547 | } 548 | cmp = compareJsQueryItem(child->bounds.leftBound, 549 | leftBound); 550 | if (cmp > 0) 551 | { 552 | leftBound = child->bounds.leftBound; 553 | leftInclusive = child->bounds.leftInclusive; 554 | } 555 | if (cmp == 0 && leftInclusive) 556 | { 557 | leftInclusive = child->bounds.leftInclusive; 558 | } 559 | } 560 | if (child->bounds.rightBound) 561 | { 562 | if (!rightBound) 563 | { 564 | rightBound = child->bounds.rightBound; 565 | rightInclusive = child->bounds.rightInclusive; 566 | } 567 | cmp = compareJsQueryItem(child->bounds.rightBound, 568 | rightBound); 569 | if (cmp > 0) 570 | { 571 | rightBound = child->bounds.rightBound; 572 | rightInclusive = child->bounds.rightInclusive; 573 | } 574 | if (cmp == 0 && rightInclusive) 575 | { 576 | rightInclusive = child->bounds.rightInclusive; 577 | } 578 | } 579 | break; 580 | case eExactValue: 581 | exactValue = child->exactValue; 582 | break; 583 | default: 584 | elog(ERROR, "Wrong state"); 585 | break; 586 | 587 | } 588 | } 589 | } 590 | 591 | child = node->args.items[start]; 592 | child->type = type; 593 | 594 | switch(type) 595 | { 596 | case eAny: 597 | case eEmptyArray: 598 | break; 599 | case eIs: 600 | child->isType = isType; 601 | break; 602 | case eInequality: 603 | child->bounds.leftBound = leftBound; 604 | child->bounds.rightBound = rightBound; 605 | child->bounds.leftInclusive = leftInclusive; 606 | child->bounds.rightInclusive = rightInclusive; 607 | break; 608 | case eExactValue: 609 | child->exactValue = exactValue; 610 | break; 611 | default: 612 | elog(ERROR, "Wrong state"); 613 | break; 614 | } 615 | 616 | for (i = start + 1; i < end; i++) 617 | node->args.items[i] = NULL; 618 | } 619 | 620 | /* 621 | * Reduce number of nodes in tree, by turning multiple conditions about 622 | * same field in same context into one node. 623 | */ 624 | static void 625 | simplifyRecursive(ExtractedNode *node) 626 | { 627 | if (node->type == eAnd) 628 | { 629 | int i, groupStart = -1; 630 | ExtractedNode *child, *prevChild = NULL; 631 | 632 | for (i = 0; i < node->args.count; i++) 633 | node->args.items[i]->number = i; 634 | 635 | pg_qsort(node->args.items, node->args.count, 636 | sizeof(ExtractedNode *), compareNodes); 637 | 638 | for (i = 0; i < node->args.count; i++) 639 | { 640 | child = node->args.items[i]; 641 | if (child->indirect || isLogicalNodeType(child->type)) 642 | break; 643 | if (!prevChild || comparePathItems(child->path, prevChild->path) != 0) 644 | { 645 | if (groupStart >= 0) 646 | processGroup(node, groupStart, i); 647 | groupStart = i; 648 | } 649 | prevChild = child; 650 | } 651 | if (groupStart >= 0) 652 | processGroup(node, groupStart, i); 653 | } 654 | 655 | if (node->type == eAnd || node->type == eOr) 656 | { 657 | int i; 658 | for (i = 0; i < node->args.count; i++) 659 | { 660 | if (node->args.items[i]) 661 | simplifyRecursive(node->args.items[i]); 662 | } 663 | } 664 | } 665 | 666 | /* 667 | * Get selectivity class of scalar node. 668 | */ 669 | static SelectivityClass 670 | getScalarSelectivityClass(ExtractedNode *node) 671 | { 672 | Assert(!isLogicalNodeType(node->type)); 673 | switch(node->type) 674 | { 675 | case eAny: 676 | return sAny; 677 | case eIs: 678 | return sIs; 679 | case eInequality: 680 | if (node->bounds.leftBound && node->bounds.rightBound) 681 | return sRange; 682 | else 683 | return sInequal; 684 | case eEmptyArray: 685 | case eExactValue: 686 | return sEqual; 687 | default: 688 | elog(ERROR, "Wrong state"); 689 | return sAny; 690 | } 691 | } 692 | 693 | /* 694 | * Make entries for all leaf tree nodes using user-provided handler. 695 | */ 696 | static ExtractedNode * 697 | makeEntries(ExtractedNode *node, MakeEntryHandler handler, Pointer extra) 698 | { 699 | if (node->type == eAnd || node->type == eOr) 700 | { 701 | int i, j = 0; 702 | ExtractedNode *child; 703 | for (i = 0; i < node->args.count; i++) 704 | { 705 | child = node->args.items[i]; 706 | if (!child) 707 | continue; 708 | /* Skip non-selective AND children */ 709 | if (child->sClass > node->sClass && 710 | node->type == eAnd && 711 | !child->forceIndex) 712 | continue; 713 | child = makeEntries(child, handler, extra); 714 | if (child) 715 | { 716 | node->args.items[j] = child; 717 | j++; 718 | } 719 | else if (node->type == eOr) 720 | { 721 | return NULL; 722 | } 723 | } 724 | if (j == 1) 725 | { 726 | return node->args.items[0]; 727 | } 728 | if (j > 0) 729 | { 730 | node->args.count = j; 731 | return node; 732 | } 733 | else 734 | { 735 | return NULL; 736 | } 737 | } 738 | else 739 | { 740 | int entryNum; 741 | 742 | if (node->hint == jsqNoIndex) 743 | return NULL; 744 | 745 | entryNum = handler(node, extra); 746 | if (entryNum >= 0) 747 | { 748 | node->entryNum = entryNum; 749 | return node; 750 | } 751 | else 752 | { 753 | return NULL; 754 | } 755 | } 756 | } 757 | 758 | static void 759 | setSelectivityClass(ExtractedNode *node, CheckEntryHandler checkHandler, 760 | Pointer extra) 761 | { 762 | int i; 763 | bool first; 764 | ExtractedNode *child; 765 | 766 | switch(node->type) 767 | { 768 | case eAnd: 769 | case eOr: 770 | first = true; 771 | node->forceIndex = false; 772 | for (i = 0; i < node->args.count; i++) 773 | { 774 | child = node->args.items[i]; 775 | 776 | if (!child) 777 | continue; 778 | 779 | setSelectivityClass(child, checkHandler, extra); 780 | 781 | if (!isLogicalNodeType(child->type)) 782 | { 783 | if (child->hint == jsqNoIndex || 784 | !checkHandler(child, extra)) 785 | continue; 786 | } 787 | 788 | if (child->forceIndex) 789 | node->forceIndex = true; 790 | 791 | if (first) 792 | { 793 | node->sClass = child->sClass; 794 | } 795 | else 796 | { 797 | if (node->type == eAnd) 798 | node->sClass = Min(node->sClass, child->sClass); 799 | else 800 | node->sClass = Max(node->sClass, child->sClass); 801 | } 802 | first = false; 803 | } 804 | break; 805 | default: 806 | node->sClass = getScalarSelectivityClass(node); 807 | node->forceIndex = node->hint == jsqForceIndex; 808 | break; 809 | } 810 | } 811 | 812 | /* 813 | * Turn jsquery into tree of entries using user-provided handler. 814 | */ 815 | ExtractedNode * 816 | extractJsQuery(JsQuery *jq, MakeEntryHandler makeHandler, 817 | CheckEntryHandler checkHandler, Pointer extra) 818 | { 819 | ExtractedNode *root; 820 | JsQueryItem jsq; 821 | 822 | jsqInit(&jsq, jq); 823 | root = recursiveExtract(&jsq, false, false, NULL); 824 | if (root) 825 | { 826 | flatternTree(root); 827 | simplifyRecursive(root); 828 | setSelectivityClass(root, checkHandler, extra); 829 | root = makeEntries(root, makeHandler, extra); 830 | } 831 | return root; 832 | } 833 | 834 | /* 835 | * Evaluate previously extracted tree. 836 | */ 837 | bool 838 | execRecursive(ExtractedNode *node, bool *check) 839 | { 840 | int i; 841 | switch(node->type) 842 | { 843 | case eAnd: 844 | for (i = 0; i < node->args.count; i++) 845 | if (!execRecursive(node->args.items[i], check)) 846 | return false; 847 | return true; 848 | case eOr: 849 | for (i = 0; i < node->args.count; i++) 850 | if (execRecursive(node->args.items[i], check)) 851 | return true; 852 | return false; 853 | default: 854 | return check[node->entryNum]; 855 | } 856 | } 857 | 858 | /* 859 | * Evaluate previously extracted tree using tri-state logic. 860 | */ 861 | bool 862 | execRecursiveTristate(ExtractedNode *node, GinTernaryValue *check) 863 | { 864 | GinTernaryValue res, v; 865 | int i; 866 | 867 | switch(node->type) 868 | { 869 | case eAnd: 870 | res = GIN_TRUE; 871 | for (i = 0; i < node->args.count; i++) 872 | { 873 | v = execRecursiveTristate(node->args.items[i], check); 874 | if (v == GIN_FALSE) 875 | return GIN_FALSE; 876 | } 877 | return res; 878 | case eOr: 879 | res = GIN_FALSE; 880 | for (i = 0; i < node->args.count; i++) 881 | { 882 | v = execRecursiveTristate(node->args.items[i], check); 883 | if (v == GIN_TRUE) 884 | return GIN_TRUE; 885 | } 886 | return res; 887 | default: 888 | return check[node->entryNum]; 889 | } 890 | } 891 | 892 | /* 893 | * Debug print of variable path. 894 | */ 895 | static void 896 | debugPath(StringInfo buf, PathItem *path) 897 | { 898 | if (!path) 899 | { 900 | appendStringInfoChar(buf, '$'); 901 | return; 902 | } 903 | if (path->parent) 904 | { 905 | debugPath(buf, path->parent); 906 | appendStringInfo(buf, "."); 907 | } 908 | switch (path->type) 909 | { 910 | case jqiAny: 911 | appendStringInfoChar(buf, '*'); 912 | break; 913 | case jqiAnyKey: 914 | appendStringInfoChar(buf, '%'); 915 | break; 916 | case jqiAnyArray: 917 | appendStringInfoChar(buf, '#'); 918 | break; 919 | case jqiIndexArray: 920 | appendStringInfo(buf, "#%d", path->arrayIndex); 921 | break; 922 | case jqiKey: 923 | appendBinaryStringInfo(buf, path->s, path->len); 924 | break; 925 | } 926 | } 927 | 928 | /* 929 | * Debug print of jsquery value. 930 | */ 931 | static void 932 | debugValue(StringInfo buf, JsQueryItem *v) 933 | { 934 | char *s; 935 | int len; 936 | 937 | switch(v->type) 938 | { 939 | case jqiNull: 940 | appendStringInfo(buf, "null"); 941 | break; 942 | case jqiString: 943 | s = jsqGetString(v, &len); 944 | appendStringInfo(buf, "\""); 945 | appendBinaryStringInfo(buf, s, len); 946 | appendStringInfo(buf, "\""); 947 | break; 948 | case jqiBool: 949 | appendStringInfo(buf, jsqGetBool(v) ? "true" : "false"); 950 | break; 951 | case jqiNumeric: 952 | s = DatumGetCString(DirectFunctionCall1(numeric_out, 953 | PointerGetDatum(jsqGetNumeric(v)))); 954 | appendStringInfoString(buf, s); 955 | break; 956 | default: 957 | elog(ERROR,"Wrong type"); 958 | break; 959 | } 960 | } 961 | 962 | static const char * 963 | getTypeString(int32 type) 964 | { 965 | switch (type) 966 | { 967 | case jbvArray: 968 | return "array"; 969 | case jbvObject: 970 | return "object"; 971 | case jbvString: 972 | return "string"; 973 | case jbvNumeric: 974 | return "numeric"; 975 | case jbvBool: 976 | return "boolean"; 977 | case jbvNull: 978 | return "null"; 979 | default: 980 | elog(ERROR,"Wrong type"); 981 | return NULL; 982 | } 983 | } 984 | 985 | 986 | /* 987 | * Recursive worker of debug print of query processing. 988 | */ 989 | static void 990 | debugRecursive(StringInfo buf, ExtractedNode *node, int shift) 991 | { 992 | int i; 993 | 994 | appendStringInfoSpaces(buf, shift * 2); 995 | 996 | if (isLogicalNodeType(node->type)) 997 | { 998 | appendStringInfo(buf, (node->type == eAnd) ? "AND\n" : "OR\n"); 999 | for (i = 0; i < node->args.count; i++) 1000 | debugRecursive(buf, node->args.items[i], shift + 1); 1001 | return; 1002 | } 1003 | 1004 | debugPath(buf, node->path); 1005 | switch(node->type) 1006 | { 1007 | case eExactValue: 1008 | appendStringInfo(buf, " = "); 1009 | debugValue(buf, node->exactValue); 1010 | appendStringInfo(buf, " ,"); 1011 | break; 1012 | case eAny: 1013 | appendStringInfo(buf, " = * ,"); 1014 | break; 1015 | case eEmptyArray: 1016 | appendStringInfo(buf, " = [] ,"); 1017 | break; 1018 | case eIs: 1019 | appendStringInfo(buf, " IS %s ,", getTypeString(node->isType)); 1020 | break; 1021 | case eInequality: 1022 | if (node->bounds.leftBound) 1023 | { 1024 | if (node->bounds.leftInclusive) 1025 | appendStringInfo(buf, " >= "); 1026 | else 1027 | appendStringInfo(buf, " > "); 1028 | debugValue(buf, node->bounds.leftBound); 1029 | appendStringInfo(buf, " ,"); 1030 | } 1031 | if (node->bounds.rightBound) 1032 | { 1033 | if (node->bounds.rightInclusive) 1034 | appendStringInfo(buf, " <= "); 1035 | else 1036 | appendStringInfo(buf, " < "); 1037 | debugValue(buf, node->bounds.rightBound); 1038 | appendStringInfo(buf, " ,"); 1039 | } 1040 | break; 1041 | default: 1042 | elog(ERROR,"Wrong type"); 1043 | break; 1044 | } 1045 | appendStringInfo(buf, " entry %d \n", node->entryNum); 1046 | } 1047 | 1048 | /* 1049 | * Debug print of query processing. 1050 | */ 1051 | char * 1052 | debugJsQuery(JsQuery *jq, MakeEntryHandler makeHandler, 1053 | CheckEntryHandler checkHandler, Pointer extra) 1054 | { 1055 | ExtractedNode *root; 1056 | StringInfoData buf; 1057 | 1058 | root = extractJsQuery(jq, makeHandler, checkHandler, extra); 1059 | if (!root) 1060 | return "NULL\n"; 1061 | 1062 | initStringInfo(&buf); 1063 | debugRecursive(&buf, root, 0); 1064 | appendStringInfoChar(&buf, '\0'); 1065 | return buf.data; 1066 | } 1067 | -------------------------------------------------------------------------------- /jsquery_gram.y: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * jsquery_gram.y 4 | * Grammar definitions for jsquery datatype 5 | * 6 | * Copyright (c) 2014, PostgreSQL Global Development Group 7 | * Portions Copyright (c) 2017-2021, Postgres Professional 8 | * Author: Teodor Sigaev 9 | * 10 | * IDENTIFICATION 11 | * contrib/jsquery/jsquery_gram.y 12 | * 13 | *------------------------------------------------------------------------- 14 | */ 15 | 16 | %{ 17 | #include "postgres.h" 18 | 19 | #include "fmgr.h" 20 | #include "utils/builtins.h" 21 | 22 | #include "jsquery.h" 23 | 24 | /* 25 | * Bison doesn't allocate anything that needs to live across parser calls, 26 | * so we can easily have it use palloc instead of malloc. This prevents 27 | * memory leaks if we error out during parsing. Note this only works with 28 | * bison >= 2.0. However, in bison 1.875 the default is to use alloca() 29 | * if possible, so there's not really much problem anyhow, at least if 30 | * you're building with gcc. 31 | */ 32 | #define YYMALLOC palloc 33 | #define YYFREE pfree 34 | 35 | /* Avoid exit() on fatal scanner errors (a bit ugly -- see yy_fatal_error) */ 36 | #undef fprintf 37 | #define fprintf(file, fmt, msg) fprintf_to_ereport(fmt, msg) 38 | 39 | static void 40 | fprintf_to_ereport(const char *fmt, const char *msg) 41 | { 42 | ereport(ERROR, (errmsg_internal("%s", msg))); 43 | } 44 | 45 | /* struct string is shared between scan and gram */ 46 | typedef struct string { 47 | char *val; 48 | int len; 49 | int total; 50 | } string; 51 | #include "jsquery_gram.h" 52 | 53 | /* flex 2.5.4 doesn't bother with a decl for this */ 54 | int jsquery_yylex(YYSTYPE * yylval_param); 55 | void jsquery_yyerror(JsQueryParseItem **result, const char *message); 56 | 57 | static JsQueryParseItem* 58 | makeItemType(int type) 59 | { 60 | JsQueryParseItem* v = palloc(sizeof(*v)); 61 | 62 | v->type = type; 63 | v->hint = jsqIndexDefault; 64 | v->next = NULL; 65 | 66 | return v; 67 | } 68 | 69 | static JsQueryParseItem* 70 | makeIndexArray(string *s) 71 | { 72 | JsQueryParseItem* v = makeItemType(jqiIndexArray); 73 | 74 | #if PG_VERSION_NUM >= 120000 75 | v->arrayIndex = pg_strtoint32(s->val); 76 | #else 77 | v->arrayIndex = pg_atoi(s->val, 4, 0); 78 | #endif 79 | 80 | return v; 81 | } 82 | 83 | static JsQueryParseItem* 84 | makeItemString(string *s) 85 | { 86 | JsQueryParseItem *v; 87 | 88 | if (s == NULL) 89 | { 90 | v = makeItemType(jqiNull); 91 | } 92 | else 93 | { 94 | v = makeItemType(jqiString); 95 | v->string.val = s->val; 96 | v->string.len = s->len; 97 | } 98 | 99 | return v; 100 | } 101 | 102 | static JsQueryParseItem* 103 | makeItemKey(string *s) 104 | { 105 | JsQueryParseItem *v; 106 | 107 | v = makeItemString(s); 108 | v->type = jqiKey; 109 | 110 | return v; 111 | } 112 | 113 | static JsQueryParseItem* 114 | makeItemNumeric(string *s) 115 | { 116 | JsQueryParseItem *v; 117 | 118 | v = makeItemType(jqiNumeric); 119 | v->numeric = DatumGetNumeric(DirectFunctionCall3(numeric_in, CStringGetDatum(s->val), 0, -1)); 120 | 121 | return v; 122 | } 123 | 124 | static JsQueryParseItem* 125 | makeItemBool(bool val) { 126 | JsQueryParseItem *v = makeItemType(jqiBool); 127 | 128 | v->boolean = val; 129 | 130 | return v; 131 | } 132 | 133 | static JsQueryParseItem* 134 | makeItemArray(List *list) 135 | { 136 | JsQueryParseItem *v = makeItemType(jqiArray); 137 | 138 | v->array.nelems = list_length(list); 139 | 140 | if (v->array.nelems > 0) 141 | { 142 | ListCell *cell; 143 | int i = 0; 144 | 145 | v->array.elems = palloc(sizeof(JsQueryParseItem) * v->array.nelems); 146 | 147 | foreach(cell, list) 148 | v->array.elems[i++] = (JsQueryParseItem*)lfirst(cell); 149 | } 150 | else 151 | { 152 | v->array.elems = NULL; 153 | } 154 | 155 | return v; 156 | } 157 | 158 | static JsQueryParseItem* 159 | makeItemBinary(int type, JsQueryParseItem* la, JsQueryParseItem *ra) 160 | { 161 | JsQueryParseItem *v = makeItemType(type); 162 | 163 | v->args.left = la; 164 | v->args.right = ra; 165 | 166 | return v; 167 | } 168 | 169 | static JsQueryParseItem* 170 | makeItemUnary(int type, JsQueryParseItem* a) 171 | { 172 | JsQueryParseItem *v = makeItemType(type); 173 | 174 | v->arg = a; 175 | 176 | return v; 177 | } 178 | 179 | static JsQueryParseItem* 180 | makeItemIs(int isType) 181 | { 182 | JsQueryParseItem *v = makeItemType(jqiIs); 183 | 184 | v->isType = isType; 185 | 186 | return v; 187 | } 188 | 189 | static JsQueryParseItem* 190 | makeItemList(List *list) { 191 | JsQueryParseItem *head, *end; 192 | ListCell *cell; 193 | 194 | head = end = (JsQueryParseItem*)linitial(list); 195 | 196 | while(end->next) 197 | end = end->next; 198 | 199 | foreach(cell, list) 200 | { 201 | JsQueryParseItem *c = (JsQueryParseItem*)lfirst(cell); 202 | 203 | if (c == head) 204 | continue; 205 | 206 | end->next = c; 207 | end = c; 208 | 209 | while(end->next) 210 | end = end->next; 211 | } 212 | 213 | return head; 214 | } 215 | 216 | %} 217 | 218 | /* BISON Declarations */ 219 | %pure-parser 220 | %expect 0 221 | %name-prefix="jsquery_yy" 222 | %error-verbose 223 | %parse-param {JsQueryParseItem **result} 224 | 225 | %union { 226 | string str; 227 | List *elems; /* list of JsQueryParseItem */ 228 | 229 | JsQueryParseItem *value; 230 | JsQueryHint hint; 231 | } 232 | 233 | %token IN_P IS_P OR_P AND_P NOT_P NULL_P TRUE_P 234 | ARRAY_T FALSE_P NUMERIC_T OBJECT_T 235 | STRING_T BOOLEAN_T 236 | 237 | %token STRING_P NUMERIC_P INT_P 238 | 239 | %type result scalar_value 240 | 241 | %type path value_list 242 | 243 | %type key key_any right_expr expr array numeric 244 | 245 | %token HINT_P 246 | 247 | %left OR_P 248 | %left AND_P 249 | %right NOT_P 250 | %nonassoc IN_P IS_P 251 | %nonassoc '(' ')' 252 | 253 | /* Grammar follows */ 254 | %% 255 | 256 | result: 257 | expr { 258 | *result = $1; 259 | (void) yynerrs; /* suppress compiler warning */ 260 | } 261 | | /* EMPTY */ { 262 | *result = NULL; 263 | yyerror(NULL, "No symbols read"); } 264 | ; 265 | 266 | array: 267 | '[' value_list ']' { $$ = makeItemArray($2); } 268 | ; 269 | 270 | scalar_value: 271 | STRING_P { $$ = makeItemString(&$1); } 272 | | IN_P { $$ = makeItemString(&$1); } 273 | | IS_P { $$ = makeItemString(&$1); } 274 | | OR_P { $$ = makeItemString(&$1); } 275 | | AND_P { $$ = makeItemString(&$1); } 276 | | NOT_P { $$ = makeItemString(&$1); } 277 | | NULL_P { $$ = makeItemString(NULL); } 278 | | TRUE_P { $$ = makeItemBool(true); } 279 | | ARRAY_T { $$ = makeItemString(&$1); } 280 | | FALSE_P { $$ = makeItemBool(false); } 281 | | NUMERIC_T { $$ = makeItemString(&$1); } 282 | | OBJECT_T { $$ = makeItemString(&$1); } 283 | | STRING_T { $$ = makeItemString(&$1); } 284 | | BOOLEAN_T { $$ = makeItemString(&$1); } 285 | | NUMERIC_P { $$ = makeItemNumeric(&$1); } 286 | | INT_P { $$ = makeItemNumeric(&$1); } 287 | ; 288 | 289 | value_list: 290 | scalar_value { $$ = lappend(NIL, $1); } 291 | | value_list ',' scalar_value { $$ = lappend($1, $3); } 292 | ; 293 | 294 | numeric: 295 | NUMERIC_P { $$ = makeItemNumeric(&$1); } 296 | | INT_P { $$ = makeItemNumeric(&$1); } 297 | ; 298 | 299 | right_expr: 300 | '=' scalar_value { $$ = makeItemUnary(jqiEqual, $2); } 301 | | IN_P '(' value_list ')' { $$ = makeItemUnary(jqiIn, makeItemArray($3)); } 302 | | '=' array { $$ = makeItemUnary(jqiEqual, $2); } 303 | | '=' '*' { $$ = makeItemUnary(jqiEqual, makeItemType(jqiAny)); } 304 | | '<' numeric { $$ = makeItemUnary(jqiLess, $2); } 305 | | '>' numeric { $$ = makeItemUnary(jqiGreater, $2); } 306 | | '<' '=' numeric { $$ = makeItemUnary(jqiLessOrEqual, $3); } 307 | | '>' '=' numeric { $$ = makeItemUnary(jqiGreaterOrEqual, $3); } 308 | | '@' '>' array { $$ = makeItemUnary(jqiContains, $3); } 309 | | '<' '@' array { $$ = makeItemUnary(jqiContained, $3); } 310 | | '&' '&' array { $$ = makeItemUnary(jqiOverlap, $3); } 311 | | IS_P ARRAY_T { $$ = makeItemIs(jbvArray); } 312 | | IS_P NUMERIC_T { $$ = makeItemIs(jbvNumeric); } 313 | | IS_P OBJECT_T { $$ = makeItemIs(jbvObject); } 314 | | IS_P STRING_T { $$ = makeItemIs(jbvString); } 315 | | IS_P BOOLEAN_T { $$ = makeItemIs(jbvBool); } 316 | ; 317 | 318 | expr: 319 | path { $$ = makeItemList($1); } 320 | | path right_expr { $$ = makeItemList(lappend($1, $2)); } 321 | | path HINT_P right_expr { $3->hint = $2; $$ = makeItemList(lappend($1, $3)); } 322 | | NOT_P expr { $$ = makeItemUnary(jqiNot, $2); } 323 | /* 324 | * In next two lines NOT_P is a path actually, not a an 325 | * logical expression. 326 | */ 327 | | NOT_P HINT_P right_expr { $3->hint = $2; $$ = makeItemList(lappend(lappend(NIL, makeItemKey(&$1)), $3)); } 328 | | NOT_P right_expr { $$ = makeItemList(lappend(lappend(NIL, makeItemKey(&$1)), $2)); } 329 | | path '(' expr ')' { $$ = makeItemList(lappend($1, $3)); } 330 | | '(' expr ')' { $$ = $2; } 331 | | expr AND_P expr { $$ = makeItemBinary(jqiAnd, $1, $3); } 332 | | expr OR_P expr { $$ = makeItemBinary(jqiOr, $1, $3); } 333 | ; 334 | 335 | /* 336 | * key is always a string, not a bool or numeric 337 | */ 338 | key: 339 | '*' { $$ = makeItemType(jqiAny); } 340 | | '#' { $$ = makeItemType(jqiAnyArray); } 341 | | '%' { $$ = makeItemType(jqiAnyKey); } 342 | | '*' ':' { $$ = makeItemType(jqiAll); } 343 | | '#' ':' { $$ = makeItemType(jqiAllArray); } 344 | | '%' ':' { $$ = makeItemType(jqiAllKey); } 345 | | '$' { $$ = makeItemType(jqiCurrent); } 346 | | '@' '#' { $$ = makeItemType(jqiLength); } 347 | | '#' INT_P { $$ = makeIndexArray(&$2); } 348 | | STRING_P { $$ = makeItemKey(&$1); } 349 | | IN_P { $$ = makeItemKey(&$1); } 350 | | IS_P { $$ = makeItemKey(&$1); } 351 | | OR_P { $$ = makeItemKey(&$1); } 352 | | AND_P { $$ = makeItemKey(&$1); } 353 | | NULL_P { $$ = makeItemKey(&$1); } 354 | | TRUE_P { $$ = makeItemKey(&$1); } 355 | | ARRAY_T { $$ = makeItemKey(&$1); } 356 | | FALSE_P { $$ = makeItemKey(&$1); } 357 | | NUMERIC_T { $$ = makeItemKey(&$1); } 358 | | OBJECT_T { $$ = makeItemKey(&$1); } 359 | | STRING_T { $$ = makeItemKey(&$1); } 360 | | BOOLEAN_T { $$ = makeItemKey(&$1); } 361 | | NUMERIC_P { $$ = makeItemKey(&$1); } 362 | | INT_P { $$ = makeItemKey(&$1); } 363 | ; 364 | 365 | /* 366 | * NOT keyword needs separate processing 367 | */ 368 | key_any: 369 | key { $$ = $$; } 370 | | '?' '(' expr ')' { $$ = makeItemUnary(jqiFilter, $3); } 371 | | NOT_P { $$ = makeItemKey(&$1); } 372 | ; 373 | 374 | path: 375 | key { $$ = lappend(NIL, $1); } 376 | | '?' '(' expr ')' { $$ = lappend(NIL, makeItemUnary(jqiFilter, $3)); } 377 | | path '.' key_any { $$ = lappend($1, $3); } 378 | | NOT_P '.' key_any { $$ = lappend(lappend(NIL, makeItemKey(&$1)), $3); } 379 | ; 380 | 381 | %% 382 | 383 | #include "jsquery_scan.c" 384 | -------------------------------------------------------------------------------- /jsquery_io.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * jsquery_io.c 4 | * I/O functions for jsquery datatype 5 | * 6 | * Copyright (c) 2014, PostgreSQL Global Development Group 7 | * Portions Copyright (c) 2016-2021, Postgres Professional 8 | * Author: Teodor Sigaev 9 | * 10 | * IDENTIFICATION 11 | * contrib/jsquery/jsquery_io.c 12 | * 13 | *------------------------------------------------------------------------- 14 | */ 15 | 16 | #include "postgres.h" 17 | #include "miscadmin.h" 18 | #include "lib/stringinfo.h" 19 | #include "utils/builtins.h" 20 | #include "utils/json.h" 21 | 22 | #include "jsquery.h" 23 | 24 | PG_MODULE_MAGIC; 25 | 26 | static int 27 | flattenJsQueryParseItem(StringInfo buf, JsQueryParseItem *item, bool onlyCurrentInPath) 28 | { 29 | int32 pos = buf->len - VARHDRSZ; /* position from begining of jsquery data */ 30 | int32 chld, next; 31 | 32 | check_stack_depth(); 33 | 34 | Assert((item->type & item->hint) == 0); 35 | Assert((item->type & JSQ_HINT_MASK) == 0); 36 | 37 | appendStringInfoChar(buf, (char)(item->type | item->hint)); 38 | alignStringInfoInt(buf); 39 | 40 | next = (item->next) ? buf->len : 0; 41 | appendBinaryStringInfo(buf, (char*)&next /* fake value */, sizeof(next)); 42 | 43 | switch(item->type) 44 | { 45 | case jqiKey: 46 | if (onlyCurrentInPath) 47 | elog(ERROR,"Array length should be last in path"); 48 | /* fall through */ 49 | case jqiString: 50 | appendBinaryStringInfo(buf, (char*)&item->string.len, sizeof(item->string.len)); 51 | appendBinaryStringInfo(buf, item->string.val, item->string.len); 52 | appendStringInfoChar(buf, '\0'); 53 | break; 54 | case jqiNumeric: 55 | appendBinaryStringInfo(buf, (char*)item->numeric, VARSIZE(item->numeric)); 56 | break; 57 | case jqiBool: 58 | appendBinaryStringInfo(buf, (char*)&item->boolean, sizeof(item->boolean)); 59 | break; 60 | case jqiIs: 61 | appendBinaryStringInfo(buf, (char*)&item->isType, sizeof(item->isType)); 62 | break; 63 | case jqiArray: 64 | { 65 | int32 i, arrayStart; 66 | 67 | appendBinaryStringInfo(buf, (char*)&item->array.nelems, sizeof(item->array.nelems)); 68 | arrayStart = buf->len; 69 | 70 | /* reserve place for "pointers" to array's elements */ 71 | for(i=0; iarray.nelems; i++) 72 | appendBinaryStringInfo(buf, (char*)&i /* fake value */, sizeof(i)); 73 | 74 | for(i=0; iarray.nelems; i++) 75 | { 76 | chld = flattenJsQueryParseItem(buf, item->array.elems[i], onlyCurrentInPath); 77 | *(int32*)(buf->data + arrayStart + i * sizeof(i)) = chld; 78 | } 79 | 80 | } 81 | break; 82 | case jqiAnd: 83 | case jqiOr: 84 | { 85 | int32 left, right; 86 | 87 | left = buf->len; 88 | appendBinaryStringInfo(buf, (char*)&left /* fake value */, sizeof(left)); 89 | right = buf->len; 90 | appendBinaryStringInfo(buf, (char*)&right /* fake value */, sizeof(right)); 91 | 92 | chld = flattenJsQueryParseItem(buf, item->args.left, onlyCurrentInPath); 93 | *(int32*)(buf->data + left) = chld; 94 | chld = flattenJsQueryParseItem(buf, item->args.right, onlyCurrentInPath); 95 | *(int32*)(buf->data + right) = chld; 96 | } 97 | break; 98 | case jqiEqual: 99 | case jqiIn: 100 | case jqiLess: 101 | case jqiGreater: 102 | case jqiLessOrEqual: 103 | case jqiGreaterOrEqual: 104 | case jqiContains: 105 | case jqiContained: 106 | case jqiOverlap: 107 | case jqiNot: 108 | case jqiFilter: 109 | { 110 | int32 arg; 111 | 112 | arg = buf->len; 113 | appendBinaryStringInfo(buf, (char*)&arg /* fake value */, sizeof(arg)); 114 | 115 | chld = flattenJsQueryParseItem(buf, item->arg, onlyCurrentInPath); 116 | *(int32*)(buf->data + arg) = chld; 117 | } 118 | break; 119 | case jqiIndexArray: 120 | appendBinaryStringInfo(buf, (char*)&item->arrayIndex, 121 | sizeof(item->arrayIndex)); 122 | /* FALLTHROUGH */ /* keep svace quiet */ 123 | case jqiAny: 124 | case jqiAnyArray: 125 | case jqiAnyKey: 126 | case jqiAll: 127 | case jqiAllArray: 128 | case jqiAllKey: 129 | if (onlyCurrentInPath) 130 | elog(ERROR,"Array length should be last in path"); 131 | case jqiCurrent: 132 | case jqiNull: 133 | break; 134 | case jqiLength: 135 | onlyCurrentInPath = true; 136 | break; 137 | default: 138 | elog(ERROR, "Unknown type: %d", item->type); 139 | } 140 | 141 | if (item->next) 142 | { 143 | chld = flattenJsQueryParseItem(buf, item->next, onlyCurrentInPath); 144 | *(int32*)(buf->data + next) = chld; 145 | } 146 | 147 | return pos; 148 | } 149 | 150 | PG_FUNCTION_INFO_V1(jsquery_in); 151 | Datum 152 | jsquery_in(PG_FUNCTION_ARGS) 153 | { 154 | char *in = PG_GETARG_CSTRING(0); 155 | int32 len = strlen(in); 156 | JsQueryParseItem *jsquery = parsejsquery(in, len); 157 | JsQuery *res; 158 | StringInfoData buf; 159 | 160 | initStringInfo(&buf); 161 | enlargeStringInfo(&buf, 4 * len /* estimation */); 162 | 163 | appendStringInfoSpaces(&buf, VARHDRSZ); 164 | 165 | flattenJsQueryParseItem(&buf, jsquery, false); 166 | 167 | res = (JsQuery*)buf.data; 168 | SET_VARSIZE(res, buf.len); 169 | 170 | PG_RETURN_JSQUERY(res); 171 | } 172 | 173 | static void 174 | printHint(StringInfo buf, JsQueryHint hint) 175 | { 176 | switch(hint) 177 | { 178 | case jsqForceIndex: 179 | appendStringInfoString(buf, " /*-- index */ "); 180 | break; 181 | case jsqNoIndex: 182 | appendStringInfoString(buf, " /*-- noindex */ "); 183 | break; 184 | case jsqIndexDefault: 185 | break; 186 | default: 187 | elog(ERROR, "Unknown hint: %d", hint); 188 | } 189 | } 190 | 191 | static void 192 | printOperation(StringInfo buf, JsQueryItemType type) 193 | { 194 | switch(type) 195 | { 196 | case jqiAnd: 197 | appendBinaryStringInfo(buf, " AND ", 5); break; 198 | case jqiOr: 199 | appendBinaryStringInfo(buf, " OR ", 4); break; 200 | case jqiEqual: 201 | appendBinaryStringInfo(buf, " = ", 3); break; 202 | case jqiLess: 203 | appendBinaryStringInfo(buf, " < ", 3); break; 204 | case jqiGreater: 205 | appendBinaryStringInfo(buf, " > ", 3); break; 206 | case jqiLessOrEqual: 207 | appendBinaryStringInfo(buf, " <= ", 4); break; 208 | case jqiGreaterOrEqual: 209 | appendBinaryStringInfo(buf, " >= ", 4); break; 210 | case jqiContains: 211 | appendBinaryStringInfo(buf, " @> ", 4); break; 212 | case jqiContained: 213 | appendBinaryStringInfo(buf, " <@ ", 4); break; 214 | case jqiOverlap: 215 | appendBinaryStringInfo(buf, " && ", 4); break; 216 | default: 217 | elog(ERROR, "Unknown type: %d", type); 218 | } 219 | } 220 | 221 | static void 222 | printJsQueryItem(StringInfo buf, JsQueryItem *v, bool inKey, bool printBracketes) 223 | { 224 | JsQueryItem elem; 225 | bool first = true; 226 | 227 | check_stack_depth(); 228 | 229 | printHint(buf, v->hint); 230 | 231 | switch(v->type) 232 | { 233 | case jqiNull: 234 | appendStringInfoString(buf, "null"); 235 | break; 236 | case jqiKey: 237 | if (inKey) 238 | appendStringInfoChar(buf, '.'); 239 | /* fall through */ 240 | /* follow next */ 241 | case jqiString: 242 | escape_json(buf, jsqGetString(v, NULL)); 243 | break; 244 | case jqiNumeric: 245 | appendStringInfoString(buf, 246 | DatumGetCString(DirectFunctionCall1(numeric_out, 247 | PointerGetDatum(jsqGetNumeric(v))))); 248 | break; 249 | case jqiBool: 250 | if (jsqGetBool(v)) 251 | appendBinaryStringInfo(buf, "true", 4); 252 | else 253 | appendBinaryStringInfo(buf, "false", 5); 254 | break; 255 | case jqiIs: 256 | appendBinaryStringInfo(buf, " IS ", 4); 257 | switch(jsqGetIsType(v)) 258 | { 259 | case jbvString: 260 | appendBinaryStringInfo(buf, "STRING", 6); 261 | break; 262 | case jbvNumeric: 263 | appendBinaryStringInfo(buf, "NUMERIC", 7); 264 | break; 265 | case jbvBool: 266 | appendBinaryStringInfo(buf, "BOOLEAN", 7); 267 | break; 268 | case jbvArray: 269 | appendBinaryStringInfo(buf, "ARRAY", 5); 270 | break; 271 | case jbvObject: 272 | appendBinaryStringInfo(buf, "OBJECT", 6); 273 | break; 274 | default: 275 | elog(ERROR, "Unknown type for IS: %d", jsqGetIsType(v)); 276 | break; 277 | } 278 | break; 279 | case jqiArray: 280 | if (printBracketes) 281 | appendStringInfoChar(buf, '['); 282 | 283 | while(jsqIterateArray(v, &elem)) 284 | { 285 | if (first == false) 286 | appendBinaryStringInfo(buf, ", ", 2); 287 | else 288 | first = false; 289 | printJsQueryItem(buf, &elem, false, true); 290 | } 291 | 292 | if (printBracketes) 293 | appendStringInfoChar(buf, ']'); 294 | break; 295 | case jqiAnd: 296 | case jqiOr: 297 | appendStringInfoChar(buf, '('); 298 | jsqGetLeftArg(v, &elem); 299 | printJsQueryItem(buf, &elem, false, true); 300 | printOperation(buf, v->type); 301 | jsqGetRightArg(v, &elem); 302 | printJsQueryItem(buf, &elem, false, true); 303 | appendStringInfoChar(buf, ')'); 304 | break; 305 | case jqiEqual: 306 | case jqiLess: 307 | case jqiGreater: 308 | case jqiLessOrEqual: 309 | case jqiGreaterOrEqual: 310 | case jqiContains: 311 | case jqiContained: 312 | case jqiOverlap: 313 | printOperation(buf, v->type); 314 | jsqGetArg(v, &elem); 315 | printJsQueryItem(buf, &elem, false, true); 316 | break; 317 | case jqiIn: 318 | appendBinaryStringInfo(buf, " IN (", 5); 319 | jsqGetArg(v, &elem); 320 | printJsQueryItem(buf, &elem, false, false); 321 | appendStringInfoChar(buf, ')'); 322 | break; 323 | case jqiNot: 324 | appendStringInfoChar(buf, '('); 325 | appendBinaryStringInfo(buf, "NOT ", 4); 326 | jsqGetArg(v, &elem); 327 | printJsQueryItem(buf, &elem, false, true); 328 | appendStringInfoChar(buf, ')'); 329 | break; 330 | case jqiCurrent: 331 | if (inKey) 332 | appendStringInfoChar(buf, '.'); 333 | appendStringInfoChar(buf, '$'); 334 | break; 335 | case jqiLength: 336 | if (inKey) 337 | appendStringInfoChar(buf, '.'); 338 | appendStringInfoChar(buf, '@'); 339 | appendStringInfoChar(buf, '#'); 340 | break; 341 | case jqiAny: 342 | if (inKey) 343 | appendStringInfoChar(buf, '.'); 344 | appendStringInfoChar(buf, '*'); 345 | break; 346 | case jqiAnyArray: 347 | if (inKey) 348 | appendStringInfoChar(buf, '.'); 349 | appendStringInfoChar(buf, '#'); 350 | break; 351 | case jqiAnyKey: 352 | if (inKey) 353 | appendStringInfoChar(buf, '.'); 354 | appendStringInfoChar(buf, '%'); 355 | break; 356 | case jqiAll: 357 | if (inKey) 358 | appendStringInfoChar(buf, '.'); 359 | appendStringInfoChar(buf, '*'); 360 | appendStringInfoChar(buf, ':'); 361 | break; 362 | case jqiAllArray: 363 | if (inKey) 364 | appendStringInfoChar(buf, '.'); 365 | appendStringInfoChar(buf, '#'); 366 | appendStringInfoChar(buf, ':'); 367 | break; 368 | case jqiAllKey: 369 | if (inKey) 370 | appendStringInfoChar(buf, '.'); 371 | appendStringInfoChar(buf, '%'); 372 | appendStringInfoChar(buf, ':'); 373 | break; 374 | case jqiIndexArray: 375 | if (inKey) 376 | appendStringInfoChar(buf, '.'); 377 | appendStringInfo(buf, "#%u", v->arrayIndex); 378 | break; 379 | case jqiFilter: 380 | if (inKey) 381 | appendStringInfoChar(buf, '.'); 382 | appendBinaryStringInfo(buf, " ?(", 3); 383 | jsqGetArg(v, &elem); 384 | printJsQueryItem(buf, &elem, false, false); 385 | appendBinaryStringInfo(buf, ") ", 2); 386 | break; 387 | default: 388 | elog(ERROR, "Unknown JsQueryItem type: %d", v->type); 389 | } 390 | 391 | if (jsqGetNext(v, &elem)) 392 | printJsQueryItem(buf, &elem, true, true); 393 | } 394 | 395 | PG_FUNCTION_INFO_V1(jsquery_out); 396 | Datum 397 | jsquery_out(PG_FUNCTION_ARGS) 398 | { 399 | JsQuery *in = PG_GETARG_JSQUERY(0); 400 | StringInfoData buf; 401 | JsQueryItem v; 402 | 403 | initStringInfo(&buf); 404 | enlargeStringInfo(&buf, VARSIZE(in) /* estimation */); 405 | 406 | jsqInit(&v, in); 407 | printJsQueryItem(&buf, &v, false, true); 408 | 409 | PG_RETURN_CSTRING(buf.data); 410 | } 411 | 412 | 413 | -------------------------------------------------------------------------------- /jsquery_op.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * jsquery_op.c 4 | * Functions and operations over jsquery/jsonb datatypes 5 | * 6 | * Copyright (c) 2014, PostgreSQL Global Development Group 7 | * Portions Copyright (c) 2017-2021, Postgres Professional 8 | * Author: Teodor Sigaev 9 | * 10 | * IDENTIFICATION 11 | * contrib/jsquery/jsquery_op.c 12 | * 13 | *------------------------------------------------------------------------- 14 | */ 15 | 16 | #include "postgres.h" 17 | 18 | #include "miscadmin.h" 19 | #include "utils/builtins.h" 20 | #include "utils/pg_crc.h" 21 | #if PG_VERSION_NUM >= 90500 22 | /* 23 | * We have to keep same checksum algorithm as in pre-9.5 in order to be 24 | * pg_upgradeable. 25 | */ 26 | #define INIT_CRC32 INIT_LEGACY_CRC32 27 | #define FIN_CRC32 FIN_LEGACY_CRC32 28 | #define COMP_CRC32 COMP_LEGACY_CRC32 29 | #endif 30 | 31 | #include "jsquery.h" 32 | 33 | typedef struct ResultAccum { 34 | StringInfo buf; 35 | bool missAppend; 36 | JsonbParseState *jbArrayState; 37 | } ResultAccum; 38 | 39 | 40 | static bool recursiveExecute(JsQueryItem *jsq, JsonbValue *jb, JsQueryItem *jsqLeftArg, 41 | ResultAccum *ra); 42 | 43 | static void 44 | appendResult(ResultAccum *ra, JsonbValue *jb) 45 | { 46 | if (ra == NULL || ra->missAppend == true) 47 | return; 48 | 49 | if (ra->jbArrayState == NULL) 50 | pushJsonbValue(&ra->jbArrayState, WJB_BEGIN_ARRAY, NULL); 51 | 52 | pushJsonbValue(&ra->jbArrayState, WJB_ELEM, jb); 53 | } 54 | 55 | static void 56 | concatResult(ResultAccum *ra, JsonbParseState *a, JsonbParseState *b) 57 | { 58 | Jsonb *value; 59 | JsonbIterator *it; 60 | int32 r; 61 | JsonbValue v; 62 | 63 | Assert(a); 64 | Assert(b); 65 | 66 | ra->jbArrayState = a; 67 | 68 | value = JsonbValueToJsonb(pushJsonbValue(&b, WJB_END_ARRAY, NULL)); 69 | it = JsonbIteratorInit(&value->root); 70 | 71 | while((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE) 72 | if (r == WJB_ELEM) 73 | pushJsonbValue(&ra->jbArrayState, WJB_ELEM, &v); 74 | } 75 | 76 | static int 77 | compareNumeric(Numeric a, Numeric b) 78 | { 79 | return DatumGetInt32( 80 | DirectFunctionCall2( 81 | numeric_cmp, 82 | PointerGetDatum(a), 83 | PointerGetDatum(b) 84 | ) 85 | ); 86 | } 87 | 88 | #define jbvScalar jbvBinary 89 | static int 90 | JsonbType(JsonbValue *jb) 91 | { 92 | int type = jb->type; 93 | 94 | if (jb->type == jbvBinary) 95 | { 96 | JsonbContainer *jbc = jb->val.binary.data; 97 | 98 | if (jbc->header & JB_FSCALAR) 99 | type = jbvScalar; 100 | else if (jbc->header & JB_FOBJECT) 101 | type = jbvObject; 102 | else if (jbc->header & JB_FARRAY) 103 | type = jbvArray; 104 | else 105 | elog(ERROR, "Unknown container type: 0x%08x", jbc->header); 106 | } 107 | 108 | return type; 109 | } 110 | 111 | static bool 112 | recursiveAny(JsQueryItem *jsq, JsonbValue *jb, ResultAccum *ra) 113 | { 114 | bool res = false; 115 | JsonbIterator *it; 116 | int32 r; 117 | JsonbValue v; 118 | 119 | check_stack_depth(); 120 | 121 | it = JsonbIteratorInit(jb->val.binary.data); 122 | 123 | while(res == false && (r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE) 124 | { 125 | if (r == WJB_KEY) 126 | { 127 | r = JsonbIteratorNext(&it, &v, true); 128 | Assert(r == WJB_VALUE); 129 | } 130 | 131 | if (r == WJB_VALUE || r == WJB_ELEM) 132 | { 133 | /* 134 | * we don't need actually store result, we may need to store whole 135 | * object/array 136 | */ 137 | res = recursiveExecute(jsq, &v, NULL, ra); 138 | 139 | if (res == false && v.type == jbvBinary) 140 | res = recursiveAny(jsq, &v, ra); 141 | } 142 | } 143 | 144 | return res; 145 | } 146 | 147 | static bool 148 | recursiveAll(JsQueryItem *jsq, JsonbValue *jb, ResultAccum *ra) 149 | { 150 | bool res = true; 151 | JsonbIterator *it; 152 | int32 r; 153 | JsonbValue v; 154 | 155 | check_stack_depth(); 156 | 157 | it = JsonbIteratorInit(jb->val.binary.data); 158 | 159 | while((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE) 160 | { 161 | if (r == WJB_KEY) 162 | { 163 | r = JsonbIteratorNext(&it, &v, true); 164 | Assert(r == WJB_VALUE); 165 | } 166 | 167 | if (r == WJB_VALUE || r == WJB_ELEM) 168 | { 169 | /* 170 | * we don't need actually store result, we may need to store whole 171 | * object/array 172 | */ 173 | if ((res = recursiveExecute(jsq, &v, NULL, ra)) == true) 174 | { 175 | if (v.type == jbvBinary) 176 | res = recursiveAll(jsq, &v, ra); 177 | } 178 | 179 | if (res == false) 180 | break; 181 | } 182 | } 183 | 184 | return res; 185 | } 186 | 187 | static bool 188 | checkScalarEquality(JsQueryItem *jsq, JsonbValue *jb) 189 | { 190 | int len; 191 | char *s; 192 | 193 | if (jsq->type == jqiAny) 194 | return true; 195 | 196 | if (jb->type == jbvBinary) 197 | return false; 198 | 199 | if ((int)jb->type != (int)jsq->type /* see enums */) 200 | return false; 201 | 202 | switch(jsq->type) 203 | { 204 | case jqiNull: 205 | return true; 206 | case jqiString: 207 | s = jsqGetString(jsq, &len); 208 | return (len == jb->val.string.len && memcmp(jb->val.string.val, s, len) == 0); 209 | case jqiBool: 210 | return (jb->val.boolean == jsqGetBool(jsq)); 211 | case jqiNumeric: 212 | return (compareNumeric(jsqGetNumeric(jsq), jb->val.numeric) == 0); 213 | default: 214 | elog(ERROR,"Wrong state"); 215 | } 216 | 217 | return false; 218 | } 219 | 220 | static bool 221 | checkArrayEquality(JsQueryItem *jsq, JsonbValue *jb) 222 | { 223 | int32 r; 224 | JsonbIterator *it; 225 | JsonbValue v; 226 | JsQueryItem elem; 227 | 228 | if (!(jsq->type == jqiArray && JsonbType(jb) == jbvArray)) 229 | return false; 230 | 231 | 232 | it = JsonbIteratorInit(jb->val.binary.data); 233 | r = JsonbIteratorNext(&it, &v, true); 234 | Assert(r == WJB_BEGIN_ARRAY); 235 | 236 | if (v.val.array.nElems != jsq->array.nelems) 237 | return false; 238 | 239 | while((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE) 240 | { 241 | if (r != WJB_ELEM) 242 | continue; 243 | 244 | jsqIterateArray(jsq, &elem); 245 | 246 | if (checkScalarEquality(&elem, &v) == false) 247 | return false; 248 | } 249 | 250 | return true; 251 | } 252 | 253 | static bool 254 | checkScalarIn(JsQueryItem *jsq, JsonbValue *jb) 255 | { 256 | JsQueryItem elem; 257 | 258 | if (jb->type == jbvBinary) 259 | return false; 260 | 261 | if (jsq->type != jqiArray) 262 | return false; 263 | 264 | while(jsqIterateArray(jsq, &elem)) 265 | if (checkScalarEquality(&elem, jb)) 266 | return true; 267 | 268 | return false; 269 | } 270 | 271 | static bool 272 | executeArrayOp(JsQueryItem *jsq, int32 op, JsonbValue *jb) 273 | { 274 | int32 r = 0; /* keep static analyzer quiet */ 275 | JsonbIterator *it; 276 | JsonbValue v; 277 | JsQueryItem elem; 278 | bool res; 279 | 280 | if (JsonbType(jb) != jbvArray) 281 | return false; 282 | if (jsq->type != jqiArray) 283 | return false; 284 | 285 | if (op == jqiContains) 286 | { 287 | while(jsqIterateArray(jsq, &elem)) 288 | { 289 | res = false; 290 | 291 | it = JsonbIteratorInit(jb->val.binary.data); 292 | 293 | while(res == false && (r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE) 294 | { 295 | if (r == WJB_ELEM && checkScalarEquality(&elem, &v)) 296 | res = true; 297 | } 298 | 299 | if (res == false) 300 | return false; 301 | } 302 | } 303 | else 304 | { 305 | it = JsonbIteratorInit(jb->val.binary.data); 306 | 307 | while((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE) 308 | { 309 | if (r == WJB_ELEM) 310 | { 311 | res = false; 312 | 313 | jsqIterateInit(jsq); 314 | while(jsqIterateArray(jsq, &elem)) 315 | { 316 | if (checkScalarEquality(&elem, &v)) 317 | { 318 | if (op == jqiOverlap) 319 | return true; 320 | res = true; 321 | break; 322 | } 323 | } 324 | jsqIterateDestroy(jsq); 325 | 326 | if (op == jqiContained && res == false) 327 | return false; 328 | } 329 | } 330 | 331 | if (op == jqiOverlap) 332 | return false; 333 | } 334 | 335 | return true; 336 | } 337 | 338 | static bool 339 | makeCompare(JsQueryItem *jsq, int32 op, JsonbValue *jb) 340 | { 341 | int res; 342 | 343 | if (jb->type != jbvNumeric) 344 | return false; 345 | if (jsq->type != jqiNumeric) 346 | return false; 347 | 348 | res = compareNumeric(jb->val.numeric, jsqGetNumeric(jsq)); 349 | 350 | switch(op) 351 | { 352 | case jqiEqual: 353 | return (res == 0); 354 | case jqiLess: 355 | return (res < 0); 356 | case jqiGreater: 357 | return (res > 0); 358 | case jqiLessOrEqual: 359 | return (res <= 0); 360 | case jqiGreaterOrEqual: 361 | return (res >= 0); 362 | default: 363 | elog(ERROR, "Unknown operation"); 364 | } 365 | 366 | return false; 367 | } 368 | 369 | static bool 370 | executeExpr(JsQueryItem *jsq, int32 op, JsonbValue *jb, JsQueryItem *jsqLeftArg) 371 | { 372 | bool res = false; 373 | /* 374 | * read arg type 375 | */ 376 | Assert(jsqGetNext(jsq, NULL) == false); 377 | Assert(jsq->type == jqiAny || jsq->type == jqiString || jsq->type == jqiNumeric || 378 | jsq->type == jqiNull || jsq->type == jqiBool || jsq->type == jqiArray); 379 | 380 | if (jsqLeftArg && jsqLeftArg->type == jqiLength) 381 | { 382 | if (JsonbType(jb) == jbvArray || JsonbType(jb) == jbvObject) 383 | { 384 | int32 length; 385 | JsonbIterator *it; 386 | JsonbValue v; 387 | int r; 388 | 389 | it = JsonbIteratorInit(jb->val.binary.data); 390 | r = JsonbIteratorNext(&it, &v, true); 391 | Assert(r == WJB_BEGIN_ARRAY || r == WJB_BEGIN_OBJECT); 392 | 393 | length = (r == WJB_BEGIN_ARRAY) ? v.val.array.nElems : v.val.object.nPairs; 394 | 395 | v.type = jbvNumeric; 396 | v.val.numeric = DatumGetNumeric(DirectFunctionCall1(int4_numeric, Int32GetDatum(length))); 397 | 398 | switch(op) 399 | { 400 | case jqiEqual: 401 | case jqiLess: 402 | case jqiGreater: 403 | case jqiLessOrEqual: 404 | case jqiGreaterOrEqual: 405 | res = makeCompare(jsq, op, &v); 406 | break; 407 | case jqiIn: 408 | res = checkScalarIn(jsq, &v); 409 | break; 410 | case jqiOverlap: 411 | case jqiContains: 412 | case jqiContained: 413 | break; 414 | default: 415 | elog(ERROR, "Unknown operation"); 416 | } 417 | } 418 | } 419 | else 420 | { 421 | switch(op) 422 | { 423 | case jqiEqual: 424 | if (JsonbType(jb) == jbvArray && jsq->type == jqiArray) 425 | res = checkArrayEquality(jsq, jb); 426 | else 427 | res = checkScalarEquality(jsq, jb); 428 | break; 429 | case jqiIn: 430 | res = checkScalarIn(jsq, jb); 431 | break; 432 | case jqiOverlap: 433 | case jqiContains: 434 | case jqiContained: 435 | res = executeArrayOp(jsq, op, jb); 436 | break; 437 | case jqiLess: 438 | case jqiGreater: 439 | case jqiLessOrEqual: 440 | case jqiGreaterOrEqual: 441 | res = makeCompare(jsq, op, jb); 442 | break; 443 | default: 444 | elog(ERROR, "Unknown operation"); 445 | } 446 | } 447 | 448 | return res; 449 | } 450 | 451 | static bool 452 | recursiveExecute(JsQueryItem *jsq, JsonbValue *jb, JsQueryItem *jsqLeftArg, 453 | ResultAccum *ra) 454 | { 455 | JsQueryItem elem; 456 | bool res = false; 457 | 458 | check_stack_depth(); 459 | 460 | switch(jsq->type) { 461 | case jqiAnd: 462 | { 463 | JsonbParseState *saveJbArrayState = NULL; 464 | 465 | jsqGetLeftArg(jsq, &elem); 466 | if (ra && ra->missAppend == false) 467 | { 468 | saveJbArrayState = ra->jbArrayState; 469 | ra->jbArrayState = NULL; 470 | } 471 | 472 | res = recursiveExecute(&elem, jb, jsqLeftArg, ra); 473 | if (res == true) 474 | { 475 | jsqGetRightArg(jsq, &elem); 476 | res = recursiveExecute(&elem, jb, jsqLeftArg, ra); 477 | } 478 | 479 | if (ra && ra->missAppend == false) 480 | { 481 | if (res == true) 482 | { 483 | if (saveJbArrayState != NULL) 484 | /* append args lists to current */ 485 | concatResult(ra, saveJbArrayState, ra->jbArrayState); 486 | } 487 | else 488 | ra->jbArrayState = saveJbArrayState; 489 | } 490 | 491 | break; 492 | } 493 | case jqiOr: 494 | jsqGetLeftArg(jsq, &elem); 495 | res = recursiveExecute(&elem, jb, jsqLeftArg, ra); 496 | if (res == false) 497 | { 498 | jsqGetRightArg(jsq, &elem); 499 | res = recursiveExecute(&elem, jb, jsqLeftArg, ra); 500 | } 501 | break; 502 | case jqiNot: 503 | { 504 | bool saveMissAppend = (ra) ? ra->missAppend : true; 505 | 506 | jsqGetArg(jsq, &elem); 507 | if (ra) 508 | ra->missAppend = true; 509 | res = !recursiveExecute(&elem, jb, jsqLeftArg, ra); 510 | if (ra) 511 | ra->missAppend = saveMissAppend; 512 | 513 | break; 514 | } 515 | case jqiKey: 516 | if (JsonbType(jb) == jbvObject) { 517 | JsonbValue *v, key; 518 | 519 | key.type = jbvString; 520 | key.val.string.val = jsqGetString(jsq, &key.val.string.len); 521 | 522 | v = findJsonbValueFromContainer(jb->val.binary.data, JB_FOBJECT, &key); 523 | 524 | if (v != NULL) 525 | { 526 | if (jsqGetNext(jsq, &elem) == false) 527 | { 528 | appendResult(ra, v); 529 | res = true; 530 | } 531 | else 532 | res = recursiveExecute(&elem, v, NULL, ra); 533 | pfree(v); 534 | } 535 | } 536 | break; 537 | case jqiCurrent: 538 | if (jsqGetNext(jsq, &elem) == false) 539 | { 540 | appendResult(ra, jb); 541 | res = true; 542 | } 543 | else if (JsonbType(jb) == jbvScalar) 544 | { 545 | JsonbIterator *it; 546 | int32 r PG_USED_FOR_ASSERTS_ONLY; 547 | JsonbValue v; 548 | 549 | it = JsonbIteratorInit(jb->val.binary.data); 550 | 551 | r = JsonbIteratorNext(&it, &v, true); 552 | Assert(r == WJB_BEGIN_ARRAY); 553 | Assert(v.val.array.rawScalar == 1); 554 | Assert(v.val.array.nElems == 1); 555 | 556 | r = JsonbIteratorNext(&it, &v, true); 557 | Assert(r == WJB_ELEM); 558 | 559 | res = recursiveExecute(&elem, &v, jsqLeftArg, ra); 560 | } 561 | else 562 | { 563 | res = recursiveExecute(&elem, jb, jsqLeftArg, ra); 564 | } 565 | break; 566 | case jqiAny: 567 | if (jsqGetNext(jsq, &elem) == false) 568 | { 569 | res = true; 570 | appendResult(ra, jb); 571 | } 572 | else if (recursiveExecute(&elem, jb, NULL, ra)) 573 | res = true; 574 | else if (jb->type == jbvBinary) 575 | res = recursiveAny(&elem, jb, ra); 576 | break; 577 | case jqiAll: 578 | if (jsqGetNext(jsq, &elem) == false) 579 | { 580 | res = true; 581 | appendResult(ra, jb); 582 | } 583 | if ((res = recursiveExecute(&elem, jb, NULL, ra)) == true) 584 | { 585 | if (jb->type == jbvBinary) 586 | res = recursiveAll(&elem, jb, ra); 587 | } 588 | break; 589 | case jqiAnyArray: 590 | case jqiAllArray: 591 | if (JsonbType(jb) == jbvArray) 592 | { 593 | JsonbIterator *it; 594 | int32 r; 595 | JsonbValue v; 596 | bool anyres = false; 597 | bool hasNext; 598 | 599 | hasNext = jsqGetNext(jsq, &elem); 600 | it = JsonbIteratorInit(jb->val.binary.data); 601 | 602 | if (hasNext == false) 603 | { 604 | res = true; 605 | 606 | while(ra && (r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE) 607 | if (r == WJB_ELEM) 608 | appendResult(ra, &v); 609 | 610 | break; 611 | } 612 | 613 | if (jsq->type == jqiAllArray) 614 | res = true; 615 | 616 | while((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE) 617 | { 618 | if (r == WJB_ELEM) 619 | { 620 | res = recursiveExecute(&elem, &v, NULL, ra); 621 | 622 | if (jsq->type == jqiAnyArray) 623 | { 624 | anyres |= res; 625 | if (res == true && 626 | (ra == NULL || ra->missAppend == true)) 627 | break; 628 | } 629 | else if (jsq->type == jqiAllArray) 630 | { 631 | if (res == false) 632 | break; 633 | } 634 | } 635 | } 636 | 637 | if (jsq->type == jqiAnyArray) 638 | res = anyres; 639 | } 640 | break; 641 | case jqiIndexArray: 642 | if (JsonbType(jb) == jbvArray) 643 | { 644 | JsonbValue *v; 645 | 646 | v = getIthJsonbValueFromContainer(jb->val.binary.data, 647 | jsq->arrayIndex); 648 | 649 | if (v) 650 | { 651 | if (jsqGetNext(jsq, &elem) == false) 652 | { 653 | res = true; 654 | appendResult(ra, v); 655 | } 656 | else 657 | res = recursiveExecute(&elem, v, NULL, ra); 658 | } 659 | } 660 | break; 661 | case jqiAnyKey: 662 | case jqiAllKey: 663 | if (JsonbType(jb) == jbvObject) 664 | { 665 | JsonbIterator *it; 666 | int32 r; 667 | JsonbValue v; 668 | bool anyres = false; 669 | bool hasNext; 670 | 671 | hasNext = jsqGetNext(jsq, &elem); 672 | it = JsonbIteratorInit(jb->val.binary.data); 673 | 674 | if (hasNext == false) 675 | { 676 | res = true; 677 | 678 | while(ra && (r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE) 679 | if (r == WJB_VALUE) 680 | appendResult(ra, &v); 681 | 682 | break; 683 | } 684 | 685 | if (jsq->type == jqiAllKey) 686 | res = true; 687 | 688 | while((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE) 689 | { 690 | if (r == WJB_VALUE) 691 | { 692 | res = recursiveExecute(&elem, &v, NULL, ra); 693 | 694 | if (jsq->type == jqiAnyKey) 695 | { 696 | anyres |= res; 697 | if (res == true && 698 | (ra == NULL || ra->missAppend == true)) 699 | break; 700 | } 701 | else if (jsq->type == jqiAllKey) 702 | { 703 | if (res == false) 704 | break; 705 | } 706 | } 707 | } 708 | 709 | if (jsq->type == jqiAnyKey) 710 | res = anyres; 711 | } 712 | break; 713 | case jqiEqual: 714 | case jqiIn: 715 | case jqiLess: 716 | case jqiGreater: 717 | case jqiLessOrEqual: 718 | case jqiGreaterOrEqual: 719 | case jqiContains: 720 | case jqiContained: 721 | case jqiOverlap: 722 | jsqGetArg(jsq, &elem); 723 | res = executeExpr(&elem, jsq->type, jb, jsqLeftArg); 724 | break; 725 | case jqiLength: 726 | jsqGetNext(jsq, &elem); 727 | res = recursiveExecute(&elem, jb, jsq, ra); 728 | break; 729 | case jqiIs: 730 | if (JsonbType(jb) == jbvScalar) 731 | { 732 | JsonbIterator *it; 733 | int32 r PG_USED_FOR_ASSERTS_ONLY; 734 | JsonbValue v; 735 | 736 | it = JsonbIteratorInit(jb->val.binary.data); 737 | 738 | r = JsonbIteratorNext(&it, &v, true); 739 | Assert(r == WJB_BEGIN_ARRAY); 740 | Assert(v.val.array.rawScalar == 1); 741 | Assert(v.val.array.nElems == 1); 742 | 743 | r = JsonbIteratorNext(&it, &v, true); 744 | Assert(r == WJB_ELEM); 745 | 746 | res = (jsqGetIsType(jsq) == JsonbType(&v)); 747 | } 748 | else 749 | { 750 | res = (jsqGetIsType(jsq) == JsonbType(jb)); 751 | } 752 | break; 753 | case jqiFilter: 754 | { 755 | bool saveMissAppend = (ra) ? ra->missAppend : true; 756 | 757 | jsqGetArg(jsq, &elem); 758 | if (ra) 759 | ra->missAppend = true; 760 | res = recursiveExecute(&elem, jb, jsqLeftArg, ra); 761 | if (ra) 762 | ra->missAppend = saveMissAppend; 763 | if (res) { 764 | if (jsqGetNext(jsq, &elem) == false) 765 | { 766 | appendResult(ra, jb); 767 | res = true; 768 | } 769 | else 770 | res = recursiveExecute(&elem, jb, jsqLeftArg, ra); 771 | } 772 | } 773 | break; 774 | default: 775 | elog(ERROR,"Wrong state: %d", jsq->type); 776 | } 777 | 778 | return res; 779 | } 780 | 781 | PG_FUNCTION_INFO_V1(jsquery_json_exec); 782 | Datum 783 | jsquery_json_exec(PG_FUNCTION_ARGS) 784 | { 785 | JsQuery *jq = PG_GETARG_JSQUERY(0); 786 | Jsonb *jb = PG_GETARG_JSONB_P(1); 787 | bool res; 788 | JsonbValue jbv; 789 | JsQueryItem jsq; 790 | 791 | jbv.type = jbvBinary; 792 | jbv.val.binary.data = &jb->root; 793 | jbv.val.binary.len = VARSIZE_ANY_EXHDR(jb); 794 | 795 | jsqInit(&jsq, jq); 796 | 797 | res = recursiveExecute(&jsq, &jbv, NULL, NULL); 798 | 799 | PG_FREE_IF_COPY(jq, 0); 800 | PG_FREE_IF_COPY(jb, 1); 801 | 802 | PG_RETURN_BOOL(res); 803 | } 804 | 805 | PG_FUNCTION_INFO_V1(json_jsquery_exec); 806 | Datum 807 | json_jsquery_exec(PG_FUNCTION_ARGS) 808 | { 809 | Jsonb *jb = PG_GETARG_JSONB_P(0); 810 | JsQuery *jq = PG_GETARG_JSQUERY(1); 811 | bool res; 812 | JsonbValue jbv; 813 | JsQueryItem jsq; 814 | 815 | jbv.type = jbvBinary; 816 | jbv.val.binary.data = &jb->root; 817 | jbv.val.binary.len = VARSIZE_ANY_EXHDR(jb); 818 | 819 | jsqInit(&jsq, jq); 820 | 821 | res = recursiveExecute(&jsq, &jbv, NULL, NULL); 822 | 823 | PG_FREE_IF_COPY(jb, 0); 824 | PG_FREE_IF_COPY(jq, 1); 825 | 826 | PG_RETURN_BOOL(res); 827 | } 828 | 829 | PG_FUNCTION_INFO_V1(json_jsquery_filter); 830 | Datum 831 | json_jsquery_filter(PG_FUNCTION_ARGS) 832 | { 833 | Jsonb *jb = PG_GETARG_JSONB_P(0); 834 | JsQuery *jq = PG_GETARG_JSQUERY(1); 835 | Jsonb *res = NULL; 836 | JsonbValue jbv; 837 | JsQueryItem jsq; 838 | ResultAccum ra; 839 | 840 | jbv.type = jbvBinary; 841 | jbv.val.binary.data = &jb->root; 842 | jbv.val.binary.len = VARSIZE_ANY_EXHDR(jb); 843 | 844 | jsqInit(&jsq, jq); 845 | memset(&ra, 0, sizeof(ra)); 846 | 847 | recursiveExecute(&jsq, &jbv, NULL, &ra); 848 | 849 | if (ra.jbArrayState) 850 | { 851 | res = JsonbValueToJsonb( 852 | pushJsonbValue(&ra.jbArrayState, WJB_END_ARRAY, NULL) 853 | ); 854 | } 855 | 856 | PG_FREE_IF_COPY(jb, 0); 857 | PG_FREE_IF_COPY(jq, 1); 858 | 859 | if (res) 860 | PG_RETURN_JSONB_P(res); 861 | 862 | PG_RETURN_NULL(); 863 | } 864 | 865 | 866 | static int 867 | compareJsQuery(JsQueryItem *v1, JsQueryItem *v2) 868 | { 869 | JsQueryItem elem1, elem2; 870 | int32 res = 0; 871 | 872 | check_stack_depth(); 873 | 874 | if (v1->type != v2->type) 875 | return (v1->type > v2->type) ? 1 : -1; 876 | 877 | switch(v1->type) 878 | { 879 | case jqiNull: 880 | case jqiCurrent: 881 | case jqiLength: 882 | case jqiAny: 883 | case jqiAnyArray: 884 | case jqiAnyKey: 885 | case jqiAll: 886 | case jqiAllArray: 887 | case jqiAllKey: 888 | case jqiFilter: 889 | break; 890 | case jqiIndexArray: 891 | if (v1->arrayIndex != v2->arrayIndex) 892 | res = (v1->arrayIndex > v2->arrayIndex) ? 1 : -1; 893 | break; 894 | case jqiKey: 895 | case jqiString: 896 | { 897 | int32 len1, len2; 898 | char *s1, *s2; 899 | 900 | s1 = jsqGetString(v1, &len1); 901 | s2 = jsqGetString(v2, &len2); 902 | 903 | if (len1 != len2) 904 | res = (len1 > len2) ? 1 : -1; 905 | else 906 | res = memcmp(s1, s2, len1); 907 | } 908 | break; 909 | case jqiNumeric: 910 | res = compareNumeric(jsqGetNumeric(v1), jsqGetNumeric(v2)); 911 | break; 912 | case jqiBool: 913 | if (jsqGetBool(v1) != jsqGetBool(v2)) 914 | res = (jsqGetBool(v1) > jsqGetBool(v2)) ? 1 : -1; 915 | break; 916 | case jqiArray: 917 | if (v1->array.nelems != v2->array.nelems) 918 | res = (v1->array.nelems > v2->array.nelems) ? 1 : -1; 919 | 920 | while(res == 0 && jsqIterateArray(v1, &elem1) && jsqIterateArray(v2, &elem2)) 921 | res = compareJsQuery(&elem1, &elem2); 922 | break; 923 | case jqiAnd: 924 | case jqiOr: 925 | jsqGetLeftArg(v1, &elem1); 926 | jsqGetLeftArg(v2, &elem2); 927 | 928 | res = compareJsQuery(&elem1, &elem2); 929 | 930 | if (res == 0) 931 | { 932 | jsqGetRightArg(v1, &elem1); 933 | jsqGetRightArg(v2, &elem2); 934 | 935 | res = compareJsQuery(&elem1, &elem2); 936 | } 937 | break; 938 | case jqiEqual: 939 | case jqiIn: 940 | case jqiLess: 941 | case jqiGreater: 942 | case jqiLessOrEqual: 943 | case jqiGreaterOrEqual: 944 | case jqiContains: 945 | case jqiContained: 946 | case jqiOverlap: 947 | case jqiNot: 948 | jsqGetArg(v1, &elem1); 949 | jsqGetArg(v2, &elem2); 950 | 951 | res = compareJsQuery(&elem1, &elem2); 952 | break; 953 | default: 954 | elog(ERROR, "Unknown JsQueryItem type: %d", v1->type); 955 | } 956 | 957 | if (res == 0) 958 | { 959 | if (jsqGetNext(v1, &elem1)) 960 | { 961 | if (jsqGetNext(v2, &elem2)) 962 | res = compareJsQuery(&elem1, &elem2); 963 | else 964 | res = 1; 965 | } 966 | else if (jsqGetNext(v2, &elem2)) 967 | { 968 | res = -1; 969 | } 970 | } 971 | 972 | return res; 973 | } 974 | 975 | PG_FUNCTION_INFO_V1(jsquery_cmp); 976 | Datum 977 | jsquery_cmp(PG_FUNCTION_ARGS) 978 | { 979 | JsQuery *jq1 = PG_GETARG_JSQUERY(0); 980 | JsQuery *jq2 = PG_GETARG_JSQUERY(1); 981 | int32 res; 982 | JsQueryItem v1, v2; 983 | 984 | jsqInit(&v1, jq1); 985 | jsqInit(&v2, jq2); 986 | 987 | res = compareJsQuery(&v1, &v2); 988 | 989 | PG_FREE_IF_COPY(jq1, 0); 990 | PG_FREE_IF_COPY(jq2, 1); 991 | 992 | PG_RETURN_INT32(res); 993 | } 994 | 995 | PG_FUNCTION_INFO_V1(jsquery_lt); 996 | Datum 997 | jsquery_lt(PG_FUNCTION_ARGS) 998 | { 999 | JsQuery *jq1 = PG_GETARG_JSQUERY(0); 1000 | JsQuery *jq2 = PG_GETARG_JSQUERY(1); 1001 | int32 res; 1002 | JsQueryItem v1, v2; 1003 | 1004 | jsqInit(&v1, jq1); 1005 | jsqInit(&v2, jq2); 1006 | 1007 | res = compareJsQuery(&v1, &v2); 1008 | 1009 | PG_FREE_IF_COPY(jq1, 0); 1010 | PG_FREE_IF_COPY(jq2, 1); 1011 | 1012 | PG_RETURN_BOOL(res < 0); 1013 | } 1014 | 1015 | PG_FUNCTION_INFO_V1(jsquery_le); 1016 | Datum 1017 | jsquery_le(PG_FUNCTION_ARGS) 1018 | { 1019 | JsQuery *jq1 = PG_GETARG_JSQUERY(0); 1020 | JsQuery *jq2 = PG_GETARG_JSQUERY(1); 1021 | int32 res; 1022 | JsQueryItem v1, v2; 1023 | 1024 | jsqInit(&v1, jq1); 1025 | jsqInit(&v2, jq2); 1026 | 1027 | res = compareJsQuery(&v1, &v2); 1028 | 1029 | PG_FREE_IF_COPY(jq1, 0); 1030 | PG_FREE_IF_COPY(jq2, 1); 1031 | 1032 | PG_RETURN_BOOL(res <= 0); 1033 | } 1034 | 1035 | PG_FUNCTION_INFO_V1(jsquery_eq); 1036 | Datum 1037 | jsquery_eq(PG_FUNCTION_ARGS) 1038 | { 1039 | JsQuery *jq1 = PG_GETARG_JSQUERY(0); 1040 | JsQuery *jq2 = PG_GETARG_JSQUERY(1); 1041 | int32 res; 1042 | JsQueryItem v1, v2; 1043 | 1044 | jsqInit(&v1, jq1); 1045 | jsqInit(&v2, jq2); 1046 | 1047 | res = compareJsQuery(&v1, &v2); 1048 | 1049 | PG_FREE_IF_COPY(jq1, 0); 1050 | PG_FREE_IF_COPY(jq2, 1); 1051 | 1052 | PG_RETURN_BOOL(res == 0); 1053 | } 1054 | 1055 | PG_FUNCTION_INFO_V1(jsquery_ne); 1056 | Datum 1057 | jsquery_ne(PG_FUNCTION_ARGS) 1058 | { 1059 | JsQuery *jq1 = PG_GETARG_JSQUERY(0); 1060 | JsQuery *jq2 = PG_GETARG_JSQUERY(1); 1061 | int32 res; 1062 | JsQueryItem v1, v2; 1063 | 1064 | jsqInit(&v1, jq1); 1065 | jsqInit(&v2, jq2); 1066 | 1067 | res = compareJsQuery(&v1, &v2); 1068 | 1069 | PG_FREE_IF_COPY(jq1, 0); 1070 | PG_FREE_IF_COPY(jq2, 1); 1071 | 1072 | PG_RETURN_BOOL(res != 0); 1073 | } 1074 | 1075 | PG_FUNCTION_INFO_V1(jsquery_ge); 1076 | Datum 1077 | jsquery_ge(PG_FUNCTION_ARGS) 1078 | { 1079 | JsQuery *jq1 = PG_GETARG_JSQUERY(0); 1080 | JsQuery *jq2 = PG_GETARG_JSQUERY(1); 1081 | int32 res; 1082 | JsQueryItem v1, v2; 1083 | 1084 | jsqInit(&v1, jq1); 1085 | jsqInit(&v2, jq2); 1086 | 1087 | res = compareJsQuery(&v1, &v2); 1088 | 1089 | PG_FREE_IF_COPY(jq1, 0); 1090 | PG_FREE_IF_COPY(jq2, 1); 1091 | 1092 | PG_RETURN_BOOL(res >= 0); 1093 | } 1094 | 1095 | PG_FUNCTION_INFO_V1(jsquery_gt); 1096 | Datum 1097 | jsquery_gt(PG_FUNCTION_ARGS) 1098 | { 1099 | JsQuery *jq1 = PG_GETARG_JSQUERY(0); 1100 | JsQuery *jq2 = PG_GETARG_JSQUERY(1); 1101 | int32 res; 1102 | JsQueryItem v1, v2; 1103 | 1104 | jsqInit(&v1, jq1); 1105 | jsqInit(&v2, jq2); 1106 | 1107 | res = compareJsQuery(&v1, &v2); 1108 | 1109 | PG_FREE_IF_COPY(jq1, 0); 1110 | PG_FREE_IF_COPY(jq2, 1); 1111 | 1112 | PG_RETURN_BOOL(res > 0); 1113 | } 1114 | 1115 | static void 1116 | hashJsQuery(JsQueryItem *v, pg_crc32 *crc) 1117 | { 1118 | JsQueryItem elem; 1119 | 1120 | check_stack_depth(); 1121 | 1122 | COMP_CRC32(*crc, &v->type, sizeof(v->type)); 1123 | 1124 | switch(v->type) 1125 | { 1126 | case jqiNull: 1127 | COMP_CRC32(*crc, "null", 5); 1128 | break; 1129 | case jqiKey: 1130 | case jqiString: 1131 | { 1132 | int32 len; 1133 | char *s; 1134 | 1135 | s = jsqGetString(v, &len); 1136 | 1137 | if (v->type == jqiKey) 1138 | len++; /* include trailing '\0' */ 1139 | COMP_CRC32(*crc, s, len); 1140 | } 1141 | break; 1142 | case jqiNumeric: 1143 | *crc ^= (pg_crc32)DatumGetInt32(DirectFunctionCall1( 1144 | hash_numeric, 1145 | PointerGetDatum(jsqGetNumeric(v)))); 1146 | break; 1147 | case jqiBool: 1148 | { 1149 | bool b = jsqGetBool(v); 1150 | 1151 | COMP_CRC32(*crc, &b, 1); 1152 | } 1153 | break; 1154 | case jqiArray: 1155 | COMP_CRC32(*crc, &v->array.nelems, sizeof(v->array.nelems)); 1156 | while(jsqIterateArray(v, &elem)) 1157 | hashJsQuery(&elem, crc); 1158 | break; 1159 | case jqiAnd: 1160 | case jqiOr: 1161 | jsqGetLeftArg(v, &elem); 1162 | hashJsQuery(&elem, crc); 1163 | jsqGetRightArg(v, &elem); 1164 | hashJsQuery(&elem, crc); 1165 | break; 1166 | case jqiNot: 1167 | case jqiEqual: 1168 | case jqiIn: 1169 | case jqiLess: 1170 | case jqiGreater: 1171 | case jqiLessOrEqual: 1172 | case jqiGreaterOrEqual: 1173 | case jqiContains: 1174 | case jqiContained: 1175 | case jqiOverlap: 1176 | jsqGetArg(v, &elem); 1177 | hashJsQuery(&elem, crc); 1178 | break; 1179 | case jqiCurrent: 1180 | case jqiLength: 1181 | case jqiAny: 1182 | case jqiAnyArray: 1183 | case jqiAnyKey: 1184 | case jqiAll: 1185 | case jqiAllArray: 1186 | case jqiAllKey: 1187 | case jqiFilter: 1188 | break; 1189 | case jqiIndexArray: 1190 | COMP_CRC32(*crc, &v->arrayIndex, sizeof(v->arrayIndex)); 1191 | break; 1192 | default: 1193 | elog(ERROR, "Unknown JsQueryItem type: %d", v->type); 1194 | } 1195 | } 1196 | 1197 | PG_FUNCTION_INFO_V1(jsquery_hash); 1198 | Datum 1199 | jsquery_hash(PG_FUNCTION_ARGS) 1200 | { 1201 | JsQuery *jq = PG_GETARG_JSQUERY(0); 1202 | JsQueryItem v; 1203 | pg_crc32 res; 1204 | 1205 | INIT_CRC32(res); 1206 | jsqInit(&v, jq); 1207 | hashJsQuery(&v, &res); 1208 | FIN_CRC32(res); 1209 | 1210 | PG_FREE_IF_COPY(jq, 0); 1211 | 1212 | PG_RETURN_INT32(res); 1213 | } 1214 | 1215 | -------------------------------------------------------------------------------- /jsquery_scan.l: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * jsquery_scan.l 4 | * Lexical parser for jsquery datatype 5 | * 6 | * Copyright (c) 2014, PostgreSQL Global Development Group 7 | * Portions Copyright (c) 2017-2021, Postgres Professional 8 | * Author: Teodor Sigaev 9 | * 10 | * IDENTIFICATION 11 | * contrib/jsquery/jsquery_scan.l 12 | * 13 | *------------------------------------------------------------------------- 14 | */ 15 | 16 | %{ 17 | #include "mb/pg_wchar.h" 18 | 19 | static string scanstring; 20 | 21 | /* No reason to constrain amount of data slurped */ 22 | /* #define YY_READ_BUF_SIZE 16777216 */ 23 | 24 | /* Handles to the buffer that the lexer uses internally */ 25 | static YY_BUFFER_STATE scanbufhandle; 26 | static char *scanbuf; 27 | static int scanbuflen; 28 | 29 | static void addstring(bool init, char *s, int l); 30 | static void addchar(bool init, char s); 31 | static int checkSpecialVal(void); /* examine scanstring for the special value */ 32 | static JsQueryHint checkHint(void); 33 | 34 | static void parseUnicode(char *s, int l); 35 | 36 | %} 37 | 38 | %option 8bit 39 | %option never-interactive 40 | %option nodefault 41 | %option noinput 42 | %option nounput 43 | %option noyywrap 44 | %option warn 45 | %option prefix="jsquery_yy" 46 | %option bison-bridge 47 | 48 | %x xQUOTED 49 | %x xNONQUOTED 50 | %x xCOMMENT 51 | 52 | special [\?\%\$\.\[\]\(\)\|\&\!\=\<\>\@\#\,\*:] 53 | any [^\?\%\$\.\[\]\(\)\|\&\!\=\<\>\@\#\,\* \t\n\r\f\\\"\/:] 54 | blank [ \t\n\r\f] 55 | unicode \\u[0-9A-Fa-f]{4} 56 | 57 | %% 58 | 59 | {special} { return *yytext; } 60 | 61 | {blank}+ { /* ignore */ } 62 | 63 | \/\* { 64 | addchar(true, '\0'); 65 | BEGIN xCOMMENT; 66 | } 67 | 68 | [+-]?[0-9]+(\.[0-9]+)?[eE][+-]?[0-9]+ /* float */ { 69 | addstring(true, yytext, yyleng); 70 | addchar(false, '\0'); 71 | yylval->str = scanstring; 72 | return NUMERIC_P; 73 | } 74 | 75 | [+-]?\.[0-9]+[eE][+-]?[0-9]+ /* float */ { 76 | addstring(true, yytext, yyleng); 77 | addchar(false, '\0'); 78 | yylval->str = scanstring; 79 | return NUMERIC_P; 80 | } 81 | 82 | [+-]?([0-9]+)?\.[0-9]+ { 83 | addstring(true, yytext, yyleng); 84 | addchar(false, '\0'); 85 | yylval->str = scanstring; 86 | return NUMERIC_P; 87 | } 88 | 89 | [+-][0-9]+ { 90 | addstring(true, yytext, yyleng); 91 | addchar(false, '\0'); 92 | yylval->str = scanstring; 93 | return NUMERIC_P; 94 | } 95 | 96 | [0-9]+ { 97 | addstring(true, yytext, yyleng); 98 | addchar(false, '\0'); 99 | yylval->str = scanstring; 100 | return INT_P; 101 | } 102 | 103 | {any}+ { 104 | addstring(true, yytext, yyleng); 105 | BEGIN xNONQUOTED; 106 | } 107 | 108 | \" { 109 | addchar(true, '\0'); 110 | BEGIN xQUOTED; 111 | } 112 | 113 | \\ { 114 | yyless(0); 115 | addchar(true, '\0'); 116 | BEGIN xNONQUOTED; 117 | } 118 | 119 | {any}+ { 120 | addstring(false, yytext, yyleng); 121 | } 122 | 123 | {blank}+ { 124 | yylval->str = scanstring; 125 | BEGIN INITIAL; 126 | return checkSpecialVal(); 127 | } 128 | 129 | 130 | \/\* { 131 | yylval->str = scanstring; 132 | addchar(true, '\0'); 133 | BEGIN xCOMMENT; 134 | return checkSpecialVal(); 135 | } 136 | 137 | 138 | \/ { addchar(false, '/'); } 139 | 140 | ({special}|\") { 141 | yylval->str = scanstring; 142 | yyless(0); 143 | BEGIN INITIAL; 144 | return checkSpecialVal(); 145 | } 146 | 147 | <> { 148 | yylval->str = scanstring; 149 | BEGIN INITIAL; 150 | return checkSpecialVal(); 151 | } 152 | 153 | \\[\"\\] { addchar(false, yytext[1]); } 154 | 155 | \\b { addchar(false, '\b'); } 156 | 157 | \\f { addchar(false, '\f'); } 158 | 159 | \\n { addchar(false, '\n'); } 160 | 161 | \\r { addchar(false, '\r'); } 162 | 163 | \\t { addchar(false, '\t'); } 164 | 165 | {unicode}+ { parseUnicode(yytext, yyleng); } 166 | 167 | \\u { yyerror(NULL, "Unicode sequence is invalid"); } 168 | 169 | \\. { yyerror(NULL, "Escape sequence is invalid"); } 170 | 171 | \\ { yyerror(NULL, "Unexpected end after backslesh"); } 172 | 173 | <> { yyerror(NULL, "Unexpected end of quoted string"); } 174 | 175 | \" { 176 | yylval->str = scanstring; 177 | BEGIN INITIAL; 178 | return STRING_P; 179 | } 180 | 181 | [^\\\"]+ { addstring(false, yytext, yyleng); } 182 | 183 | <> { yyterminate(); } 184 | 185 | \*\/ { 186 | BEGIN INITIAL; 187 | 188 | if ((yylval->hint = checkHint()) != jsqIndexDefault) 189 | return HINT_P; 190 | } 191 | 192 | [^\*]+ { addstring(false, yytext, yyleng); } 193 | 194 | \* { addchar(false, '*'); } 195 | 196 | <> { yyerror(NULL, "Unexpected end of comment"); } 197 | 198 | %% 199 | 200 | void 201 | yyerror(JsQueryParseItem **result, const char *message) 202 | { 203 | if (*yytext == YY_END_OF_BUFFER_CHAR) 204 | { 205 | ereport(ERROR, 206 | (errcode(ERRCODE_SYNTAX_ERROR), 207 | errmsg("bad jsquery representation"), 208 | /* translator: %s is typically "syntax error" */ 209 | errdetail("%s at the end of input", message))); 210 | } 211 | else 212 | { 213 | ereport(ERROR, 214 | (errcode(ERRCODE_SYNTAX_ERROR), 215 | errmsg("bad jsquery representation"), 216 | /* translator: first %s is typically "syntax error" */ 217 | errdetail("%s at or near \"%s\"", message, yytext))); 218 | } 219 | } 220 | 221 | typedef struct keyword 222 | { 223 | int16 len; 224 | bool lowercase; 225 | int val; 226 | char *keyword; 227 | } keyword; 228 | 229 | /* 230 | * Array of key words should be sorted by length and then 231 | * alphabetical order 232 | */ 233 | 234 | static keyword keywords[] = { 235 | { 2, false, IN_P, "in"}, 236 | { 2, false, IS_P, "is"}, 237 | { 2, false, OR_P, "or"}, 238 | { 3, false, AND_P, "and"}, 239 | { 3, false, NOT_P, "not"}, 240 | { 4, true, NULL_P, "null"}, 241 | { 4, true, TRUE_P, "true"}, 242 | { 5, false, ARRAY_T, "array"}, 243 | { 5, true, FALSE_P, "false"}, 244 | { 6, false, OBJECT_T, "object"}, 245 | { 6, false, STRING_T, "string"}, 246 | { 7, false, BOOLEAN_T, "boolean"}, 247 | { 7, false, NUMERIC_T, "numeric"} 248 | }; 249 | 250 | static int 251 | checkSpecialVal() 252 | { 253 | int res = STRING_P; 254 | int diff; 255 | keyword *StopLow = keywords, 256 | *StopHigh = keywords + lengthof(keywords), 257 | *StopMiddle; 258 | 259 | if (scanstring.len > keywords[lengthof(keywords) - 1].len) 260 | return res; 261 | 262 | while(StopLow < StopHigh) 263 | { 264 | StopMiddle = StopLow + ((StopHigh - StopLow) >> 1); 265 | 266 | if (StopMiddle->len == scanstring.len) 267 | diff = pg_strncasecmp(StopMiddle->keyword, scanstring.val, scanstring.len); 268 | else 269 | diff = StopMiddle->len - scanstring.len; 270 | 271 | if (diff < 0) 272 | StopLow = StopMiddle + 1; 273 | else if (diff > 0) 274 | StopHigh = StopMiddle; 275 | else 276 | { 277 | if (StopMiddle->lowercase) 278 | diff = strncmp(StopMiddle->keyword, scanstring.val, scanstring.len); 279 | 280 | if (diff == 0) 281 | res = StopMiddle->val; 282 | 283 | break; 284 | } 285 | } 286 | 287 | return res; 288 | } 289 | 290 | static JsQueryHint 291 | checkHint() 292 | { 293 | if (scanstring.len <= 2 || strncmp(scanstring.val, "--", 2) != 0) 294 | return jsqIndexDefault; 295 | 296 | scanstring.val += 2; 297 | scanstring.len -= 2; 298 | 299 | while(scanstring.len > 0 && isspace(*scanstring.val)) 300 | { 301 | scanstring.val++; 302 | scanstring.len--; 303 | } 304 | 305 | if (scanstring.len >= 5 && pg_strncasecmp(scanstring.val, "index", 5) == 0) 306 | return jsqForceIndex; 307 | 308 | if (scanstring.len >= 7 && pg_strncasecmp(scanstring.val, "noindex", 7) == 0) 309 | return jsqNoIndex; 310 | 311 | return jsqIndexDefault; 312 | } 313 | /* 314 | * Called before any actual parsing is done 315 | */ 316 | static void 317 | jsquery_scanner_init(const char *str, int slen) 318 | { 319 | if (slen <= 0) 320 | slen = strlen(str); 321 | 322 | /* 323 | * Might be left over after ereport() 324 | */ 325 | if (YY_CURRENT_BUFFER) 326 | yy_delete_buffer(YY_CURRENT_BUFFER); 327 | 328 | /* 329 | * Make a scan buffer with special termination needed by flex. 330 | */ 331 | 332 | scanbuflen = slen; 333 | scanbuf = palloc(slen + 2); 334 | memcpy(scanbuf, str, slen); 335 | scanbuf[slen] = scanbuf[slen + 1] = YY_END_OF_BUFFER_CHAR; 336 | scanbufhandle = yy_scan_buffer(scanbuf, slen + 2); 337 | 338 | BEGIN(INITIAL); 339 | } 340 | 341 | 342 | /* 343 | * Called after parsing is done to clean up after jsquery_scanner_init() 344 | */ 345 | static void 346 | jsquery_scanner_finish(void) 347 | { 348 | yy_delete_buffer(scanbufhandle); 349 | pfree(scanbuf); 350 | } 351 | 352 | static void 353 | addstring(bool init, char *s, int l) { 354 | if (init) { 355 | scanstring.total = 32; 356 | scanstring.val = palloc(scanstring.total); 357 | scanstring.len = 0; 358 | } 359 | 360 | if (s && l) { 361 | while(scanstring.len + l + 1 >= scanstring.total) { 362 | scanstring.total *= 2; 363 | scanstring.val = repalloc(scanstring.val, scanstring.total); 364 | } 365 | 366 | memcpy(scanstring.val + scanstring.len, s, l); 367 | scanstring.len += l; 368 | } 369 | } 370 | 371 | static void 372 | addchar(bool init, char s) { 373 | if (init) 374 | { 375 | scanstring.total = 32; 376 | scanstring.val = palloc(scanstring.total); 377 | scanstring.len = 0; 378 | } 379 | else if(scanstring.len + 1 >= scanstring.total) 380 | { 381 | scanstring.total *= 2; 382 | scanstring.val = repalloc(scanstring.val, scanstring.total); 383 | } 384 | 385 | scanstring.val[ scanstring.len ] = s; 386 | if (s != '\0') 387 | scanstring.len++; 388 | } 389 | 390 | JsQueryParseItem* 391 | parsejsquery(const char *str, int len) { 392 | JsQueryParseItem *parseresult; 393 | 394 | jsquery_scanner_init(str, len); 395 | 396 | if (jsquery_yyparse((void*)&parseresult) != 0) 397 | jsquery_yyerror(NULL, "bugus input"); 398 | 399 | jsquery_scanner_finish(); 400 | 401 | return parseresult; 402 | } 403 | 404 | static int 405 | hexval(char c) 406 | { 407 | if (c >= '0' && c <= '9') 408 | return c - '0'; 409 | if (c >= 'a' && c <= 'f') 410 | return c - 'a' + 0xA; 411 | if (c >= 'A' && c <= 'F') 412 | return c - 'A' + 0xA; 413 | elog(ERROR, "invalid hexadecimal digit"); 414 | return 0; /* not reached */ 415 | } 416 | 417 | /* 418 | * parseUnicode was adopted from json_lex_string() in 419 | * src/backend/utils/adt/json.c 420 | */ 421 | static void 422 | parseUnicode(char *s, int l) 423 | { 424 | int i, j; 425 | int ch = 0; 426 | int hi_surrogate = -1; 427 | 428 | Assert(l % 6 /* \uXXXX */ == 0); 429 | 430 | for(i = 0; i < l / 6; i++) 431 | { 432 | ch = 0; 433 | 434 | for(j=0; j<4; j++) 435 | ch = (ch << 4) | hexval(s[ i*6 + 2 + j]); 436 | 437 | if (ch >= 0xd800 && ch <= 0xdbff) 438 | { 439 | if (hi_surrogate != -1) 440 | ereport(ERROR, 441 | (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), 442 | errmsg("invalid input syntax for type jsquery"), 443 | errdetail("Unicode high surrogate must not follow a high surrogate."))); 444 | hi_surrogate = (ch & 0x3ff) << 10; 445 | continue; 446 | } 447 | else if (ch >= 0xdc00 && ch <= 0xdfff) 448 | { 449 | if (hi_surrogate == -1) 450 | ereport(ERROR, 451 | (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), 452 | errmsg("invalid input syntax for type jsquery"), 453 | errdetail("Unicode low surrogate must follow a high surrogate."))); 454 | ch = 0x10000 + hi_surrogate + (ch & 0x3ff); 455 | hi_surrogate = -1; 456 | } 457 | 458 | if (hi_surrogate != -1) 459 | ereport(ERROR, 460 | (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), 461 | errmsg("invalid input syntax for type jsquery"), 462 | errdetail("Unicode low surrogate must follow a high surrogate."))); 463 | 464 | /* 465 | * For UTF8, replace the escape sequence by the actual 466 | * utf8 character in lex->strval. Do this also for other 467 | * encodings if the escape designates an ASCII character, 468 | * otherwise raise an error. 469 | */ 470 | 471 | if (ch == 0) 472 | { 473 | /* We can't allow this, since our TEXT type doesn't */ 474 | ereport(ERROR, 475 | (errcode(ERRCODE_UNTRANSLATABLE_CHARACTER), 476 | errmsg("unsupported Unicode escape sequence"), 477 | errdetail("\\u0000 cannot be converted to text."))); 478 | } 479 | else if (GetDatabaseEncoding() == PG_UTF8) 480 | { 481 | char utf8str[5]; 482 | int utf8len; 483 | 484 | unicode_to_utf8(ch, (unsigned char *) utf8str); 485 | utf8len = pg_utf_mblen((unsigned char *) utf8str); 486 | addstring(false, utf8str, utf8len); 487 | } 488 | else if (ch <= 0x007f) 489 | { 490 | /* 491 | * This is the only way to designate things like a 492 | * form feed character in JSON, so it's useful in all 493 | * encodings. 494 | */ 495 | addchar(false, (char) ch); 496 | } 497 | else 498 | { 499 | ereport(ERROR, 500 | (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), 501 | errmsg("invalid input syntax for type jsquery"), 502 | errdetail("Unicode escape values cannot be used for code point values above 007F when the server encoding is not UTF8."))); 503 | } 504 | 505 | hi_surrogate = -1; 506 | } 507 | } 508 | 509 | 510 | -------------------------------------------------------------------------------- /jsquery_support.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------- 2 | * 3 | * jsquery_support.c 4 | * Functions and operations to support jsquery 5 | * 6 | * Copyright (c) 2014, PostgreSQL Global Development Group 7 | * Portions Copyright (c) 2017-2021, Postgres Professional 8 | * Author: Teodor Sigaev 9 | * 10 | * IDENTIFICATION 11 | * contrib/jsquery/jsquery_support.c 12 | * 13 | *------------------------------------------------------------------------- 14 | */ 15 | 16 | #include "postgres.h" 17 | 18 | #include "jsquery.h" 19 | 20 | #define read_byte(v, b, p) do { \ 21 | (v) = *(uint8*)((b) + (p)); \ 22 | (p) += 1; \ 23 | } while(0) \ 24 | 25 | #define read_int32(v, b, p) do { \ 26 | (v) = *(uint32*)((b) + (p)); \ 27 | (p) += sizeof(int32); \ 28 | } while(0) \ 29 | 30 | void 31 | alignStringInfoInt(StringInfo buf) 32 | { 33 | switch(INTALIGN(buf->len) - buf->len) 34 | { 35 | case 3: 36 | appendStringInfoCharMacro(buf, 0); 37 | /* fall through */ 38 | case 2: 39 | appendStringInfoCharMacro(buf, 0); 40 | /* fall through */ 41 | case 1: 42 | appendStringInfoCharMacro(buf, 0); 43 | /* fall through */ 44 | default: 45 | break; 46 | } 47 | } 48 | 49 | void 50 | jsqInit(JsQueryItem *v, JsQuery *js) 51 | { 52 | jsqInitByBuffer(v, VARDATA(js), 0); 53 | } 54 | 55 | void 56 | jsqInitByBuffer(JsQueryItem *v, char *base, int32 pos) 57 | { 58 | v->base = base; 59 | 60 | read_byte(v->type, base, pos); 61 | 62 | v->hint = v->type & JSQ_HINT_MASK; 63 | v->type &= ~JSQ_HINT_MASK; 64 | 65 | switch(INTALIGN(pos) - pos) 66 | { 67 | case 3: pos++; /* fall through */ 68 | case 2: pos++; /* fall through */ 69 | case 1: pos++; /* fall through */ 70 | default: break; 71 | } 72 | 73 | read_int32(v->nextPos, base, pos); 74 | 75 | switch(v->type) 76 | { 77 | case jqiNull: 78 | case jqiCurrent: 79 | case jqiLength: 80 | case jqiAny: 81 | case jqiAnyArray: 82 | case jqiAnyKey: 83 | case jqiAll: 84 | case jqiAllArray: 85 | case jqiAllKey: 86 | break; 87 | case jqiIndexArray: 88 | read_int32(v->arrayIndex, base, pos); 89 | break; 90 | case jqiKey: 91 | case jqiString: 92 | read_int32(v->value.datalen, base, pos); 93 | /* fall through */ 94 | /* follow next */ 95 | case jqiNumeric: 96 | case jqiBool: 97 | case jqiIs: 98 | v->value.data = base + pos; 99 | break; 100 | case jqiArray: 101 | read_int32(v->array.nelems, base, pos); 102 | v->array.current = 0; 103 | v->array.arrayPtr = (int32*)(base + pos); 104 | break; 105 | case jqiAnd: 106 | case jqiOr: 107 | read_int32(v->args.left, base, pos); 108 | read_int32(v->args.right, base, pos); 109 | break; 110 | case jqiEqual: 111 | case jqiLess: 112 | case jqiGreater: 113 | case jqiLessOrEqual: 114 | case jqiGreaterOrEqual: 115 | case jqiContains: 116 | case jqiContained: 117 | case jqiOverlap: 118 | case jqiIn: 119 | case jqiNot: 120 | case jqiFilter: 121 | read_int32(v->arg, base, pos); 122 | break; 123 | default: 124 | abort(); 125 | elog(ERROR, "Unknown type: %d", v->type); 126 | } 127 | } 128 | 129 | void 130 | jsqGetArg(JsQueryItem *v, JsQueryItem *a) 131 | { 132 | Assert( 133 | v->type == jqiEqual || 134 | v->type == jqiLess || 135 | v->type == jqiGreater || 136 | v->type == jqiLessOrEqual || 137 | v->type == jqiGreaterOrEqual || 138 | v->type == jqiContains || 139 | v->type == jqiContained || 140 | v->type == jqiOverlap || 141 | v->type == jqiFilter || 142 | v->type == jqiIn || 143 | v->type == jqiNot 144 | ); 145 | 146 | jsqInitByBuffer(a, v->base, v->arg); 147 | } 148 | 149 | bool 150 | jsqGetNext(JsQueryItem *v, JsQueryItem *a) 151 | { 152 | if (v->nextPos > 0) 153 | { 154 | Assert( 155 | v->type == jqiKey || 156 | v->type == jqiAny || 157 | v->type == jqiIndexArray || 158 | v->type == jqiAnyArray || 159 | v->type == jqiAnyKey || 160 | v->type == jqiAll || 161 | v->type == jqiAllArray || 162 | v->type == jqiAllKey || 163 | v->type == jqiCurrent || 164 | v->type == jqiFilter || 165 | v->type == jqiLength 166 | ); 167 | 168 | if (a) 169 | jsqInitByBuffer(a, v->base, v->nextPos); 170 | return true; 171 | } 172 | 173 | return false; 174 | } 175 | 176 | void 177 | jsqGetLeftArg(JsQueryItem *v, JsQueryItem *a) 178 | { 179 | Assert( 180 | v->type == jqiAnd || 181 | v->type == jqiOr 182 | ); 183 | 184 | jsqInitByBuffer(a, v->base, v->args.left); 185 | } 186 | 187 | void 188 | jsqGetRightArg(JsQueryItem *v, JsQueryItem *a) 189 | { 190 | Assert( 191 | v->type == jqiAnd || 192 | v->type == jqiOr 193 | ); 194 | 195 | jsqInitByBuffer(a, v->base, v->args.right); 196 | } 197 | 198 | bool 199 | jsqGetBool(JsQueryItem *v) 200 | { 201 | Assert(v->type == jqiBool); 202 | 203 | return (bool)*v->value.data; 204 | } 205 | 206 | Numeric 207 | jsqGetNumeric(JsQueryItem *v) 208 | { 209 | Assert(v->type == jqiNumeric); 210 | 211 | return (Numeric)v->value.data; 212 | } 213 | 214 | int32 215 | jsqGetIsType(JsQueryItem *v) 216 | { 217 | Assert(v->type == jqiIs); 218 | 219 | return (int32)*v->value.data; 220 | } 221 | 222 | char* 223 | jsqGetString(JsQueryItem *v, int32 *len) 224 | { 225 | Assert( 226 | v->type == jqiKey || 227 | v->type == jqiString 228 | ); 229 | 230 | if (len) 231 | *len = v->value.datalen; 232 | return v->value.data; 233 | } 234 | 235 | void 236 | jsqIterateInit(JsQueryItem *v) 237 | { 238 | Assert(v->type == jqiArray); 239 | 240 | v->array.current = 0; 241 | } 242 | 243 | bool 244 | jsqIterateArray(JsQueryItem *v, JsQueryItem *e) 245 | { 246 | Assert(v->type == jqiArray); 247 | 248 | if (v->array.current < v->array.nelems) 249 | { 250 | jsqInitByBuffer(e, v->base, v->array.arrayPtr[v->array.current]); 251 | v->array.current++; 252 | return true; 253 | } 254 | else 255 | { 256 | return false; 257 | } 258 | } 259 | 260 | void 261 | jsqIterateDestroy(JsQueryItem *v) 262 | { 263 | Assert(v->type == jqiArray); 264 | Assert(v->array.current <= v->array.nelems); 265 | v->array.current++; 266 | } 267 | 268 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2025, Postgres Professional 2 | 3 | # Does not support the PGXS infrastructure at this time. Please, compile as part 4 | # of the contrib source tree. 5 | 6 | jsquery_sources = files( 7 | 'jsonb_gin_ops.c', 8 | 'jsquery_constr.c', 9 | 'jsquery_extract.c', 10 | 'jsquery_io.c', 11 | 'jsquery_op.c', 12 | 'jsquery_support.c', 13 | ) 14 | 15 | jsquery_gram = custom_target('jsquerygram', 16 | input: 'jsquery_gram.y', 17 | kwargs: bison_kw, 18 | ) 19 | generated_sources += jsquery_gram.to_list() 20 | jsquery_sources += jsquery_gram 21 | 22 | run_command(flex, '--outfile=jsquery_scan.c', 'jsquery_scan.l', check: true) 23 | 24 | if host_system == 'windows' 25 | jsquery_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ 26 | '--NAME', 'jsquery', 27 | '--FILEDESC', 'jsquery - language to query jsonb data type.',]) 28 | endif 29 | 30 | jsquery = shared_module('jsquery', 31 | jsquery_sources, 32 | include_directories: include_directories('.'), 33 | kwargs: contrib_mod_args, 34 | ) 35 | contrib_targets += jsquery 36 | 37 | install_data( 38 | 'jsquery.control', 39 | 'jsquery--1.0--1.1.sql', 40 | 'jsquery--1.1.sql', 41 | kwargs: contrib_data_args, 42 | ) 43 | 44 | tests += { 45 | 'name': 'jsquery', 46 | 'sd': meson.current_source_dir(), 47 | 'bd': meson.current_build_dir(), 48 | 'regress': { 49 | 'sql': [ 50 | 'jsquery', 51 | ], 52 | }, 53 | } 54 | -------------------------------------------------------------------------------- /sql/jsquery.sql: -------------------------------------------------------------------------------- 1 | CREATE EXTENSION jsquery; 2 | 3 | set escape_string_warning=off; 4 | set standard_conforming_strings=on; 5 | 6 | CREATE TABLE test_jsquery (v jsonb); 7 | 8 | \copy test_jsquery from 'data/test_jsquery.data' 9 | 10 | select ''::jsquery; 11 | select 'asd.zzz = 13'::jsquery; 12 | select 'asd.zzz < 13'::jsquery; 13 | select 'asd(zzz < 13)'::jsquery; 14 | select 'asd(zzz < 13 AND x = true)'::jsquery; 15 | select 'asd(zzz < 13 AND x = "true")'::jsquery; 16 | select 'asd(zzz < 13 AND x.zxc = "true")'::jsquery; 17 | select 'asd(zzz < 13 OR #.zxc = "true" /* test */)'::jsquery; 18 | select 'asd(* < 13 AND /* ttt */ #.zxc = "true")'::jsquery; 19 | select '(* < 13 AND #.zxc = "true")'::jsquery; 20 | select '* < 13 AND #.zxc/* t2 */ = "true"'::jsquery; 21 | select '* < 13 AND #.zxc"x" = "true"'::jsquery; 22 | 23 | select 'a < 1'::jsquery; 24 | select 'a < -1'::jsquery; 25 | select 'a < +1'::jsquery; 26 | select 'a < .1'::jsquery; 27 | select 'a < -.1'::jsquery; 28 | select 'a < +.1'::jsquery; 29 | select 'a < 0.1'::jsquery; 30 | select 'a < -0.1'::jsquery; 31 | select 'a < +0.1'::jsquery; 32 | select 'a < 10.1'::jsquery; 33 | select 'a < -10.1'::jsquery; 34 | select 'a < +10.1'::jsquery; 35 | select 'a < 1e1'::jsquery; 36 | select 'a < -1e1'::jsquery; 37 | select 'a < +1e1'::jsquery; 38 | select 'a < .1e1'::jsquery; 39 | select 'a < -.1e1'::jsquery; 40 | select 'a < +.1e1'::jsquery; 41 | select 'a < 0.1e1'::jsquery; 42 | select 'a < -0.1e1'::jsquery; 43 | select 'a < +0.1e1'::jsquery; 44 | select 'a < 10.1e1'::jsquery; 45 | select 'a < -10.1e1'::jsquery; 46 | select 'a < +10.1e1'::jsquery; 47 | select 'a < 1e-1'::jsquery; 48 | select 'a < -1e-1'::jsquery; 49 | select 'a < +1e-1'::jsquery; 50 | select 'a < .1e-1'::jsquery; 51 | select 'a < -.1e-1'::jsquery; 52 | select 'a < +.1e-1'::jsquery; 53 | select 'a < 0.1e-1'::jsquery; 54 | select 'a < -0.1e-1'::jsquery; 55 | select 'a < +0.1e-1'::jsquery; 56 | select 'a < 10.1e-1'::jsquery; 57 | select 'a < -10.1e-1'::jsquery; 58 | select 'a < +10.1e-1'::jsquery; 59 | select 'a < 1e+1'::jsquery; 60 | select 'a < -1e+1'::jsquery; 61 | select 'a < +1e+1'::jsquery; 62 | select 'a < .1e+1'::jsquery; 63 | select 'a < -.1e+1'::jsquery; 64 | select 'a < +.1e+1'::jsquery; 65 | select 'a < 0.1e+1'::jsquery; 66 | select 'a < -0.1e+1'::jsquery; 67 | select 'a < +0.1e+1'::jsquery; 68 | select 'a < 10.1e+1'::jsquery; 69 | select 'a < -10.1e+1'::jsquery; 70 | select 'a < +10.1e+1'::jsquery; 71 | 72 | select 'a in (0,1,2)'::jsquery; 73 | select 'a IN (0,null, "null", xxx, "zzz", 2)'::jsquery; 74 | 75 | select 'not < 1'::jsquery; 76 | select 'not not < 1'::jsquery; 77 | select 'not( not < 1)'::jsquery; 78 | select 'not.x < 1'::jsquery; 79 | select 'x.not < 1'::jsquery; 80 | select 'a.%(not x > 0 and not (y < 0 or z = 0))'::jsquery; 81 | 82 | select 'is < 1'::jsquery; 83 | select 'in < 1'::jsquery; 84 | 85 | select 'not is < 1'::jsquery; 86 | select 'not in < 1'::jsquery; 87 | 88 | select 'in in (1,2)'::jsquery; 89 | select 'is in (1,2)'::jsquery; 90 | select 'not in (1,2)'::jsquery; 91 | 92 | select 'in is numeric'::jsquery; 93 | select 'is is numeric'::jsquery; 94 | select 'not is numeric'::jsquery; 95 | 96 | select 'not.in < 1'::jsquery; 97 | select 'not.is < 1'::jsquery; 98 | select 'not.not < 1'::jsquery; 99 | select 'in.in < 1'::jsquery; 100 | select 'in.is < 1'::jsquery; 101 | select 'in.not < 1'::jsquery; 102 | select 'is.in < 1'::jsquery; 103 | select 'is.is < 1'::jsquery; 104 | select 'is.not < 1'::jsquery; 105 | 106 | select 'a.b.#4 > 4'::jsquery; 107 | select 'a.b.#10203.* > 4'::jsquery; 108 | 109 | select '{"a": {"b": null}}'::jsonb @@ 'a.b = 1'::jsquery; 110 | select '{"a": {"b": null}}'::jsonb @@ 'a.b = null'::jsquery; 111 | select '{"a": {"b": null}}'::jsonb @@ 'a.b = false'::jsquery; 112 | select '{"a": {"b": false}}'::jsonb @@ 'a.b = false'::jsquery; 113 | select '{"a": {"b": false}}'::jsonb @@ 'a.b = true'::jsquery; 114 | select '{"a": {"b": true}}'::jsonb @@ 'a.b = true'::jsquery; 115 | 116 | 117 | select '{"a": {"b": 1}}'::jsonb @@ 'a.b = 1'::jsquery; 118 | select '{"a": {"b": 1}}'::jsonb @@ 'a.b < 1'::jsquery; 119 | select '{"a": {"b": 1}}'::jsonb @@ 'a.b <= 1'::jsquery; 120 | select '{"a": {"b": 1}}'::jsonb @@ 'a.b >= 1'::jsquery; 121 | select '{"a": {"b": 1}}'::jsonb @@ 'a.b > 1'::jsquery; 122 | 123 | select '{"a": {"b": 1}}'::jsonb @@ 'a.b = 2'::jsquery; 124 | select '{"a": {"b": 1}}'::jsonb @@ 'a.b < 2'::jsquery; 125 | select '{"a": {"b": 1}}'::jsonb @@ 'a.b <= 2'::jsquery; 126 | select '{"a": {"b": 1}}'::jsonb @@ 'a.b >= 2'::jsquery; 127 | select '{"a": {"b": 1}}'::jsonb @@ 'a.b > 2'::jsquery; 128 | 129 | select '{"a": {"b": 1}}'::jsonb @@ 'a.b = 0'::jsquery; 130 | select '{"a": {"b": 1}}'::jsonb @@ 'a.b < 0'::jsquery; 131 | select '{"a": {"b": 1}}'::jsonb @@ 'a.b <= 0'::jsquery; 132 | select '{"a": {"b": 1}}'::jsonb @@ 'a.b >= 0'::jsquery; 133 | select '{"a": {"b": 1}}'::jsonb @@ 'a.b > 0'::jsquery; 134 | 135 | select '{"a": {"b": 1}}'::jsonb @@ '*.b > 0'::jsquery; 136 | select '{"a": {"b": 1}}'::jsonb @@ '*.b > 0'::jsquery; 137 | select '{"a": {"b": 1}}'::jsonb @@ 'a.* > 0'::jsquery; 138 | select '{"a": {"b": 1}}'::jsonb @@ 'a.* > 0'::jsquery; 139 | 140 | select '{"a": {"b": [1,2,3]}}'::jsonb @@ '*.b && [ 1 ]'::jsquery; 141 | select '{"a": {"b": [1,2,3]}}'::jsonb @@ '*.b @> [ 1 ]'::jsquery; 142 | select '{"a": {"b": [1,2,3]}}'::jsonb @@ '*.b <@ [ 1 ]'::jsquery; 143 | select '{"a": {"b": [1,2,3]}}'::jsonb @@ '*.b @> [ 1,2,3,4 ]'::jsquery; 144 | select '{"a": {"b": [1,2,3]}}'::jsonb @@ '*.b <@ [ 1,2,3,4 ]'::jsquery; 145 | 146 | select '[{"a": 2}, {"a": 3}]'::jsonb @@ '*.a = 4'::jsquery; 147 | select '[{"a": 2}, {"a": 3}]'::jsonb @@ '*.a = 3'::jsquery; 148 | 149 | select '[{"a": 2}, {"a": 3}, {"a": {"a":4}}]'::jsonb @@ '#.a = 4'::jsquery; 150 | select '[{"a": 2}, {"a": 3}, {"a": {"a":4}}]'::jsonb @@ '*.a = 4'::jsquery; 151 | 152 | select '[{"a": 2}, {"a": 3}, {"a": {"a":4}}]'::jsonb @@ '#(a = 1 OR a=3)'::jsquery; 153 | select '[{"a": 2}, {"a": 3}, {"a": {"a":4}}]'::jsonb @@ '#(a = 3 OR a=1)'::jsquery; 154 | select '[{"a": 2}, {"a": 3}, {"a": {"a":4}}]'::jsonb @@ '#(a = 3 and a=1)'::jsquery; 155 | select '[{"a": 2}, {"a": 3}, {"a": {"a":4}}]'::jsonb @@ '#(a = 3 and a=2)'::jsquery as "false"; 156 | select '[{"a": 2, "b":3}, {"a": 3, "b": 1}]'::jsonb @@ '#(b = 1 and a=3)'::jsquery; 157 | 158 | select '[{"a": 2}, {"a": 3}, {"a": {"a":4}}]'::jsonb @@ '#.a.a = 4'::jsquery; 159 | select '[{"a": 2}, {"a": 3}, {"a": {"a":4}}]'::jsonb @@ '*.a.a = 4'::jsquery; 160 | select '[{"a": 2}, {"a": 3}, {"a": {"a":4}}]'::jsonb @@ '*.#.a.a = 4'::jsquery; 161 | select '[{"a": 2}, {"a": 3}, {"a": {"a":4}}]'::jsonb @@ '#.*.a.a = 4'::jsquery; 162 | 163 | select '{"a": 1}'::jsonb @@ 'a in (0,1,2)'::jsquery; 164 | select '{"a": 1}'::jsonb @@ 'a in (0,2)'::jsquery; 165 | 166 | select '{"a": {"b": [1,2,3]}}'::jsonb @@ 'a.b.#=2'::jsquery; 167 | select '{"a": {"b": [1,2,3]}}'::jsonb @@ '*.b && [ 5 ]'::jsquery; 168 | 169 | select '{"a": {"b": [1,2,3]}}'::jsonb @@ 'a=*'::jsquery; 170 | select '{"a": {"b": [1,2,3]}}'::jsonb @@ 'a.b=*'::jsquery; 171 | select '{"a": {"b": [1,2,3]}}'::jsonb @@ 'a.c=*'::jsquery; 172 | 173 | select '{"a": {"b": [1,2,3]}}'::jsonb @@ 'a.b = [1,2,3]'::jsquery; 174 | select '{"a": {"b": [1,2,3]}}'::jsonb @@ 'a.b.# = [1,2,3]'::jsquery; 175 | select '{"a": {"b": [1,2,3]}}'::jsonb @@ 'a.b && [1,2,3]'::jsquery; 176 | select '{"a": {"b": [1,2,3]}}'::jsonb @@ 'a.b.# && [1,2,3]'::jsquery; 177 | 178 | select 'asd.# = 3'::jsquery & 'zzz = true' | 'xxx.# = zero'::jsquery; 179 | select !'asd.# = 3'::jsquery & 'zzz = true' | !'xxx.# = zero'::jsquery; 180 | select !'asd.#3.f = 3'::jsquery & 'zzz = true' | !'xxx.# = zero'::jsquery; 181 | 182 | select '{"x":[0,1,1,2]}'::jsonb @@ 'x @> [1,0]'::jsquery; 183 | select '{"x":[0,1,1,2]}'::jsonb @@ 'x @> [1,0,1]'::jsquery; 184 | select '{"x":[0,1,1,2]}'::jsonb @@ 'x @> [1,0,3]'::jsquery; 185 | 186 | select '{"a": {"b": [1,2,3]}}'::jsonb @@ '*.b && [ 2 ]'::jsquery; 187 | select '{"a": {"b": [1,2,3]}}'::jsonb @@ '*.b($ && [ 2 ])'::jsquery; 188 | select '{"a": {"b": [1,2,3]}}'::jsonb @@ 'a.$.b && [ 2 ]'::jsquery; 189 | select '{"a": {"b": [1,2,3]}}'::jsonb @@ 'a.$.b ($ && [ 2 ])'::jsquery; 190 | 191 | select '[1,2,3]'::jsonb @@ '# && [2]'::jsquery; 192 | select '[1,2,3]'::jsonb @@ '#($ && [2])'::jsquery; 193 | select '[1,2,3]'::jsonb @@ '$ && [2]'::jsquery; 194 | select '[1,2,3]'::jsonb @@ '$ ($ && [2])'::jsquery; 195 | select '[1,2,3]'::jsonb @@ '$ = 2'::jsquery; 196 | select '[1,2,3]'::jsonb @@ '# = 2'::jsquery; 197 | select '[1,2,3]'::jsonb @@ '#.$ = 2'::jsquery; 198 | select '[1,2,3]'::jsonb @@ '#($ = 2)'::jsquery; 199 | 200 | select '[3,4]'::jsonb @@ '#($ > 2 and $ < 5)'::jsquery; 201 | select '[3,4]'::jsonb @@ '# > 2 and # < 5'::jsquery; 202 | select '[1,6]'::jsonb @@ '#($ > 2 and $ < 5)'::jsquery; 203 | select '[1,6]'::jsonb @@ '# > 2 and # < 5'::jsquery; 204 | 205 | select '{"a": {"b": 3, "c": "hey"}, "x": [5,6]}'::jsonb @@ '%.b=3'::jsquery; 206 | select '{"a": {"b": 3, "c": "hey"}, "x": [5,6]}'::jsonb @@ 'a.%=3'::jsquery; 207 | select '{"a": {"b": 3, "c": "hey"}, "x": [5,6]}'::jsonb @@ '%.%="hey"'::jsquery; 208 | select '{"a": {"b": 3, "c": "hey"}, "x": [5,6]}'::jsonb @@ '%="hey"'::jsquery; 209 | select '{"a": {"b": 3, "c": "hey"}, "x": [5,6]}'::jsonb @@ '%=[5,6]'::jsquery; 210 | 211 | select '{"a": {"b": [1,2,3]}}'::jsonb @@ 'a.b.#1 = 2'::jsquery; 212 | select '{"a": {"b": [1,2,3]}}'::jsonb @@ 'a.b.#2 = 2'::jsquery; 213 | select '{"a": {"b": [1,2,3]}}'::jsonb @@ 'a.b.#3 = 2'::jsquery; 214 | select '{"a": {"b": [{"x":1},{"x":2},{"x":3}]}}'::jsonb @@ 'a.b.#1.x = 2'::jsquery; 215 | select '{"a": {"b": [{"x":1},{"x":2},{"x":3}]}}'::jsonb @@ 'a.b.#2.x = 2'::jsquery; 216 | select '{"a": {"b": [{"x":1},{"x":2},{"x":3}]}}'::jsonb @@ 'a.b.#3.x = 2'::jsquery; 217 | 218 | select '"XXX"'::jsonb @@ '$="XXX"'::jsquery; 219 | select '"XXX"'::jsonb @@ '#.$="XXX"'::jsquery; 220 | 221 | --Unicode 222 | 223 | select 'a\t = "dollar \u0024 character"'::jsquery; 224 | select '{ "a": "dollar \u0024 character" }'::jsonb @@ '* = "dollar \u0024 character"'::jsquery; 225 | select '{ "a": "dollar \u0024 character" }'::jsonb @@ '* = "dollar $ character"'::jsquery; 226 | select '{ "a": "dollar $ character" }'::jsonb @@ '* = "dollar \u0024 character"'::jsquery; 227 | select 'a\r = "\n\""'::jsquery; 228 | select 'a\r = "\u0000"'::jsquery; 229 | select 'a\r = \u0000'::jsquery; 230 | select 'a\r = "\abcd"'::jsquery AS err; 231 | select 'a\r = "\\abcd"'::jsquery; 232 | select 'a\r = "x\u0000"'::jsquery; 233 | select 'a\r = x\u0000'::jsquery; 234 | select 'a\r = "x\abcd"'::jsquery AS err; 235 | select 'a\r = "x\\abcd"'::jsquery; 236 | select 'a\r = "x\u0000x"'::jsquery; 237 | select 'a\r = x\u0000x'::jsquery; 238 | select 'a\r = "x\abcdx"'::jsquery AS err; 239 | select 'a\r = "x\\abcdx"'::jsquery; 240 | select 'a\r = "\u0000x"'::jsquery; 241 | select 'a\r = \u0000x'::jsquery; 242 | select 'a\r = "\abcdx"'::jsquery AS err; 243 | select 'a\r = "\\abcdx"'::jsquery; 244 | select 'a\r = x"\\abcd"'::jsquery AS err; 245 | 246 | --IS 247 | 248 | select 'as IS boolean OR as is ARRAY OR as is ObJect OR as is Numeric OR as is string'::jsquery; 249 | select '{"as": "xxx"}' @@ 'as IS string'::jsquery; 250 | select '{"as": "xxx"}' @@ 'as IS boolean OR as is ARRAY OR as is ObJect OR as is Numeric'::jsquery; 251 | select '{"as": 5}' @@ 'as is Numeric'::jsquery; 252 | select '{"as": true}' @@ 'as is boolean'::jsquery; 253 | select '{"as": false}' @@ 'as is boolean'::jsquery; 254 | select '{"as": "false"}' @@ 'as is boolean'::jsquery; 255 | select '["xxx"]' @@ '$ IS array'::jsquery; 256 | select '{"as": false}' @@ '$ IS object'::jsquery; 257 | select '"xxx"' @@ '$ IS string'::jsquery; 258 | select '"xxx"' @@ '$ IS numeric'::jsquery; 259 | 260 | --hint 261 | 262 | select 'a /*-- noindex */ = 5'::jsquery; 263 | select 'a /*-- index */ = 5'::jsquery; 264 | select 'asd.# = 3'::jsquery & 'zzz /*-- noindex */ = true' | 'xxx.# /*-- index */ = zero'; 265 | select 'a /*-- xxx */ = 5'::jsquery; 266 | select 'a /* index */ = 5'::jsquery; 267 | select 'a /* noindex */ = 5'::jsquery; 268 | select 'a = /*-- noindex */ 5'::jsquery; 269 | select 'a = /* noindex */ 5'::jsquery; 270 | 271 | --LENGTH 272 | select 'a.@# = 4'::jsquery; 273 | select 'a.@#.$ = 4'::jsquery as noerror; 274 | select 'a.@#.a = 4'::jsquery as error; 275 | select 'a.@#.* = 4'::jsquery as error; 276 | select 'a.@#.% = 4'::jsquery as error; 277 | select 'a.@#.# = 4'::jsquery as error; 278 | select 'a.@#.*: = 4'::jsquery as error; 279 | select 'a.@#.%: = 4'::jsquery as error; 280 | select 'a.@#.#: = 4'::jsquery as error; 281 | select 'a.@# (a = 5 or b = 6)'::jsquery as error; 282 | select '[]' @@ '@# = 0'::jsquery; 283 | select '[]' @@ '@# < 2'::jsquery; 284 | select '[]' @@ '@# > 1'::jsquery; 285 | select '[1]' @@ '@# = 0'::jsquery; 286 | select '[1]' @@ '@# < 2'::jsquery; 287 | select '[1]' @@ '@# > 1'::jsquery; 288 | select '[1,2]' @@ '@# = 0'::jsquery; 289 | select '[1,2]' @@ '@# < 2'::jsquery; 290 | select '[1,2]' @@ '@# > 1'::jsquery; 291 | select '[1,2]' @@ '@# in (1, 2)'::jsquery; 292 | select '[1,2]' @@ '@# in (1, 3)'::jsquery; 293 | select '{"a":[1,2]}' @@ '@# in (2, 4)'::jsquery; 294 | select '{"a":[1,2]}' @@ 'a.@# in (2, 4)'::jsquery; 295 | select '{"a":[1,2]}' @@ '%.@# in (2, 4)'::jsquery; 296 | select '{"a":[1,2]}' @@ '*.@# in (2, 4)'::jsquery; 297 | select '{"a":[1,2]}' @@ '*.@# ($ = 4 or $ = 2)'::jsquery; 298 | select '{"a":[1,2]}' @@ '@# = 1'::jsquery; 299 | 300 | --filter 301 | select '?( not b>0). x'::jsquery; 302 | select 'a.?(b>0 and x= 0 ) .c'::jsquery; 303 | select 'a.$. ?(b>0 and x= 0 ) . c.k'::jsquery; 304 | select 'a.$.? (b>0 and x.*= 0 ).c.k'::jsquery; 305 | select '[{"a":1, "b":10}, {"a":2, "b":20}, {"a":3, "b":30}]'::jsonb @@ '#. ?(a < 0) (b=20)'::jsquery; 306 | select '[{"a":1, "b":10}, {"a":2, "b":20}, {"a":3, "b":30}]'::jsonb @@ '#. ?(a > 0) (b=20)'::jsquery; 307 | select '[{"a":1, "b":10}, {"a":2, "b":20}, {"a":3, "b":30}]'::jsonb @@ '#. ?(a > 1) (b=20)'::jsquery; 308 | select '[{"a":1, "b":10}, {"a":2, "b":20}, {"a":3, "b":30}]'::jsonb @@ '#. ?(a > 2) (b=20)'::jsquery; 309 | select '[{"a":1, "b":10}, {"a":2, "b":20}, {"a":3, "b":30}]'::jsonb @@ '#. ?(a > 3) (b=20)'::jsquery; 310 | select '[{"a":1, "b":10}, {"a":2, "b":20}]'::jsonb ~~ '#.a'::jsquery; 311 | select '[{"a":1, "b":10}, {"a":2, "b":20}]'::jsonb ~~ '#. ?(a > 1). b'::jsquery; 312 | select '[{"a":1, "b":10}, {"a":2, "b":20}, {"a":3, "b":30}]'::jsonb ~~ '# . ?(a > 1)'::jsquery; 313 | select '[{"a":1, "b":10}, {"a":2, "b":20}, {"a":3, "b":30}]'::jsonb ~~ '%'::jsquery; 314 | select '{"a":1, "b":2, "c":3}'::jsonb ~~ '%'::jsquery; 315 | select '{"a":1, "b":2, "c":3}'::jsonb ~~ '% . ? ( $ > 2 )'::jsquery; 316 | select '{"a":1, "b":2, "c":3}'::jsonb ~~ '% . ? ( $ > 2 ).$'::jsquery; 317 | select '{"a":1, "b":2, "c":3}'::jsonb ~~ '? ( % > 2 )'::jsquery; 318 | select '{"a":1, "b":2, "c":3}'::jsonb ~~ '? ( %: > 0 )'::jsquery; 319 | select '{"a":1, "b":2, "c":3}'::jsonb ~~ '? ( %: > 2 )'::jsquery; 320 | select '[{"a":1, "b":10}, {"a":2, "b":20}, {"a":3, "b":30}]'::jsonb ~~ '#'::jsquery; 321 | select '[1,2,3]'::jsonb ~~ '#'::jsquery; 322 | select '[1,2,3]'::jsonb ~~ '#. ?($ > 2)'::jsquery; 323 | select '[1,2,3]'::jsonb ~~ '#. ?($ > 2).$'::jsquery; 324 | select '[1,2,3]'::jsonb ~~ ' ?(#.$ > 2).$'::jsquery; 325 | select '[1,2,3]'::jsonb ~~ ' ?(#:.$ > 2).$'::jsquery; 326 | select '[1,2,3]'::jsonb ~~ ' ?(#:.$ > 0).$'::jsquery; 327 | select '{"a": {"b": {"c": 1}}}'::jsonb ~~ '*.?(c >0)'::jsquery; 328 | select '{"a": {"b": {"c": 1}}}'::jsonb ~~ '?(*.c >0)'::jsquery; 329 | select '{"tags":[{"term":["NYC", "CYN"]}, {"term":["1NYC", "1CYN"]} ]}'::jsonb ~~ 'tags.#.term.#. ? ( $ = "NYC")'::jsquery; 330 | select '{"tags":[{"term":["NYC", "CYN"]}, {"term":["1NYC", "1CYN"]} ]}'::jsonb ~~ 'tags.#.term. ? ( # = "NYC")'::jsquery; 331 | 332 | --ALL 333 | select 'a.*: = 4'::jsquery; 334 | select '%: = 4'::jsquery; 335 | select '#:.i = 4'::jsquery; 336 | select '[]' @@ '#: ($ > 1 and $ < 5)'::jsquery; 337 | select '[2,3,4]' @@ '#: ($ > 1 and $ < 5)'::jsquery; 338 | select '[2,3,5]' @@ '#: ($ > 1 and $ < 5)'::jsquery; 339 | select '[2,3,5]' @@ '# ($ > 1 and $ < 5)'::jsquery; 340 | select '[2,3,"x"]' @@ '#: ($ > 1 and $ < 5)'::jsquery; 341 | select '{}' @@ '%: ($ > 1 and $ < 5)'::jsquery; 342 | select '{}' @@ '*: ($ is object)'::jsquery; 343 | select '"a"' @@ '*: is string'::jsquery; 344 | select '1' @@ '*: is string'::jsquery; 345 | select '{"a":2,"b":3,"c":4}' @@ '%: ($ > 1 and $ < 5)'::jsquery; 346 | select '{"a":2,"b":3,"c":5}' @@ '%: ($ > 1 and $ < 5)'::jsquery; 347 | select '{"a":2,"b":3,"c":5}' @@ '% ($ > 1 and $ < 5)'::jsquery; 348 | select '{"a":2,"b":3,"c":"x"}' @@ '%: ($ > 1 and $ < 5)'::jsquery; 349 | select '{"a":2,"b":3,"c":4}' @@ '*: ($ > 1 and $ < 5)'::jsquery; 350 | select '{"a":2,"b":3,"c":5}' @@ '*: ($ > 1 and $ < 5)'::jsquery; 351 | select '{"a":2,"b":3,"c":4}' @@ '*: ($ is object OR ($> 1 and $ < 5))'::jsquery; 352 | select '{"a":2,"b":3,"c":5}' @@ '*: ($ is object OR ($> 1 and $ < 5))'::jsquery; 353 | select '{"b":{"ba":3, "bb":4}}' @@ '*: ($ is object OR ($ > 1 and $ < 5))'::jsquery; 354 | select '{"b":{"ba":3, "bb":5}}' @@ '*: ($ is object OR ($> 1 and $ < 5))'::jsquery; 355 | select '{"a":{"aa":1, "ab":2}, "b":{"ba":3, "bb":4}}' @@ '*: ($ is object OR ($ > 0 and $ < 5))'::jsquery; 356 | select '{"a":{"aa":1, "ab":2}, "b":{"ba":3, "bb":5}}' @@ '*: ($ is object OR ($> 0 and $ < 5))'::jsquery; 357 | select '{"a":{"aa":1, "ab":2}, "b":{"ba":3, "bb":5}}' @@ '* ($ > 0 and $ < 5)'::jsquery; 358 | select '{"a":{"aa":1, "ab":2}, "b":{"ba":3, "bb":5}}' @@ '*: ($ is object OR $ is numeric)'::jsquery; 359 | select '{"a":{"aa":1, "ab":2}, "b":[5,6]}' @@ '*: ($ is object OR $ is numeric)'::jsquery; 360 | select '{"a":{"aa":1, "ab":2}, "b":[5,6]}' @@ '*: ($ is object OR $ is array OR $ is numeric)'::jsquery; 361 | select '{"a":{"aa":1, "ab":2}, "b":[5,6, {"c":8}]}' @@ '*: ($ is object OR $ is array OR $ is numeric)'::jsquery; 362 | select '{"a":{"aa":1, "ab":2}, "b":[5,6, {"c":"x"}]}' @@ '*: ($ is object OR $ is array OR $ is numeric)'::jsquery; 363 | select '{"a":{"aa":1, "ab":2}, "b":[5,6, {"c":null}]}' @@ '*: ($ is object OR $ is array OR $ is numeric)'::jsquery; 364 | select '{"a":{"aa":1}, "b":{"aa":1, "bb":2}}' @@ '%:.aa is numeric'::jsquery; 365 | select '{"a":{"aa":1}, "b":{"aa":true, "bb":2}}' @@ '%:.aa is numeric'::jsquery; 366 | select '{"a":{"aa":1}, "b":{"aa":1, "bb":2}, "aa":16}' @@ '*: (not $ is object or $.aa is numeric)'::jsquery; 367 | select '{"a":{"aa":1}, "b":{"aa":1, "bb":2}}' @@ '*: (not $ is object or $.aa is numeric or % is object)'::jsquery; 368 | SELECT 'test.# IN (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64)'::jsquery; 369 | 370 | select '[]' @@ '(@# > 0 and #: = 16)'::jsquery; 371 | select '[16]' @@ '(@# > 0 and #: = 16)'::jsquery; 372 | 373 | select '{"a": {"b": 1, "c": 2}, "b": {"d":3}}'::jsonb @@ 'a.b or b.d'::jsquery; 374 | select '{"a": {"b": 1, "c": 2}, "b": {"d":3}}'::jsonb @@ 'a.c or b.d'::jsquery; 375 | select '{"a": {"b": 1, "c": 2}, "b": {"d":3}}'::jsonb @@ 'a.g or b.d'::jsquery; 376 | select '{"a": {"b": 1, "c": 2}, "b": {"d":3}}'::jsonb @@ 'a.g or b.c'::jsquery; 377 | select '{"a": {"b": 1, "c": 2}, "b": {"d":3}}'::jsonb @@ 'a.b and b.d'::jsquery; 378 | select '{"a": {"b": 1, "c": 2}, "b": {"d":3}}'::jsonb @@ 'a.c and b.d'::jsquery; 379 | select '{"a": {"b": 1, "c": 2}, "b": {"d":3}}'::jsonb @@ 'a.c and b.b'::jsquery; 380 | select '{"a": {"b": 1, "c": 2}, "b": {"d":3}}'::jsonb @@ 'a.g and b.d'::jsquery; 381 | 382 | select '{"a": {"b": 1, "c": 2}, "b": {"d":3}}'::jsonb ~~ 'a.b and b.d'::jsquery; 383 | select '{"a": {"b": 1, "c": 2}, "b": {"d":3}}'::jsonb ~~ 'a.b and b.c'::jsquery; 384 | select '{"a": {"b": 1, "c": 2}, "b": {"d":3}}'::jsonb ~~ 'a.b and (b.d or b.c)'::jsquery; 385 | select '{"a": {"b": 1, "c": 2}, "b": {"d":3}}'::jsonb ~~ 'a.b and (b.c or b.d)'::jsquery; 386 | select '{"a": {"b": 1, "c": 2}, "b": {"d":3}}'::jsonb ~~ 'a.b and a.c and b.d'::jsquery; 387 | select '{"a": {"b": 1, "c": 2}, "b": {"d":3}}'::jsonb ~~ '(a.b or a.c) and b.d'::jsquery; 388 | select '{"a": {"b": 1, "c": 2}, "b": {"d":3}}'::jsonb ~~ '(a.e or a.c) and b.d'::jsquery; 389 | select '{"a": {"b": 1, "c": 2}, "b": {"d":3}}'::jsonb ~~ '(a.e or a.g) and b.d'::jsquery; 390 | select '{"a": {"b": 1, "c": 2}, "b": {"d":3}}'::jsonb ~~ 'a.b or (b.d or b.c)'::jsquery; 391 | select '{"a": {"b": 1, "c": 2}, "b": {"d":3}}'::jsonb ~~ 'b.d or (a.b or a.c)'::jsquery; 392 | select '{"a": {"b": 1, "c": 2}, "b": {"d":3}}'::jsonb ~~ 'b.d or (a.b and a.c)'::jsquery; 393 | select '{"a": {"b": 1, "c": 2}, "b": {"d":3}}'::jsonb ~~ 'b.f or (a.b and a.c)'::jsquery; 394 | select '{"a": {"b": 1, "c": 2}, "b": {"d":3}}'::jsonb ~~ 'b.d and (a.b and a.c)'::jsquery; 395 | select '{"a": {"b": [6,5,4], "c": 2}, "b": {"d":3}}'::jsonb ~~ 'b.d and (a.b and a.c)'::jsquery; 396 | 397 | --extract entries for index scan 398 | 399 | SELECT gin_debug_query_path_value('NOT NOT NOT x(y(NOT (a=1) and NOT (b=2)) OR NOT NOT (c=3)) and z = 5'); 400 | SELECT gin_debug_query_path_value('NOT #(x=1) and NOT *(y=1) and NOT %(z=1) '); 401 | SELECT gin_debug_query_path_value('#(NOT x=1) and *(NOT y=1) and %(NOT z=1) '); 402 | SELECT gin_debug_query_path_value('NOT #(NOT x=1) and NOT *(NOT y=1) and NOT %(NOT z=1) '); 403 | SELECT gin_debug_query_path_value('#(x = "a" and y > 0 and y < 1 and z > 0)'); 404 | SELECT gin_debug_query_path_value('#(x = "a" and y /*-- index */ >= 0 and y < 1 and z > 0)'); 405 | SELECT gin_debug_query_path_value('#(x /*-- noindex */ = "a" and y > 0 and y <= 1 and z /*-- index */ > 0)'); 406 | SELECT gin_debug_query_path_value('x = 1 and (y /*-- index */ > 0 and y < 1 OR z > 0)'); 407 | SELECT gin_debug_query_path_value('%.x = 1'); 408 | SELECT gin_debug_query_path_value('*.x = "b"'); 409 | SELECT gin_debug_query_path_value('x && [1,2,3]'); 410 | SELECT gin_debug_query_path_value('x @> [1,2,3]'); 411 | SELECT gin_debug_query_path_value('x <@ [1,2,3]'); 412 | SELECT gin_debug_query_path_value('x = *'); 413 | SELECT gin_debug_query_path_value('x is boolean'); 414 | SELECT gin_debug_query_path_value('x is string'); 415 | SELECT gin_debug_query_path_value('x is numeric'); 416 | SELECT gin_debug_query_path_value('x is array'); 417 | SELECT gin_debug_query_path_value('x is object'); 418 | SELECT gin_debug_query_path_value('#:(x=1) AND %:(y=1) AND *:(z=1)'); 419 | SELECT gin_debug_query_path_value('#:(NOT x=1) AND %:(NOT y=1) AND *:(NOT z=1)'); 420 | SELECT gin_debug_query_path_value('NOT #:(NOT x=1) AND NOT %:(NOT y=1) AND NOT *:(NOT z=1)'); 421 | SELECT gin_debug_query_path_value('$ = true'); 422 | SELECT gin_debug_query_path_value('$ . ? (review_votes > 10) . review_rating < 7'); 423 | SELECT gin_debug_query_path_value('similar_product_ids . ? (# = "B0002W4TL2") . $'); 424 | 425 | SELECT gin_debug_query_value_path('NOT NOT NOT x(y(NOT (a=1) and NOT (b=2)) OR NOT NOT (c=3)) and z = 5'); 426 | SELECT gin_debug_query_value_path('NOT #(x=1) and NOT *(y=1) and NOT %(z=1) '); 427 | SELECT gin_debug_query_value_path('#(NOT x=1) and *(NOT y=1) and %(NOT z=1) '); 428 | SELECT gin_debug_query_value_path('NOT #(NOT x=1) and NOT *(NOT y=1) and NOT %(NOT z=1) '); 429 | SELECT gin_debug_query_value_path('#(x = "a" and y > 0 and y < 1 and z > 0)'); 430 | SELECT gin_debug_query_value_path('#(x = "a" and y /*-- index */ >= 0 and y < 1 and z > 0)'); 431 | SELECT gin_debug_query_value_path('#(x /*-- noindex */ = "a" and y > 0 and y <= 1 and z /*-- index */ > 0)'); 432 | SELECT gin_debug_query_value_path('x = 1 and (y /*-- index */ > 0 and y < 1 OR z > 0)'); 433 | SELECT gin_debug_query_value_path('%.x = 1'); 434 | SELECT gin_debug_query_value_path('*.x = "b"'); 435 | SELECT gin_debug_query_value_path('x && [1,2,3]'); 436 | SELECT gin_debug_query_value_path('x @> [1,2,3]'); 437 | SELECT gin_debug_query_value_path('x <@ [1,2,3]'); 438 | SELECT gin_debug_query_value_path('x = [1,2,3]'); 439 | SELECT gin_debug_query_value_path('x = *'); 440 | SELECT gin_debug_query_value_path('x is boolean'); 441 | SELECT gin_debug_query_value_path('x is string'); 442 | SELECT gin_debug_query_value_path('x is numeric'); 443 | SELECT gin_debug_query_value_path('x is array'); 444 | SELECT gin_debug_query_value_path('x is object'); 445 | SELECT gin_debug_query_value_path('#:(x=1) AND %:(y=1) AND *:(z=1)'); 446 | SELECT gin_debug_query_value_path('#:(NOT x=1) AND %:(NOT y=1) AND *:(NOT z=1)'); 447 | SELECT gin_debug_query_value_path('NOT #:(NOT x=1) AND NOT %:(NOT y=1) AND NOT *:(NOT z=1)'); 448 | SELECT gin_debug_query_value_path('(@# > 0 and #: = 16)'); 449 | SELECT gin_debug_query_value_path('*.@# ($ = 4 or $ = 2)'); 450 | SELECT gin_debug_query_value_path('tags.#.term. ? ( # = "NYC").x > 0'); 451 | SELECT gin_debug_query_value_path('$ = true'); 452 | SELECT gin_debug_query_value_path('$ . ? (review_votes > 10) . review_rating < 7'); 453 | SELECT gin_debug_query_value_path('similar_product_ids . ? (# = "B0002W4TL2") . $'); 454 | 455 | ---table and index 456 | 457 | select count(*) from test_jsquery where (v->>'review_helpful_votes')::int4 > 0; 458 | select count(*) from test_jsquery where (v->>'review_helpful_votes')::int4 > 19; 459 | select count(*) from test_jsquery where (v->>'review_helpful_votes')::int4 < 19; 460 | select count(*) from test_jsquery where (v->>'review_helpful_votes')::int4 >= 19; 461 | select count(*) from test_jsquery where (v->>'review_helpful_votes')::int4 <= 19; 462 | select count(*) from test_jsquery where (v->>'review_helpful_votes')::int4 = 19; 463 | select count(*) from test_jsquery where (v->>'review_helpful_votes')::int4 > 16 AND 464 | (v->>'review_helpful_votes')::int4 < 20; 465 | 466 | 467 | select count(*) from test_jsquery where v @@ 'review_helpful_votes > 0'::jsquery; 468 | select count(*) from test_jsquery where v @@ 'review_helpful_votes > 19'::jsquery; 469 | select count(*) from test_jsquery where v @@ 'review_helpful_votes < 19'::jsquery; 470 | select count(*) from test_jsquery where v @@ 'review_helpful_votes >= 19'::jsquery; 471 | select count(*) from test_jsquery where v @@ 'review_helpful_votes <= 19'::jsquery; 472 | select count(*) from test_jsquery where v @@ 'review_helpful_votes = 19'::jsquery; 473 | select count(*) from test_jsquery where v @@ 'review_helpful_votes > 16'::jsquery AND 474 | v @@ 'review_helpful_votes < 20'::jsquery; 475 | select count(*) from test_jsquery where v @@ 'review_helpful_votes > 16 and review_helpful_votes < 20'::jsquery; 476 | select count(*) from test_jsquery where v @@ 'review_helpful_votes ($ > 16 and $ < 20)'::jsquery; 477 | select count(*) from test_jsquery where v @@ 'similar_product_ids && ["0440180295"]'::jsquery; 478 | select count(*) from test_jsquery where v @@ 'similar_product_ids(# = "0440180295") '::jsquery; 479 | select count(*) from test_jsquery where v @@ 'similar_product_ids.#($ = "0440180295") '::jsquery; 480 | select count(*) from test_jsquery where v @@ 'similar_product_ids && ["0440180295"] and product_sales_rank > 300000'::jsquery; 481 | select count(*) from test_jsquery where v @@ 'similar_product_ids <@ ["B00000DG0U", "B00004SQXU", "B0001XAM18", "B00000FDBU", "B00000FDBV", "B000002H2H", "B000002H6C", "B000002H5E", "B000002H97", "B000002HMH"]'::jsquery; 482 | select count(*) from test_jsquery where v @@ 'similar_product_ids @> ["B000002H2H", "B000002H6C"]'::jsquery; 483 | select count(*) from test_jsquery where v @@ 'customer_id = null'::jsquery; 484 | select count(*) from test_jsquery where v @@ 'review_votes = true'::jsquery; 485 | select count(*) from test_jsquery where v @@ 'product_group = false'::jsquery; 486 | select count(*) from test_jsquery where v @@ 't = *'::jsquery; 487 | select count(*) from test_jsquery where v @@ 't is boolean'::jsquery; 488 | select count(*) from test_jsquery where v @@ 't is string'::jsquery; 489 | select count(*) from test_jsquery where v @@ 't is numeric'::jsquery; 490 | select count(*) from test_jsquery where v @@ 't is array'::jsquery; 491 | select count(*) from test_jsquery where v @@ 't is object'::jsquery; 492 | select count(*) from test_jsquery where v @@ '$ is boolean'::jsquery; 493 | select count(*) from test_jsquery where v @@ '$ is string'::jsquery; 494 | select count(*) from test_jsquery where v @@ '$ is numeric'::jsquery; 495 | select count(*) from test_jsquery where v @@ '$ is array'::jsquery; 496 | select count(*) from test_jsquery where v @@ '$ is object'::jsquery; 497 | select count(*) from test_jsquery where v @@ 'similar_product_ids.#: is numeric'::jsquery; 498 | select count(*) from test_jsquery where v @@ 'similar_product_ids.#: is string'::jsquery; 499 | select count(*) from test_jsquery where v @@ 'NOT similar_product_ids.#: (NOT $ = "0440180295")'::jsquery; 500 | select count(*) from test_jsquery where v @@ '$ > 2'::jsquery; 501 | select count(*) from test_jsquery where v @@ '$ = false'::jsquery; 502 | select count(*) from test_jsquery where v @@ 't'::jsquery; 503 | select count(*) from test_jsquery where v @@ '$'::jsquery; 504 | select count(*) from test_jsquery where v @@ 'similar_product_ids.#'::jsquery; 505 | select count(*) from test_jsquery where v @@ '$ . ? (review_votes > 10) . review_rating < 7'::jsquery; 506 | select count(*) from test_jsquery where v @@ 'similar_product_ids . ? (# = "B0002W4TL2") . $'::jsquery; 507 | 508 | select v from test_jsquery where v @@ 'array <@ [2,3]'::jsquery order by v; 509 | select v from test_jsquery where v @@ 'array && [2,3]'::jsquery order by v; 510 | select v from test_jsquery where v @@ 'array @> [2,3]'::jsquery order by v; 511 | select v from test_jsquery where v @@ 'array = [2,3]'::jsquery order by v; 512 | 513 | create index t_idx on test_jsquery using gin (v jsonb_value_path_ops); 514 | set enable_seqscan = off; 515 | 516 | explain (costs off) select count(*) from test_jsquery where v @@ 'review_helpful_votes > 0'::jsquery; 517 | 518 | select count(*) from test_jsquery where v @@ 'review_helpful_votes > 0'::jsquery; 519 | select count(*) from test_jsquery where v @@ 'review_helpful_votes > 19'::jsquery; 520 | select count(*) from test_jsquery where v @@ 'review_helpful_votes < 19'::jsquery; 521 | select count(*) from test_jsquery where v @@ 'review_helpful_votes >= 19'::jsquery; 522 | select count(*) from test_jsquery where v @@ 'review_helpful_votes <= 19'::jsquery; 523 | select count(*) from test_jsquery where v @@ 'review_helpful_votes = 19'::jsquery; 524 | select count(*) from test_jsquery where v @@ 'review_helpful_votes > 16'::jsquery AND 525 | v @@ 'review_helpful_votes < 20'::jsquery; 526 | select count(*) from test_jsquery where v @@ 'review_helpful_votes > 16 and review_helpful_votes < 20'::jsquery; 527 | select count(*) from test_jsquery where v @@ 'review_helpful_votes ($ > 16 and $ < 20)'::jsquery; 528 | select count(*) from test_jsquery where v @@ 'similar_product_ids && ["0440180295"]'::jsquery; 529 | select count(*) from test_jsquery where v @@ 'similar_product_ids(# = "0440180295") '::jsquery; 530 | select count(*) from test_jsquery where v @@ 'similar_product_ids.#($ = "0440180295") '::jsquery; 531 | select count(*) from test_jsquery where v @@ 'similar_product_ids && ["0440180295"] and product_sales_rank > 300000'::jsquery; 532 | select count(*) from test_jsquery where v @@ 'similar_product_ids <@ ["B00000DG0U", "B00004SQXU", "B0001XAM18", "B00000FDBU", "B00000FDBV", "B000002H2H", "B000002H6C", "B000002H5E", "B000002H97", "B000002HMH"]'::jsquery; 533 | select count(*) from test_jsquery where v @@ 'similar_product_ids @> ["B000002H2H", "B000002H6C"]'::jsquery; 534 | select count(*) from test_jsquery where v @@ 'customer_id = null'::jsquery; 535 | select count(*) from test_jsquery where v @@ 'review_votes = true'::jsquery; 536 | select count(*) from test_jsquery where v @@ 'product_group = false'::jsquery; 537 | select count(*) from test_jsquery where v @@ 't = *'::jsquery; 538 | select count(*) from test_jsquery where v @@ 't is boolean'::jsquery; 539 | select count(*) from test_jsquery where v @@ 't is string'::jsquery; 540 | select count(*) from test_jsquery where v @@ 't is numeric'::jsquery; 541 | select count(*) from test_jsquery where v @@ 't is array'::jsquery; 542 | select count(*) from test_jsquery where v @@ 't is object'::jsquery; 543 | select count(*) from test_jsquery where v @@ '$ is boolean'::jsquery; 544 | select count(*) from test_jsquery where v @@ '$ is string'::jsquery; 545 | select count(*) from test_jsquery where v @@ '$ is numeric'::jsquery; 546 | select count(*) from test_jsquery where v @@ '$ is array'::jsquery; 547 | select count(*) from test_jsquery where v @@ '$ is object'::jsquery; 548 | select count(*) from test_jsquery where v @@ 'similar_product_ids.#: is numeric'::jsquery; 549 | select count(*) from test_jsquery where v @@ 'similar_product_ids.#: is string'::jsquery; 550 | select count(*) from test_jsquery where v @@ 'NOT similar_product_ids.#: (NOT $ = "0440180295")'::jsquery; 551 | select count(*) from test_jsquery where v @@ '$ > 2'::jsquery; 552 | select count(*) from test_jsquery where v @@ '$ = false'::jsquery; 553 | select count(*) from test_jsquery where v @@ 't'::jsquery; 554 | select count(*) from test_jsquery where v @@ '$'::jsquery; 555 | select count(*) from test_jsquery where v @@ 'similar_product_ids.#'::jsquery; 556 | select count(*) from test_jsquery where v @@ '$ . ? (review_votes > 10) . review_rating < 7'::jsquery; 557 | select count(*) from test_jsquery where v @@ 'similar_product_ids . ? (# = "B0002W4TL2") . $'::jsquery; 558 | 559 | explain (costs off) select v from test_jsquery where v @@ 'array <@ [2,3]'::jsquery order by v; 560 | explain (costs off) select v from test_jsquery where v @@ 'array && [2,3]'::jsquery order by v; 561 | explain (costs off) select v from test_jsquery where v @@ 'array @> [2,3]'::jsquery order by v; 562 | explain (costs off) select v from test_jsquery where v @@ 'array = [2,3]'::jsquery order by v; 563 | 564 | select v from test_jsquery where v @@ 'array <@ [2,3]'::jsquery order by v; 565 | select v from test_jsquery where v @@ 'array && [2,3]'::jsquery order by v; 566 | select v from test_jsquery where v @@ 'array @> [2,3]'::jsquery order by v; 567 | select v from test_jsquery where v @@ 'array = [2,3]'::jsquery order by v; 568 | 569 | drop index t_idx; 570 | 571 | create index t_idx on test_jsquery using gin (v jsonb_path_value_ops); 572 | set enable_seqscan = off; 573 | 574 | explain (costs off) select count(*) from test_jsquery where v @@ 'review_helpful_votes > 0'::jsquery; 575 | 576 | select count(*) from test_jsquery where v @@ 'review_helpful_votes > 0'::jsquery; 577 | select count(*) from test_jsquery where v @@ 'review_helpful_votes > 19'::jsquery; 578 | select count(*) from test_jsquery where v @@ 'review_helpful_votes < 19'::jsquery; 579 | select count(*) from test_jsquery where v @@ 'review_helpful_votes >= 19'::jsquery; 580 | select count(*) from test_jsquery where v @@ 'review_helpful_votes <= 19'::jsquery; 581 | select count(*) from test_jsquery where v @@ 'review_helpful_votes = 19'::jsquery; 582 | select count(*) from test_jsquery where v @@ 'review_helpful_votes > 16'::jsquery AND 583 | v @@ 'review_helpful_votes < 20'::jsquery; 584 | select count(*) from test_jsquery where v @@ 'review_helpful_votes > 16 and review_helpful_votes < 20'::jsquery; 585 | select count(*) from test_jsquery where v @@ 'review_helpful_votes ($ > 16 and $ < 20)'::jsquery; 586 | select count(*) from test_jsquery where v @@ 'similar_product_ids && ["0440180295"]'::jsquery; 587 | select count(*) from test_jsquery where v @@ 'similar_product_ids(# = "0440180295") '::jsquery; 588 | select count(*) from test_jsquery where v @@ 'similar_product_ids.#($ = "0440180295") '::jsquery; 589 | select count(*) from test_jsquery where v @@ 'similar_product_ids && ["0440180295"] and product_sales_rank > 300000'::jsquery; 590 | select count(*) from test_jsquery where v @@ 'similar_product_ids <@ ["B00000DG0U", "B00004SQXU", "B0001XAM18", "B00000FDBU", "B00000FDBV", "B000002H2H", "B000002H6C", "B000002H5E", "B000002H97", "B000002HMH"]'::jsquery; 591 | select count(*) from test_jsquery where v @@ 'similar_product_ids @> ["B000002H2H", "B000002H6C"]'::jsquery; 592 | select count(*) from test_jsquery where v @@ 'customer_id = null'::jsquery; 593 | select count(*) from test_jsquery where v @@ 'review_votes = true'::jsquery; 594 | select count(*) from test_jsquery where v @@ 'product_group = false'::jsquery; 595 | select count(*) from test_jsquery where v @@ 't = *'::jsquery; 596 | select count(*) from test_jsquery where v @@ 't is boolean'::jsquery; 597 | select count(*) from test_jsquery where v @@ 't is string'::jsquery; 598 | select count(*) from test_jsquery where v @@ 't is numeric'::jsquery; 599 | select count(*) from test_jsquery where v @@ 't is array'::jsquery; 600 | select count(*) from test_jsquery where v @@ 't is object'::jsquery; 601 | select count(*) from test_jsquery where v @@ '$ is boolean'::jsquery; 602 | select count(*) from test_jsquery where v @@ '$ is string'::jsquery; 603 | select count(*) from test_jsquery where v @@ '$ is numeric'::jsquery; 604 | select count(*) from test_jsquery where v @@ '$ is array'::jsquery; 605 | select count(*) from test_jsquery where v @@ '$ is object'::jsquery; 606 | select count(*) from test_jsquery where v @@ 'similar_product_ids.#: is numeric'::jsquery; 607 | select count(*) from test_jsquery where v @@ 'similar_product_ids.#: is string'::jsquery; 608 | select count(*) from test_jsquery where v @@ 'NOT similar_product_ids.#: (NOT $ = "0440180295")'::jsquery; 609 | select count(*) from test_jsquery where v @@ '$ > 2'::jsquery; 610 | select count(*) from test_jsquery where v @@ '$ = false'::jsquery; 611 | select count(*) from test_jsquery where v @@ 't'::jsquery; 612 | select count(*) from test_jsquery where v @@ '$'::jsquery; 613 | select count(*) from test_jsquery where v @@ 'similar_product_ids.#'::jsquery; 614 | select count(*) from test_jsquery where v @@ '$ . ? (review_votes > 10) . review_rating < 7'::jsquery; 615 | select count(*) from test_jsquery where v @@ 'similar_product_ids . ? (# = "B0002W4TL2") . $'::jsquery; 616 | 617 | explain (costs off) select v from test_jsquery where v @@ 'array <@ [2,3]'::jsquery order by v; 618 | explain (costs off) select v from test_jsquery where v @@ 'array && [2,3]'::jsquery order by v; 619 | explain (costs off) select v from test_jsquery where v @@ 'array @> [2,3]'::jsquery order by v; 620 | explain (costs off) select v from test_jsquery where v @@ 'array = [2,3]'::jsquery order by v; 621 | 622 | select v from test_jsquery where v @@ 'array <@ [2,3]'::jsquery order by v; 623 | select v from test_jsquery where v @@ 'array && [2,3]'::jsquery order by v; 624 | select v from test_jsquery where v @@ 'array @> [2,3]'::jsquery order by v; 625 | select v from test_jsquery where v @@ 'array = [2,3]'::jsquery order by v; 626 | 627 | RESET enable_seqscan; 628 | -------------------------------------------------------------------------------- /travis/dep-ubuntu-llvm.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cat ./travis/llvm-snapshot.gpg.key | sudo apt-key add - 4 | echo "deb http://apt.llvm.org/trusty/ llvm-toolchain-$(lsb_release -cs)-$LLVM_VER main" | sudo tee /etc/apt/sources.list.d/llvm.list 5 | -------------------------------------------------------------------------------- /travis/dep-ubuntu-postgres.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cat ./travis/postgresql.gpg.key | sudo apt-key add - 4 | echo "deb http://apt.postgresql.org/pub/repos/apt/ $(lsb_release -cs)-pgdg main $PG_VER" | sudo tee /etc/apt/sources.list.d/pgdg.list 5 | -------------------------------------------------------------------------------- /travis/llvm-snapshot.gpg.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PUBLIC KEY BLOCK----- 2 | Version: GnuPG v1.4.12 (GNU/Linux) 3 | 4 | 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 50 | bXb5c6gCHESH5PXwPU4jQEE7Ib9J6sbk7ZT2Mw== 51 | =j+4q 52 | -----END PGP PUBLIC KEY BLOCK----- 53 | -------------------------------------------------------------------------------- /travis/pg-travis-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eux 4 | 5 | echo -en 'travis_fold:start:pg_install\\r' && echo 'PostgreSQL installation' 6 | 7 | sudo apt-get update 8 | 9 | # bug: http://www.postgresql.org/message-id/20130508192711.GA9243@msgid.df7cb.de 10 | sudo update-alternatives --remove-all postmaster.1.gz 11 | 12 | # stop all existing instances (because of https://github.com/travis-ci/travis-cookbooks/pull/221) 13 | sudo service postgresql stop 14 | # ... and make sure they don't come back 15 | echo 'exit 0' | sudo tee /etc/init.d/postgresql 16 | sudo chmod a+x /etc/init.d/postgresql 17 | 18 | # install PostgreSQL 19 | if [ $CHECK_TYPE = "valgrind" ]; then 20 | # install required packages 21 | apt_packages="build-essential libgd-dev valgrind lcov" 22 | sudo apt-get -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" -y install -qq $apt_packages 23 | 24 | set -e 25 | 26 | pushd ~ 27 | CUSTOM_PG_BIN=$PWD/pg_bin 28 | CUSTOM_PG_SRC=$PWD/postgresql 29 | 30 | curl "https://ftp.postgresql.org/pub/source/v$PG_VER/postgresql-$PG_VER.tar.bz2" -o postgresql.tar.bz2 31 | mkdir $CUSTOM_PG_SRC 32 | 33 | tar \ 34 | --extract \ 35 | --file postgresql.tar.bz2 \ 36 | --directory $CUSTOM_PG_SRC \ 37 | --strip-components 1 38 | 39 | cd $CUSTOM_PG_SRC 40 | 41 | # enable Valgrind support 42 | sed -i.bak "s/\/* #define USE_VALGRIND *\//#define USE_VALGRIND/g" src/include/pg_config_manual.h 43 | 44 | # enable additional options 45 | ./configure \ 46 | CFLAGS='-Og -ggdb3 -fno-omit-frame-pointer' \ 47 | --enable-cassert \ 48 | --enable-coverage \ 49 | --prefix=$CUSTOM_PG_BIN \ 50 | --quiet 51 | 52 | # build & install PG 53 | time make -s -j4 && make -s install 54 | 55 | # override default PostgreSQL instance 56 | export PATH=$CUSTOM_PG_BIN/bin:$PATH 57 | export LD_LIBRARY_PATH=$CUSTOM_PG_BIN/lib 58 | 59 | popd 60 | set +e 61 | prefix=$CUSTOM_PG_BIN 62 | else 63 | apt_packages="postgresql-$PG_VER postgresql-server-dev-$PG_VER postgresql-common build-essential libgd-dev" 64 | sudo apt-get -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" -y install -qq $apt_packages 65 | prefix=/usr/lib/postgresql/$PG_VER 66 | fi 67 | 68 | # config path 69 | pg_ctl_path=$prefix/bin/pg_ctl 70 | initdb_path=$prefix/bin/initdb 71 | config_path=$prefix/bin/pg_config 72 | 73 | # exit code 74 | status=0 75 | 76 | echo -en 'travis_fold:end:pg_install\\r' 77 | 78 | # perform code analysis if necessary 79 | if [ $CHECK_TYPE = "static" ]; then 80 | echo -en 'travis_fold:start:static_analysis\\r' && echo 'Static analysis' 81 | 82 | if [ "$CC" = "clang" ]; then 83 | sudo apt-get -y install -qq clang-$LLVM_VER 84 | 85 | scan-build-$LLVM_VER --status-bugs \ 86 | -disable-checker deadcode.DeadStores \ 87 | make USE_PGXS=1 USE_ASSERT_CHECKING=1 PG_CONFIG=$config_path || status=$? 88 | 89 | elif [ "$CC" = "gcc" ]; then 90 | sudo apt-get -y install -qq cppcheck 91 | 92 | cppcheck --template "{file} ({line}): {severity} ({id}): {message}" \ 93 | --enable=warning,portability,performance \ 94 | --suppress=redundantAssignment \ 95 | --suppress=uselessAssignmentPtrArg \ 96 | --suppress=incorrectStringBooleanError \ 97 | --std=c89 *.c *.h 2> cppcheck.log 98 | 99 | if [ -s cppcheck.log ]; then 100 | cat cppcheck.log 101 | status=1 # error 102 | fi 103 | fi 104 | 105 | # don't forget to "make clean" 106 | make clean USE_PGXS=1 PG_CONFIG=$config_path 107 | echo -en 'travis_fold:end:static_analysis\\r' 108 | exit $status 109 | fi 110 | 111 | echo -en 'travis_fold:start:build_extension\\r' && echo 'Build extension' 112 | 113 | # build extension (using CFLAGS_SL for gcov) 114 | if [ $CHECK_TYPE == "valgrind" ]; then 115 | make USE_PGXS=1 USE_ASSERT_CHECKING=1 PG_CONFIG=$config_path 116 | make install USE_PGXS=1 PG_CONFIG=$config_path 117 | else 118 | make USE_PGXS=1 USE_ASSERT_CHECKING=1 CC=$CC PG_CONFIG=$config_path CFLAGS_SL="$($config_path --cflags_sl) -coverage" 119 | sudo make install USE_PGXS=1 PG_CONFIG=$config_path 120 | fi 121 | 122 | echo -en 'travis_fold:end:build_extension\\r' 123 | 124 | echo -en 'travis_fold:start:run_tests\\r' && echo 'Run tests' 125 | 126 | # enable core dumps and specify their path 127 | ulimit -c unlimited -S 128 | echo '/tmp/%e-%s-%p.core' | sudo tee /proc/sys/kernel/core_pattern 129 | 130 | # set permission to write postgres locks 131 | sudo chown $USER /var/run/postgresql/ 132 | 133 | # create cluster 'test' 134 | CLUSTER_PATH=$(pwd)/test_cluster 135 | $initdb_path -D $CLUSTER_PATH -U $USER -A trust 136 | 137 | # start cluster 'test' 138 | echo "port = 55435" >> $CLUSTER_PATH/postgresql.conf 139 | if [ $CHECK_TYPE = "valgrind" ]; then 140 | PGCTLTIMEOUT=600 \ 141 | valgrind --leak-check=no --gen-suppressions=all \ 142 | --suppressions=$CUSTOM_PG_SRC/src/tools/valgrind.supp --time-stamp=yes \ 143 | --log-file=/tmp/pid-%p.log --trace-children=yes \ 144 | $pg_ctl_path -D $CLUSTER_PATH start -l postgres.log -w 145 | else 146 | $pg_ctl_path -D $CLUSTER_PATH start -l postgres.log -w 147 | fi 148 | 149 | # run regression tests 150 | PGPORT=55435 PGUSER=$USER PG_CONFIG=$config_path make installcheck USE_PGXS=1 || status=$? 151 | 152 | # stop cluster 153 | $pg_ctl_path -D $CLUSTER_PATH stop -l postgres.log -w 154 | 155 | echo -en 'travis_fold:end:run_tests\\r' 156 | 157 | echo -en 'travis_fold:start:output\\r' && echo 'Check output' 158 | 159 | # show diff if it exists 160 | if test -f regression.diffs; then cat regression.diffs; fi 161 | 162 | # show valgrind logs if needed 163 | if [ $CHECK_TYPE = "valgrind" ]; then 164 | for f in ` find /tmp -name pid-*.log ` ; do 165 | if grep -q 'Command: [^ ]*/postgres' $f && grep -q 'ERROR SUMMARY: [1-9]' $f; then 166 | echo "========= Contents of $f" 167 | cat $f 168 | status=1 169 | fi 170 | done 171 | fi 172 | 173 | # check core dumps if any 174 | for corefile in $(find /tmp/ -name '*.core' 2>/dev/null) ; do 175 | binary=$(gdb -quiet -core $corefile -batch -ex 'info auxv' | grep AT_EXECFN | perl -pe "s/^.*\"(.*)\"\$/\$1/g") 176 | echo dumping $corefile for $binary 177 | gdb --batch --quiet -ex "thread apply all bt full" -ex "quit" $binary $corefile 178 | done 179 | 180 | echo -en 'travis_fold:end:output\\r' 181 | 182 | echo -en 'travis_fold:start:coverage\\r' && echo 'Coverage check' 183 | 184 | #generate *.gcov files 185 | if [ $CC = "clang" ]; then 186 | bash <(curl -s https://codecov.io/bash) -x "llvm-cov gcov" 187 | else 188 | bash <(curl -s https://codecov.io/bash) 189 | fi 190 | 191 | echo -en 'travis_fold:end:coverage\\r' 192 | 193 | exit $status 194 | -------------------------------------------------------------------------------- /travis/postgresql.gpg.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PUBLIC KEY BLOCK----- 2 | 3 | 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 76 | =DA1T 77 | -----END PGP PUBLIC KEY BLOCK----- 78 | --------------------------------------------------------------------------------