Open-source project
postgrespro/jsquery avatar
postgrespro/jsquery

JsQuery gives PostgreSQL a dedicated language for nested jsonb matching

JsQuery – json query language with GIN indexing support

710 stars51 forksCNOASSERTION

At a glance

What is it?
JsQuery is a PostgreSQL C extension that adds a jsquery datatype, an @@ match operator and a path language with GIN indexing support for jsonb documents. It targets the queries built in jsonb operators cannot express, and it is a standalone extension rather than a core PostgreSQL feature.
Who is it for?
JsQuery fits an engineer who stores semi structured data in jsonb and needs predicates over nested arrays and objects that the built in operators will not carry, and who is willing to compile a C extension and carry that dependency across PostgreSQL upgrades. It does not fit a team that wants a feature available in a managed PostgreSQL service without building extensions, and it is a poor substitute for a proper relational schema when the shape of the data is stable.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 11 days ago.
What is it written in?
Mainly C, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

JsQuery targets the jsonb searches PostgreSQL 9.4 left out

PostgreSQL introduced the jsonb data type in release 9.4, and the JsQuery authors frame their project as filling a gap in that type rather than as a general purpose JSON tool. The stated primary goal is to provide additional functionality to jsonb, currently missing in PostgreSQL, such as a simple and effective way to search in nested objects and arrays, together with more comparison operators that carry index support. The README goes further and says the hope is that jsquery will eventually become part of PostgreSQL. In practice it is a standalone C extension published by Postgres Professional, authored by Teodor Sigaev, Alexander Korotkov and Oleg Bartunov, and it is not part of a default PostgreSQL installation. The audience is narrow: engineers who keep semi structured documents in jsonb and need predicates that the built in operators cannot express over nested arrays and objects.

A query becomes a first class value, like tsquery

JsQuery introduces its own datatype, jsquery, which represents a whole JSON query as a single value in the same way tsquery represents a full text search expression. The query is then applied to a jsonb column with the @@ match operator. That design choice is the interesting part. The query is not a string handed to a function, so it can live in its own column, be indexed, and be reused across many rows, and the syntax borrows from PostgreSQL full text search rather than from the jsonb path operators that shipped with 9.4. A simple expression is a path, a binary operator and a value, or a path with a unary operator. Complex expressions combine simple ones with the logical operators AND, OR and NOT, grouped with braces. The grammar is written in bison and the lexer in flex, which is visible in the source tree as jsquery_gram.y and jsquery_scan.l.

Path placeholders, and an existential default that surprises people

Paths are where the language earns its keep. A path is key names and placeholders joined by dot signs, and the available placeholders are specific. # means any index of an array, #N the N-th index, % any key of an object, and * any sequence of array indexes and object keys. @# is the length of an array or object and may only be used as the last component of a path, while $ is the whole JSON document as a single value and may only be the whole path. Key names can be written with or without double quotes, though an unquoted name may not contain spaces, start with a number, or match a jsquery keyword. The default semantics are existential: an expression is true when the operator holds against at least one value selected by the path. That default catches people out, since the same document can match or fail to match depending only on which placeholder you typed.

Every-semantics placeholders and their rewrites

A second family of placeholders inverts the meaning. #: stands for every index of an array, %: for every key of an object, and *: for every sequence of indexes and keys. The README demonstrates the difference with one assertion written three ways. The first form requires at least one key whose value is an array of numerics between 0 and 1. The second drops the every prefix on the inner placeholder and instead asserts that the value is an array and that all of its elements satisfy the condition. The third drops the every prefix entirely and states that there are no elements failing the condition, while adding an explicit IS ARRAY check. These are not stylistic variants. The final form asserts the absence of counterexamples, which is a different claim from the second, and the README does not discuss how either behaves against an empty array or against a value that is not an array at all.

Building the extension with USE_PGXS and registering it in a database

The README states the prerequisites before any build step: PostgreSQL 9.4 or higher, the PostgreSQL development package installed or PostgreSQL built from source, flex and bison on the system (the project says it was tested on flex 2.5.37-2.5.39 and bison 2.7.12), and a PATH that makes pg_config reachable, or the PG_CONFIG variable set. The typical installation procedure is five commands.

bash
    $ git clone https://github.com/postgrespro/jsquery.git
    $ cd jsquery
    $ make USE_PGXS=1
    $ sudo make USE_PGXS=1 install
    $ make USE_PGXS=1 installcheck
    $ psql DB -c "CREATE EXTENSION jsquery;"

