Skip to content

Commit

Permalink
Add mixed affinity UPDATE, unify UUID behaviour (pgspider#95)
Browse files Browse the repository at this point in the history
  • Loading branch information
mkgrgis authored Mar 29, 2024
1 parent 7dd696c commit a272452
Show file tree
Hide file tree
Showing 27 changed files with 2,088 additions and 1,412 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ OBJS = connection.o option.o deparse.o sqlite_query.o sqlite_fdw.o sqlite_data_n
EXTENSION = sqlite_fdw
DATA = sqlite_fdw--1.0.sql sqlite_fdw--1.0--1.1.sql

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
REGRESS = extra/sqlite_fdw_post extra/bitstring extra/bool extra/float4 extra/float8 extra/int4 extra/int8 extra/numeric extra/out_of_range extra/timestamp extra/uuid extra/join extra/limit extra/aggregates extra/prepare extra/select_having extra/select extra/insert extra/update extra/encodings sqlite_fdw type aggregate selectfunc
REGRESS_OPTS = --encoding=utf8

SQLITE_LIB = sqlite3
Expand Down
20 changes: 10 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ Features
- Support Bulk `INSERT` by using `batch_size` option
- Support `INSERT`/`UPDATE` with generated column
- Support `ON CONFLICT DO NOTHING`
- Support mixed SQLite [data affinity](https://www.sqlite.org/datatype3.html) input and filtering (`SELECT`/`WHERE` usage) for such dataypes as
- Support mixed SQLite [data affinity](https://www.sqlite.org/datatype3.html) input and filtering (`SELECT`/`WHERE` usage) for such data types as
- `timestamp`: `text` and `int`,
- `uuid`: `text`(32..39) and `blob`(16),
- `bool`: `text`(1..5) and `int`.
- Support mixed SQLite [data affinity](https://www.sqlite.org/datatype3.html) output (`INSERT`/`UPDATE`) for such dataypes as
- Support mixed SQLite [data affinity](https://www.sqlite.org/datatype3.html) output (`INSERT`/`UPDATE`) for such data types as
- `timestamp`: `text`(default) or `int`,
- `uuid`: `text`(36) or `blob`(16)(default).

Expand All @@ -55,7 +55,7 @@ Features
- `mod()` is pushdowned. In PostgreSQL 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).
- `upper`, `lower` and other character case functions are **not** pushed down because they does not work with UNICODE character in SQLite.
- `WITH TIES` option is **not** pushed down.
- Bit string `#` (XOR) operator is **not** pushed down becasuse there is no equal SQLite operator.
- Bit string `#` (XOR) operator is **not** pushed down because there is no equal SQLite operator.

### Notes about pushdowning

Expand All @@ -69,7 +69,7 @@ Features
- 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.
- `sqlite_fdw` can return implementation-dependent order for column if the column is not specified in `ORDER BY` clause.
- 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.
- [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.
- [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.

Also see [Limitations](#limitations)

Expand Down Expand Up @@ -227,7 +227,7 @@ In OS `sqlite_fdw` works as executed code with permissions of user of PostgreSQL
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`.

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

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

Expand Down Expand Up @@ -300,7 +300,7 @@ sqlite_fdw_version
Identifier case handling
------------------------

PostgreSQL folds identifiers to lower case by default, SQLite is case insensetive by default
PostgreSQL folds identifiers to lower case by default, SQLite is case insensitive by default
and doesn't differ uppercase and lowercase ASCII base latin letters. It's important
to be aware of potential issues with table and column names.

Expand Down Expand Up @@ -533,7 +533,7 @@ Limitations
- For `sum` function of SQLite, output of `sum(bigint)` is `integer` value. If input values are big, the overflow error may occurs on SQLite because it overflow within the range of signed 64bit. For PostgreSQL, it can calculate as over the precision of `bigint`, so overflow does not occur.
- SQLite promises to preserve the 15 most significant digits of a floating point value. The big value which exceed 15 most significant digits may become different value after inserted.
- SQLite does not support `numeric` type as PostgreSQL. Therefore, it does not allow to store numbers with too high precision and scale. Error out of range occurs.
- SQLite does not support special values for IEEE 754-2008 numbers such as `NaN`, `+Infinity` and `-Infinity` in SQL expressions with numeric context. Also SQLite can not store this values with `real` [affinity](https://www.sqlite.org/datatype3.html). In opposite to SQLite, PostgreSQL can store special values in columns belongs to `real` datatype family such as `float` or `double precision` and use arithmetic comparation for this values. In oppose to PostgreSQL, SQLite stores `NaN`, `+Infinity` and `-Infinity` as a text values. Also conditions with special literals (such as ` n < '+Infinity'` or ` m > '-Infinity'` ) isn't numeric conditions in SQLite and gives unexpected result after pushdowning in oppose to internal PostgreSQL calculations. During `INSERT INTO ... SELECT` or in `WHERE` conditions `sqlite_fdw` uses given by PostgreSQL standard case sensetive literals **only** in follow forms: `NaN`, `-Infinity`, `Infinity`, not original strings from `WHERE` condition. *This can caused selecting issues*.
- SQLite does not support special values for IEEE 754-2008 numbers such as `NaN`, `+Infinity` and `-Infinity` in SQL expressions with numeric context. Also SQLite can not store this values with `real` [affinity](https://www.sqlite.org/datatype3.html). In opposite to SQLite, PostgreSQL can store special values in columns belongs to `real` datatype family such as `float` or `double precision` and use arithmetic comparation for this values. In oppose to PostgreSQL, SQLite stores `NaN`, `+Infinity` and `-Infinity` as a text values. Also conditions with special literals (such as ` n < '+Infinity'` or ` m > '-Infinity'` ) isn't numeric conditions in SQLite and gives unexpected result after pushdowning in oppose to internal PostgreSQL calculations. During `INSERT INTO ... SELECT` or in `WHERE` conditions `sqlite_fdw` uses given by PostgreSQL standard case sensitive literals **only** in follow forms: `NaN`, `-Infinity`, `Infinity`, not original strings from `WHERE` condition. *This can caused selecting issues*.

### Boolean values
- `sqlite_fdw` boolean values support exists only for `bool` columns in foreign table. SQLite documentation recommends to store boolean as value with `integer` [affinity](https://www.sqlite.org/datatype3.html). `NULL` isn't converted, 1 converted to `true`, all other `NOT NULL` values converted to `false`. During `SELECT ... WHERE condition_column` condition converted only to `condition_column`.
Expand All @@ -542,17 +542,17 @@ Limitations
### UUID values
- `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.
- Expected affinity of UUID value in SQLite table determined by `column_type` option of the column
for `INSERT` and `UPDATE` commands. In PostgreSQL 14- only `text` data affinity is availlable, PostgreSQL 14+ supports also `blob` data affinity.
for `INSERT` and `UPDATE` commands. PostgreSQL supports both `blob` and `text` [affinity](https://www.sqlite.org/datatype3.html).

### bit and varbit support
- `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.
- `sqlite_fdw` doesn't pushdown `#` (XOR) operator becasuse there is no equal SQLite operator.
- `sqlite_fdw` doesn't pushdown `#` (XOR) operator because there is no equal SQLite operator.

Tests
-----
Test directory have structure as following:

```sql
```
+---sql
| +---12.16
| | filename1.sql
Expand Down
55 changes: 55 additions & 0 deletions deparse.c
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ static void sqlite_get_relation_column_alias_ids(Var *node, RelOptInfo *foreignr
static char *sqlite_quote_identifier(const char *s, char q);
static bool sqlite_contain_immutable_functions_walker(Node *node, void *context);
static bool sqlite_is_valid_type(Oid type);
static int preferred_sqlite_affinity (Oid relid, int varattno);

/*
* Append remote name of specified foreign table to buf.
Expand Down Expand Up @@ -2107,6 +2108,9 @@ sqlite_deparse_column_ref(StringInfo buf, int varno, int varattno, PlannerInfo *
}
}

/*
* Get column option with optionname for a variable attribute in deparsing context
*/
static char *
sqlite_deparse_column_option(int varno, int varattno, PlannerInfo *root, char *optionname)
{
Expand Down Expand Up @@ -2296,6 +2300,36 @@ sqlite_deparse_update(StringInfo buf, PlannerInfo *root,
}
}

/*
* Preferred SQLite affinity from "column_type" foreign column option
* SQLITE_NULL if no value or no normal value
*/
int
preferred_sqlite_affinity (Oid relid, int varattno)
{
char *coltype = NULL;
List *options;
ListCell *lc;

elog(DEBUG4, "sqlite_fdw : %s ", __func__);
if (varattno == 0)
return SQLITE_NULL;

options = GetForeignColumnOptions(relid, varattno);
foreach(lc, options)
{
DefElem *def = (DefElem *) lfirst(lc);

if (strcmp(def->defname, "column_type") == 0)
{
coltype = defGetString(def);
elog(DEBUG4, "column type = %s", coltype);
break;
}
}
return sqlite_affinity_code(coltype);
}

/*
* deparse remote UPDATE statement
*
Expand Down Expand Up @@ -2346,7 +2380,11 @@ sqlite_deparse_direct_update_sql(StringInfo buf, PlannerInfo *root,
forboth(lc, targetlist, lc2, targetAttrs)
{
int attnum = lfirst_int(lc2);
int preferred_affinity = SQLITE_NULL;
TargetEntry *tle;
RangeTblEntry *rte;
bool special_affinity = false;
Oid pg_attyp;
#if (PG_VERSION_NUM >= 140000)
tle = lfirst_node(TargetEntry, lc);

Expand All @@ -2366,8 +2404,25 @@ sqlite_deparse_direct_update_sql(StringInfo buf, PlannerInfo *root,
first = false;

sqlite_deparse_column_ref(buf, rtindex, attnum, root, false, true);

/* Get RangeTblEntry from array in PlannerInfo. */
rte = planner_rt_fetch(rtindex, root);
pg_attyp = get_atttype(rte->relid, attnum);
preferred_affinity = preferred_sqlite_affinity(rte->relid, attnum);

appendStringInfoString(buf, " = ");
if (pg_attyp == UUIDOID && preferred_affinity == SQLITE3_TEXT)
{
appendStringInfo(buf, "sqlite_fdw_uuid_str(");
special_affinity = true;
}
sqlite_deparse_expr((Expr *) tle->expr, &context);

if (special_affinity)
{
elog(DEBUG4, "sqlite_fdw : aff %d\n", preferred_affinity);
appendStringInfoString(buf, ")");
}
}

sqlite_reset_transmission_modes(nestlevel);
Expand Down
Loading

0 comments on commit a272452

Please sign in to comment.