Skip to content

Commit a387b2f

Browse files
authored
Bool mixed affinity support (#88)
1 parent 78406b9 commit a387b2f

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

+11712
-1949
lines changed

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@
1010
##########################################################################
1111

1212
MODULE_big = sqlite_fdw
13-
OBJS = connection.o option.o deparse.o sqlite_query.o sqlite_fdw.o uuid_extension.o
13+
OBJS = connection.o option.o deparse.o sqlite_query.o sqlite_fdw.o sqlite_data_norm.o
1414

1515
EXTENSION = sqlite_fdw
1616
DATA = sqlite_fdw--1.0.sql sqlite_fdw--1.0--1.1.sql
1717

18-
REGRESS = extra/sqlite_fdw_post extra/float4 extra/float8 extra/int4 extra/int8 extra/numeric extra/join extra/limit extra/aggregates extra/prepare extra/select_having extra/select extra/insert extra/update extra/timestamp extra/encodings sqlite_fdw type aggregate selectfunc
18+
REGRESS = extra/sqlite_fdw_post extra/float4 extra/float8 extra/int4 extra/int8 extra/numeric extra/join extra/limit extra/aggregates extra/prepare extra/select_having extra/select extra/insert extra/update extra/timestamp extra/encodings extra/bool extra/uuid sqlite_fdw type aggregate selectfunc
1919
REGRESS_OPTS = --encoding=utf8
2020

2121
SQLITE_LIB = sqlite3

README.md

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,14 @@ Features
3535
- Support discard cached connections to foreign servers by using function `sqlite_fdw_disconnect()`, `sqlite_fdw_disconnect_all()`.
3636
- Support Bulk `INSERT` by using `batch_size` option
3737
- Support `INSERT`/`UPDATE` with generated column
38-
- Support `ON CONFLICT DO NOTHING`.
38+
- Support `ON CONFLICT DO NOTHING`
39+
- Support mixed SQLite [data affinity](https://www.sqlite.org/datatype3.html) input and filtering (`SELECT`/`WHERE` usage) for such dataypes as
40+
- `timestamp`: `text` and `int`,
41+
- `uuid`: `text`(32..39) and `blob`(16),
42+
- `bool`: `text`(1..5) and `int`.
43+
- Support mixed SQLite [data affinity](https://www.sqlite.org/datatype3.html) output (`INSERT`/`UPDATE`) for such dataypes as
44+
- `timestamp`: `text`(default) or `int`,
45+
- `uuid`: `text`(36) or `blob`(16)(default).
3946

4047
### Pushdowning
4148
- `WHERE` clauses are pushdowned
@@ -62,6 +69,7 @@ Features
6269
- For `numeric` data type, `sqlite_fdw` use `sqlite3_column_double` to get value, while SQLite shell uses `sqlite3_column_text` to get value. Those 2 APIs may return different numeric value. Therefore, for `numeric` data type, the value returned from `sqlite_fdw` may different from the value returned from SQLite shell.
6370
- `sqlite_fdw` can return implementation-dependent order for column if the column is not specified in `ORDER BY` clause.
6471
- 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.
72+
- [String literals for `boolean`](https://www.postgresql.org/docs/current/datatype-boolean.html) (`t`, `f`, `y`, `n`, `yes`, `no`, `on`, `off` etc. case insensetive) 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.
6573

6674
Also see [Limitations](#limitations)
6775

@@ -122,7 +130,6 @@ This table represents `sqlite_fdw` behaviour if in PostgreSQL foreign table colu
122130

123131
* **** - no support (runtime error)
124132
* **V** - transparent transformation
125-
* **b** - show per-bit form
126133
* **T** - cast to text in SQLite utf-8 encoding, then to **PostgreSQL text with current encoding of database** and then transparent transformation if applicable
127134
* **** - transparent transformation where PostgreSQL datatype is equal to SQLite affinity
128135
* **V+** - transparent transformation if appliacable
@@ -133,14 +140,14 @@ SQLite `NULL` affinity always can be transparent converted for a nullable column
133140

134141
| PostgreSQL | SQLite <br> INT | SQLite <br> REAL | SQLite <br> BLOB | SQLite <br> TEXT | SQLite <br> TEXT but <br>empty|SQLite<br>nearest<br>affinity|
135142
|-------------:|:------------:|:------------:|:------------:|:------------:|:------------:|-------------:|
136-
| bool | V | ? | T | - || INT |
137-
| bit(n) | V n<=64 || V | ? || INT |
138-
| bytea | b | b || - | ? | BLOB |
143+
| bool | V | | T | V+ || INT |
144+
| bit(n) | V n<=64 || | || INT |
145+
| bytea | | || - | ? | BLOB |
139146
| date | V | V | T | V+ | `NULL` | ? |
140147
| float4 | V+ || T | - | `NULL` | REAL |
141148
| float8 | V+ || T | - | `NULL` | REAL |
142-
| int2 | | ? | T | - | `NULL` | INT |
143-
| int4 | | ? | T | - | `NULL` | INT |
149+
| int2 | V+ | ? | T | - | `NULL` | INT |
150+
| int4 | V+ | ? | T | - | `NULL` | INT |
144151
| int8 || ? | T | - | `NULL` | INT |
145152
| json | ? | ? | T | V+ | ? | TEXT |
146153
| name | ? | ? | T | V | `NULL` | TEXT |
@@ -151,7 +158,7 @@ SQLite `NULL` affinity always can be transparent converted for a nullable column
151158
|timestamp + tz| V | V | T | V+ | `NULL` | ? |
152159
| uuid |||V+<br>(only<br>16 bytes)| V+ || TEXT, BLOB |
153160
| varchar | ? | ? | T || V | TEXT |
154-
| varbit(n) | V n<=64 || V | ? || INT |
161+
| varbit(n) | V n<=64 || V | || INT |
155162

156163
### CREATE SERVER options
157164

@@ -216,10 +223,11 @@ In OS `sqlite_fdw` works as executed code with permissions of user of PostgreSQL
216223

217224
- **column_type** as *string*, optional, no default
218225

219-
Gives preferred SQLite affinity for some PostgreSQL data types can be stored in different ways in SQLite. Default preferred SQLite affinity for this types is `text`.
226+
Set preferred SQLite affinity for some PostgreSQL data types can be stored in different ways
227+
in SQLite (mixed affinity case). Updated and inserted values will have this affinity. Default preferred SQLite affinity for `timestamp` and `uuid` PostgreSQL data types is `text`.
220228

221229
- Use `INT` value for SQLite column (epoch Unix Time) to be treated/visualized as `timestamp` in PostgreSQL.
222-
- Use `BLOB` value for SQLite column to be treated/visualized as `uuid` in PostgreSQL.
230+
- Use `BLOB` value for SQLite column to be treated/visualized as `uuid` in PostgreSQL 14+.
223231

224232
- **key** as *boolean*, optional, default *false*
225233

@@ -305,7 +313,7 @@ Following SQL isn't correct for SQLite: `Error: duplicate column name: a`, but i
305313
);
306314
```
307315
Following SQLs is correct for both SQLite and PostgreSQL because there is no column
308-
names with ASCII base latin letters *only*.
316+
with names composed from ASCII base latin letters *only*.
309317

310318
```sql
311319
CREATE TABLE T_кир (
@@ -331,6 +339,19 @@ For SQLite there is no difference between
331339
SELECT * FROM "T"; -- №4
332340
```
333341
For PostgreSQL the query with comment `№4` is independend query to table `T`, not to table `t` as other queries.
342+
Please note this table name composed from ASCII base latin letters *only*. This is not applicable for other
343+
alphabet systems or mixed names. This is because `toLower` operation in PostgreSQL is Unicode opration but
344+
ASCII only operation in SQLite, hence other characters will not be changed.
345+
346+
```sql
347+
SELECT * FROM т; -- №5
348+
SELECT * FROM Т; -- №6
349+
SELECT * FROM "т"; -- №7
350+
SELECT * FROM "Т"; -- №8
351+
```
352+
In this case for PostgreSQL the query with comment `№8` is independend query to table `Т`, not to table `т`
353+
as other queries. But for SQLite the queries with comments `№6` and `№8` belongs to table `Т`, and the queries with
354+
comments `№5` and `№7` belongs to table `т`.
334355

335356
If there is
336357

@@ -521,7 +542,7 @@ Limitations
521542
### UUID values
522543
- `sqlite_fdw` UUID values support exists only for `uuid` columns in foreign table. SQLite documentation recommends to store UUID as value with both `blob` and `text` [affinity](https://www.sqlite.org/datatype3.html). `sqlite_fdw` can pushdown both reading and filtering both `text` and `blob` values.
523544
- Expected affinity of UUID value in SQLite table determined by `column_type` option of the column
524-
for `INSERT` and `UPDATE` commands.
545+
for `INSERT` and `UPDATE` commands. In PostgreSQL 14- only `text` data affinity is availlable, PostgreSQL 14+ supports also `blob` data affinity.
525546

526547
### bit and varbit support
527548
- `sqlite_fdw` PostgreSQL `bit`/`varbit` values support based on `int` SQLite data affinity, because there is no per bit operations for SQLite `blob` affinity data. Maximum SQLite `int` affinity value is 8 bytes length, hence maximum `bit`/`varbit` values length is 64 bits.
@@ -615,9 +636,8 @@ Useful links
615636
616637
License
617638
-------
618-
619-
Copyright (c) 2018, TOSHIBA CORPORATION
620-
Copyright (c) 2011 - 2016, EnterpriseDB Corporation
639+
* Copyright © 2018, TOSHIBA CORPORATION
640+
* Copyright © 2011 - 2016, EnterpriseDB Corporation
621641
622642
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.
623643

connection.c

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -220,17 +220,7 @@ sqlite_open_db(const char *dbpath)
220220
/* add included inner SQLite functions from separate c file
221221
* for using in data unifying during deparsing
222222
*/
223-
rc = sqlite_fdw_data_norm_functs_init(conn);
224-
if (rc != SQLITE_OK)
225-
{
226-
char *perr = pstrdup(err);
227-
228-
sqlite3_free(err);
229-
sqlite3_close(conn);
230-
ereport(ERROR,
231-
(errcode(ERRCODE_FDW_UNABLE_TO_ESTABLISH_CONNECTION),
232-
errmsg("failed to create UUID support function for SQLite DB. rc=%d err=%s", rc, perr)));
233-
}
223+
sqlite_fdw_data_norm_functs_init(conn);
234224
return conn;
235225
}
236226

deparse.c

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -613,7 +613,7 @@ sqlite_foreign_expr_walker(Node *node,
613613
|| strcmp(opername, "round") == 0
614614
|| strcmp(opername, "rtrim") == 0
615615
|| strcmp(opername, "substr") == 0
616-
|| strcmp(opername, "mod") == 0))
616+
|| strcmp(opername, "mod") == 0 ))
617617
{
618618
return false;
619619
}
@@ -622,7 +622,6 @@ sqlite_foreign_expr_walker(Node *node,
622622
glob_cxt, &inner_cxt, case_arg_cxt))
623623
return false;
624624

625-
626625
/*
627626
* If function's input collation is not derived from a foreign
628627
* Var, it can't be sent to remote.
@@ -675,7 +674,7 @@ sqlite_foreign_expr_walker(Node *node,
675674
ReleaseSysCache(tuple);
676675

677676
/*
678-
* Factorial (!) and Bitwise XOR (^), (#)
677+
* Factorial (!) and Bitwise XOR (^), (#)
679678
* cannot be pushed down to SQLite
680679
* Full list see in https://www.postgresql.org/docs/current/functions-bitstring.html
681680
* ILIKE cannot be pushed down to SQLite
@@ -2082,29 +2081,33 @@ sqlite_deparse_column_ref(StringInfo buf, int varno, int varattno, PlannerInfo *
20822081
colname = get_attname(rte->relid, varattno);
20832082
#endif
20842083
pg_atttyp = get_atttype(rte->relid, varattno);
2085-
2084+
20862085
/* PostgreSQL data types with possible mixed affinity SQLite base we should
20872086
* normalize to preferred form in SQLite before transfer to PostgreSQL.
20882087
* Recommended form for normalisation is someone from 1<->1 with PostgreSQL
20892088
* internal storage, hence usually this will not original text data.
20902089
*/
2091-
if (pg_atttyp == UUIDOID && !dml_context )
2090+
if (!dml_context && pg_atttyp == BOOLOID)
20922091
{
2093-
elog(DEBUG2, "UUID unification for \"%s\"", colname);
2094-
/* Please remove to UNHEX and deattach uuid_extension.c after SQLite 3.41+ support */
2095-
appendStringInfoString(buf, "coalesce(sqlite_fdw_uuid_blob(");
2092+
elog(DEBUG2, "boolean unification for \"%s\"", colname);
2093+
appendStringInfoString(buf, "sqlite_fdw_bool(");
20962094
if (qualify_col)
20972095
ADD_REL_QUALIFIER(buf, varno);
20982096
appendStringInfoString(buf, sqlite_quote_identifier(colname, '`'));
2099-
appendStringInfoString(buf, "),");
2097+
appendStringInfoString(buf, ")");
2098+
}
2099+
else if (!dml_context && pg_atttyp == UUIDOID)
2100+
{
2101+
elog(DEBUG2, "UUID unification for \"%s\"", colname);
2102+
appendStringInfoString(buf, "sqlite_fdw_uuid_blob(");
21002103
if (qualify_col)
21012104
ADD_REL_QUALIFIER(buf, varno);
21022105
appendStringInfoString(buf, sqlite_quote_identifier(colname, '`'));
21032106
appendStringInfoString(buf, ")");
21042107
}
2105-
else
2108+
else
21062109
{
2107-
elog(DEBUG3, "column name without data unification = \"%s\"", colname);
2110+
elog(DEBUG4, "column name without data unification = \"%s\"", colname);
21082111
if (qualify_col)
21092112
ADD_REL_QUALIFIER(buf, varno);
21102113
appendStringInfoString(buf, sqlite_quote_identifier(colname, '`'));
@@ -2443,7 +2446,7 @@ sqlite_deparse_direct_delete_sql(StringInfo buf, PlannerInfo *root,
24432446
List **retrieved_attrs)
24442447
{
24452448
deparse_expr_cxt context;
2446-
2449+
24472450
elog(DEBUG1, "sqlite_fdw : %s", __func__);
24482451

24492452
/* Set up context struct for recursion */
@@ -2653,7 +2656,7 @@ sqlite_deparse_const(Const *node, deparse_expr_cxt *context, int showtype)
26532656
appendStringInfo(buf, "X\'%s\'", extval + 2);
26542657
break;
26552658
case TIMESTAMPOID:
2656-
{
2659+
{
26572660
convert_timestamp_tounixepoch = false;
26582661
extval = OidOutputFunctionCall(typoutput, node->constvalue);
26592662

@@ -2675,8 +2678,8 @@ sqlite_deparse_const(Const *node, deparse_expr_cxt *context, int showtype)
26752678
sqlite_deparse_string_literal(buf, extval);
26762679
}
26772680
break;
2678-
case UUIDOID:
2679-
/* always deparse to BLOB because this is internal PostgreSQL storage
2681+
case UUIDOID:
2682+
/* always deparse to BLOB because this is internal PostgreSQL storage
26802683
* the string for BYTEA always seems to be in the format "\\x##"
26812684
* where # is a hex digit, Even if the value passed in is
26822685
* 'hi'::bytea we will receive "\x6869". Making this assumption

0 commit comments

Comments
 (0)