The first two commands fetch the source and enter the directory. The third compiles against a standalone PostgreSQL using the PGXS build path, the fourth installs the shared library and the SQL files into the server directories, and the fifth runs the regression suite. The Makefile sets REGRESS = jsquery with ENCODING = UTF8 and a comment noting that a UTF8 database is needed, so the installcheck step is a real test rather than a smoke test. The last command registers the extension in one database, which is where the jsquery type and the @@ operator become available to that database only.

GIN support lives in a bundled operator class file

The repository root holds jsonb_gin_ops.c, and jsonb_gin_ops.o is the first object listed in the Makefile, which is where the indexing story lives. The README states the goal in terms of more comparison operators with indexes support, and the presence of a dedicated operator class file is how that promise is kept in the code. The build also ships two SQL scripts, jsquery--1.1.sql and jsquery--1.0--1.1.sql, listed in the DATA variable, so installing the extension gives you a base 1.1 script plus an upgrade path from the 1.0 series. The practical effect is that a database which adopts JsQuery gains a different way of indexing jsonb, and a query plan built without the extension in place is not representative of the same query afterwards. What the README does not show is a CREATE INDEX statement naming the operator class, so the exact index definition has to come from the operator class file or the SQL scripts rather than from the documentation.

Commit activity in 2026, but no tagged release since 2018

The release history and the commit history tell different stories. Tagged releases are ver_1.0.0 on 2017-10-28, ver_1.0.1 on 2017-10-28 as a bug fix release, and ver_1.1 on 2018-05-23 as Version 1.1.0. The last push to the repository was on 2026-10-12, so files are still being changed even though no new version has been tagged in years. For an adopter that distinction is the thing to plan around, because a build made from master carries no release tag to pin and a build made from the 1.1 tag is over eight years old. The build system supports both paths: the Makefile works standalone with USE_PGXS and also drops into the contrib tree of a PostgreSQL source checkout, and a meson.build file is present at the top level for newer build setups. On licensing, the README states that JsQuery is available from GitHub under the same license as PostgreSQL, while the LICENSE file itself is a custom licence that GitHub cannot classify, so read that file before redistributing it inside a product.

Editorial conclusion

JsQuery fits an engineer who stores semi structured data in jsonb and needs predicates over nested arrays and objects that the built in operators will not carry, and who is willing to compile a C extension and carry that dependency across PostgreSQL upgrades. It does not fit a team that wants a feature available in a managed PostgreSQL service without building extensions, and it is a poor substitute for a proper relational schema when the shape of the data is stable. Verify three things before adopting it: that your server has PostgreSQL 9.4 or newer with development headers, that flex and bison are available to build the lexer and grammar, and that the query plans for your real workload still use the GIN operator class once the extension is installed. The last push was on 2026-10-12, but the newest tagged release is Version 1.1.0 from 2018-05-23, so the gap between commit activity and released versions is the thing to watch.

Frequently asked questions

How do I install the JsQuery extension in PostgreSQL?

JsQuery is not part of a default PostgreSQL installation. The README describes building it from source with make USE_PGXS=1 and installing it with sudo make USE_PGXS=1 install, which requires PostgreSQL 9.4 or higher, the PostgreSQL development package, flex and bison, and pg_config on the PATH or PG_CONFIG set.

What is the jsquery datatype and what does the @@ operator do?

The extension adds a jsquery datatype that holds an entire JSON query as a single value, in the same way tsquery holds a full text search expression. The @@ operator matches a jsonb document against such a query.

How do I require every element of a JSON array to match a condition in JsQuery?

Use the every-semantics placeholders, #: for every array index, %: for every object key, and *: for every sequence of indexes and keys. The README also shows how an every-element assertion can be rewritten using NOT and an explicit IS ARRAY check.

Which PostgreSQL versions does JsQuery support?

The project states that JsQuery supports PostgreSQL 9.4 and higher, matching the release in which the jsonb data type was introduced.

Is JsQuery part of core PostgreSQL?

No. The README says it is realized as an extension and is not available in a default PostgreSQL installation, and it must be built and then enabled per database with CREATE EXTENSION jsquery.

Official sources

  1. Issues
  2. postgrespro/jsquery on GitHub
  3. README
  4. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/postgrespro-jsquery.svg)](https://hysenlabs.com/projects/postgrespro-jsquery)

Was this useful?