Skip to content

Commit ea52a24

Browse files
authored
JSON + JSONB support, -> and ->> pushing down for JSON data (#109)
* JSON+JSONB operators -> and ->> pushing down implementation * Add types/json test case set showing -> and ->> behaviour for both JSON and JSONB for both SELECT and WHERE usage contexts * Add/restore JSONB missing support in existed code infrastructure * Full SQLite jsonb support.
1 parent 0c46de4 commit ea52a24

File tree

43 files changed

+23861
-1624
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+23861
-1624
lines changed

.github/workflows/CI.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ jobs:
2424
test:
2525
needs: detect-pgversion
2626
env:
27-
SQLITE_VERSION : "3460000"
28-
SQLITE_YEAR: "2024"
27+
SQLITE_VERSION : "3490000"
28+
SQLITE_YEAR: "2025"
2929
POSTGIS_VERSION : "3.4.2"
3030
HTTP_PROXY: ""
3131
HTTPS_PROXY: ""

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ $(info There is NO PostGIS support for SQLite FDW)
2727
endif
2828

2929
# Tests for PostgreSQL data types support
30-
DATA_TYPE_TESTS = types/bitstring types/bool types/float4 types/float8 types/int4 types/int8 types/numeric types/macaddr types/macaddr8 types/out_of_range types/timestamp types/uuid
30+
DATA_TYPE_TESTS = types/bitstring types/bool types/float4 types/float8 types/int4 types/int8 types/json types/numeric types/macaddr types/macaddr8 types/out_of_range types/timestamp types/uuid
3131
# Tests with different versions with GIS support and without GIS support
3232
GIS_DEP_TESTS = $(GIS_DEP_TESTS_DIR)/type $(GIS_DEP_TESTS_DIR)/auto_import $(GIS_DEP_TESTS_DIR)/$(GIS_TEST)
3333

@@ -36,7 +36,7 @@ ifndef REGRESS
3636
REGRESS = libsqlite extra/sqlite_fdw_post $(DATA_TYPE_TESTS) extra/join extra/limit extra/aggregates extra/prepare extra/select_having extra/select extra/insert extra/update extra/encodings sqlite_fdw aggregate selectfunc $(GIS_DEP_TESTS)
3737
endif
3838

39-
# Other encodings also are tested. Client encoding should be UTF-8-
39+
# Other encodings also are tested. Client encoding should be UTF-8.
4040
REGRESS_OPTS = --encoding=utf8
4141

4242
UNAME = uname

README.md

Lines changed: 56 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ SQLite Foreign Data Wrapper for PostgreSQL
22
==========================================
33

44
This is a foreign data wrapper (FDW) to connect [PostgreSQL](https://www.postgresql.org/)
5-
to [SQLite](https://sqlite.org/) database file. This FDW works with PostgreSQL 13, 14, 15, 16, 17 and confirmed with SQLite 3.46.0.
5+
to [SQLite](https://sqlite.org/) database file. This FDW works with PostgreSQL 13, 14, 15, 16, 17 and confirmed with SQLite 3.49.0.
66

7-
<img src="https://upload.wikimedia.org/wikipedia/commons/2/29/Postgresql_elephant.svg" align="center" height="100" alt="PostgreSQL"/> + <img src="https://upload.wikimedia.org/wikipedia/commons/3/38/SQLite370.svg" align="center" height="100" alt="SQLite"/>
7+
<img src="https://upload.wikimedia.org/wikipedia/commons/2/29/Postgresql_elephant.svg" align="center" height="100" alt="PostgreSQL"/> + <img src="SQLite.png" align="center" height="100" alt="SQLite"/>
88

99
Also this foreign data wrapper (FDW) can connect PostgreSQL with [PostGIS](https://www.postgis.net/)
1010
to [SpatiaLite](https://www.gaia-gis.it/fossil/libspatialite/index) SQLite database file. This FDW works with PostGIS 2+ and confirmed with SpatiaLite 5.1. See [GIS support description](GIS.md).
@@ -48,7 +48,8 @@ Features
4848
- `bool`: `text`(1..5) and `int`,
4949
- `double precision`, `float` and `numeric`: `real` values and special values with `text` affinity (`+Infinity`, `-Infinity`, `NaN`),
5050
- `macaddr`: `text`(12..17) or `blob`(6) or `integer`,
51-
- `macaddr8`: `text`(16..23) or `blob`(8) or `integer`.
51+
- `macaddr8`: `text`(16..23) or `blob`(8) or `integer`,
52+
- `json`: `text`(default) or `blob` as SQLite `jsonb` object.
5253
- Support mixed SQLite [data affinity](https://www.sqlite.org/datatype3.html) output (`INSERT`/`UPDATE`) for such data types as
5354
- `timestamp`: `text`(default) or `int`,
5455
- `uuid`: `text`(36) or `blob`(16)(default),
@@ -66,12 +67,13 @@ Features
6667
- `LIMIT` and `OFFSET` are pushdowned when all tables in the query are foreign tables belongs to the same PostgreSQL `FOREIGN SERVER` object.
6768
- Support `GROUP BY`, `HAVING` push-down.
6869
- `mod()` is pushdowned. In PostgreSQL this function gives [argument-dependend data type](https://www.postgresql.org/docs/current/functions-math.html), but result from SQLite always [have `real` affinity](https://www.sqlite.org/lang_mathfunc.html#mod).
69-
- `=` operator for GIS data objects are pushdowned.
70+
- `=` operator for GIS data objects is pushed down.
71+
- Operators `->` and `->>` for `json` and `jsonb` are pushed down in `WHERE` clause.
7072
- `upper`, `lower` and other character case functions are **not** pushed down because they does not work with UNICODE character in SQLite.
7173
- `WITH TIES` option is **not** pushed down.
7274
- Bit string `#` (XOR) operator is **not** pushed down because there is no equal SQLite operator.
7375
- Operations with `macaddr` or `macaddr8` data are **not** pushed down.
74-
- Operators for GIS data objects are **not** pushdowned except for `=`.
76+
- GIS data oparators are **not** pushdowned except for `=`.
7577

7678
### Notes about pushing down
7779

@@ -86,6 +88,8 @@ Features
8688
- `sqlite_fdw` can return implementation-dependent order for column if the column is not specified in `ORDER BY` clause.
8789
- When the column type is `varchar array`, if the string is shorter than the declared length, values of type character will be space-padded; values of type `character varying` will simply store the shorter string.
8890
- [String literals for `boolean`](https://www.postgresql.org/docs/current/datatype-boolean.html) (`t`, `f`, `y`, `n`, `yes`, `no`, `on`, `off` etc. case insensitive) can be readed and filtred but cannot writed, because SQLite documentation recommends only `int` affinity values (`0` or `1`) for boolean data and usually text boolean data belongs to legacy datasets.
91+
- Directry for SQLite foreign table you can use SQLite specific extractor operand for `->` or `->>` like `$.a.d[1]` in `WHERE` clause, but PostgreSQL will calculate result of equal expression in `SELECT` clause as `NULL`.
92+
- If you will use unsupported by `sqlite_fdw` older SQLite versions from your OS, please note SQLite JSON processnig behaviour was unstable between 3.45.0 and 3.48.0 especially for negative array indexes. Please note this for explaining any unexpected results after `->` or `->>` operators or failed tests on your OS.
8993

9094
Also see [Limitations](#limitations)
9195

@@ -124,11 +128,11 @@ For Debian or Ubuntu:
124128

125129
`apt-get install libspatialite-dev` - for SpatiaLite ↔ PostGIS transformations
126130

127-
Instead of `libsqlite3-dev` you can also [download SQLite source code][1] and [build SQLite][2] with FTS5 for full-text search.
131+
Instead of system `libsqlite3-dev` from OS repository you can also [download SQLite source code][1] and [build separate SQLite version][2] with FTS5 for full-text search. The directory of this not OS SQLite library can be pointed as prefix in a command like `./configure --enable-fts5 --prefix=$SQLITE_FOR_TESTING_DIR` before `make` and `make install`.
128132

129133
#### 2. Build and install sqlite_fdw
130134

131-
`sqlite_fdw` does not require to be compiled with PostGIS and `libspatialite-dev`. They are used only for full test which includes test for GIS support.
135+
`sqlite_fdw` does not require to be compiled with PostGIS and `libspatialite-dev`. They are used only for full tests which includes test for GIS support.
132136

133137
Before building please add a directory of `pg_config` to PATH or ensure `pg_config` program is accessible from command line only by the name.
134138

@@ -138,12 +142,21 @@ make USE_PGXS=1
138142
make install USE_PGXS=1
139143
```
140144

145+
Build and install without GIS support against separate compiled and installed SQLite version placed at given path.
146+
Example for `/opt/testing/other/SQLite/3.49.0`.
147+
```sh
148+
make USE_PGXS=1 SQLITE_FOR_TESTING_DIR=/opt/testing/other/SQLite/3.49.0
149+
make install USE_PGXS=1 SQLITE_FOR_TESTING_DIR=/opt/testing/other/SQLite/3.49.0
150+
```
151+
141152
Build and install with GIS support
142153
```sh
143154
make USE_PGXS=1 ENABLE_GIS=1
144155
make install USE_PGXS=1 ENABLE_GIS=1
145156
```
146157

158+
Also you can build against separate SQLite version and with GIS support using obvious combination of variables.
159+
147160
If you want to build `sqlite_fdw` in a source tree of PostgreSQL, use
148161
```sh
149162
make
@@ -233,7 +246,7 @@ This table represents `sqlite_fdw` behaviour if in PostgreSQL foreign table colu
233246
* **** - no support (runtime error)
234247
* **** - 1↔1, PostgreSQL datatype is equal to SQLite affinity
235248
* **✔-** - PostgreSQL datatype is equal to SQLite affinity, but possible out of range error
236-
* **V** - transparent transformation if possible
249+
* **V** - transparent transformation
237250
* **V+** - transparent transformation if possible
238251
* **i** - ISO:SQL transformation for some special constants
239252
* **?** - not described/not tested
@@ -248,7 +261,7 @@ SQLite `NULL` affinity always can be transparent converted for a nullable column
248261
| bool | V || T | i || INT |
249262
| bit(n) | V<br>(n<=64) ||||| INT |
250263
| bytea |||| V | ? | BLOB |
251-
| char(n) | ? | ? | T | | V | TEXT |
264+
| char(n) | ? | ? | T |- | V | TEXT |
252265
| date | V | V | T | V+ | `NULL` | ? |
253266
| float4 | V+ ||| i | `NULL` | REAL |
254267
| float8 | V+ ||| i | `NULL` | REAL |
@@ -257,17 +270,18 @@ SQLite `NULL` affinity always can be transparent converted for a nullable column
257270
| int2 | ✔- | ? ||| `NULL` | INT |
258271
| int4 | ✔- | ? ||| `NULL` | INT |
259272
| int8 || ? ||| `NULL` | INT |
260-
| json | ? | ? | T | V+ | ? | TEXT |
273+
| json ||| V+ | V+ || TEXT |
274+
| jsonb ||| V+ | V+ || BLOB |
261275
| macaddr | ✔- || V<br>(Len=6b)| V+ | ? | INT |
262276
| macaddr8 ||| V<br>(Len=8b)| V+ | ? | INT |
263-
| name | ? | ? | T | i | `NULL` | TEXT |
277+
| name | ? | ? | T | ✔- | `NULL` | TEXT |
264278
| numeric | V | V | T | i | `NULL` | REAL |
265279
| text | ? | ? | T || V | TEXT |
266280
| time | V | V | T | V+ | `NULL` | ? |
267281
| timestamp | V | V | T | V+ | `NULL` | ? |
268282
|timestamp + tz| V | V | T | V+ | `NULL` | ? |
269283
| uuid |||V+<br>(Len=16b)| V+ || TEXT, BLOB |
270-
| varchar(n) | ? | ? | T || V | TEXT |
284+
| varchar(n) | ? | ? | T |- | V | TEXT |
271285
| varbit(n) | V<br>(n<=64) ||||| INT |
272286

273287

@@ -304,6 +318,8 @@ SQLite `NULL` affinity always can be transparent converted for a nullable column
304318
| macaddr8 | macaddr8 |
305319
| [geometry](GIS.md) | geometry |
306320
| [geography](GIS.md) | geography |
321+
| json | json |
322+
| jsonb | jsonb |
307323

308324
**Note:** In case of `sqlite_fdw` compiling without GIS support, GIS data
309325
types will be converted to `bytea`.
@@ -634,6 +650,33 @@ for `INSERT` and `UPDATE` commands. PostgreSQL supports both `blob` and `text` [
634650
- `sqlite_fdw` PostgreSQL `macaddr`/`macaddr8` values support based on `int` SQLite data affinity, because there is no per bit operations for SQLite `blob` affinity data. For `macaddr` out of range error is possible because this type is 6 bytes length, but SQLite `int` can store value up to 8 bytes.
635651
- `sqlite_fdw` doesn't pushdown any operations with MAC adresses because there is 3 possible affinities for it in SQLite: `integer`, `blob` and `text`.
636652

653+
### JSON support and operators
654+
- Operators `->` and `->>` for `json` and `jsonb` are pushed down. This means if you deal with a foreign table only, you can use SQLite syntax of `->` and `->>` operators which is more rich than PostgreSQL syntax. In PostgreSQL this operators means only 1-leveled extraction after one call, but possible multilevel extraction in one call of the operator in SQLite. You can extract `'{"a": 2, "c": [4, 5, {"f": 7}]}' ->'c' -> 2` with result `{"f":7}` both for PostgreSQL and SQLite tables, but `'{"a": 2, "c": [4, 5, {"f": 7}]}' ->'$.c[2]'` possible only in SQLite and for a foreign table.
655+
- For PostgreSQL numeric argument of `->` and `->>` operators means only coordinate inside of array. In SQLite transformable to number text argument of this operators also can extract array element. PostgreSQL differs `json -> (2::text)` and `json -> 2`, but SQLite not: `json -> '2'`.
656+
- Please note you can turn off processing of normalizing possible SQLite `json` values with `text` affinity for a column with formal SQLite `json` data type as option `column_type` = `text`. This can increase `SELECT` or `ORDER` speed, because there will be no normalize function wrapping, but in this case any query will have unsuccessfully result in case of any value with `blob` affiniy including any possible SQLite `jsonb` value.
657+
```sql
658+
-- a query with normalization - standard ISO:SQL behaviour
659+
EXPLAIN (VERBOSE, COSTS OFF)
660+
SELECT "i", "j", j."j"->'c' res FROM "type_JSON" j;
661+
QUERY PLAN
662+
-------------------------------------------------------------
663+
Foreign Scan on public."type_JSON" j
664+
Output: i, j, (j -> 'c'::text)
665+
SQLite query: SELECT `i`, json(`j`) FROM main."type_JSON"
666+
(3 rows)
667+
668+
-- turn off normalization
669+
ALTER FOREIGN TABLE "type_JSON" ALTER COLUMN j OPTIONS (ADD column_type 'text');
670+
EXPLAIN (VERBOSE, COSTS OFF)
671+
SELECT "i", "j", j."j"->'c' res FROM "type_JSON" j;
672+
QUERY PLAN
673+
-------------------------------------------------------
674+
Foreign Scan on public."type_JSON" j
675+
Output: i, j, (j -> 'c'::text)
676+
SQLite query: SELECT `i`, `j` FROM main."type_JSON"
677+
(3 rows)
678+
```
679+
637680
Tests
638681
-----
639682
Test directory have structure as following:
@@ -671,6 +714,7 @@ The test cases for each version are based on the test of corresponding version o
671714
You can execute test by test.sh directly.
672715
The version of PostgreSQL is detected automatically by $(VERSION) variable in Makefile.
673716
The corresponding sql and expected directory will be used to compare the result. For example, for Postgres 15.0, you can execute "test.sh" directly, and the sql/15.0 and expected/15.0 will be used to compare automatically.
717+
Please don't forget a command like `export SQLITE_FOR_TESTING_DIR=` with the same path as in SQLite's `./configure --prefix` berfore testing if you want to test not against your OS SQLite version, but against separate downloaded, compiled and installed SQLite version.
674718

675719
Test data directory is `/tmp/sqlite_fdw_test`. If you have `/tmp` mounted as `tmpfs` the tests will be up to 800% faster.
676720

SQLite.png

22.5 KB
Loading

0 commit comments

Comments
 (0)