mirror of
https://github.com/simonw/sqlite-utils.git
synced 2026-09-27 20:34:12 +02:00
Fix transform() corrupting TRUE/FALSE/NULL column defaults into strings
* Fix transform() corrupting TRUE/FALSE/NULL column defaults quote_default_value() passed keyword-literal defaults (TRUE, FALSE, NULL) through self.quote(), wrapping them in quotes. So transform() rebuilt a column declared "INTEGER DEFAULT TRUE" as "INTEGER DEFAULT 'TRUE'", and a later default insert stored the text 'TRUE' instead of the integer 1 (likewise 'FALSE' for 0 and 'NULL' for null) - silent value corruption. Return these keyword literals unquoted, as already done for the CURRENT_TIME/DATE/TIMESTAMP literals. PR #764
This commit is contained in:
parent
77d241959c
commit
a4acc3958c
4 changed files with 48 additions and 0 deletions
|
|
@ -42,6 +42,10 @@ Column names passed to Python API methods are now matched against the table sche
|
||||||
- Foreign key columns are validated and recorded using the casing of the actual schema columns, in ``foreign_keys=`` when creating tables, ``db.add_foreign_keys()``, ``table.add_foreign_key()`` and ``table.add_column(fk_col=...)``. Duplicate foreign key detection is also case-insensitive.
|
- Foreign key columns are validated and recorded using the casing of the actual schema columns, in ``foreign_keys=`` when creating tables, ``db.add_foreign_keys()``, ``table.add_foreign_key()`` and ``table.add_column(fk_col=...)``. Duplicate foreign key detection is also case-insensitive.
|
||||||
- ``table.create()`` with ``pk=``, ``not_null=``, ``defaults=`` or ``column_order=`` referencing columns using different casing no longer creates an unwanted extra primary key column or raises a ``ValueError``.
|
- ``table.create()`` with ``pk=``, ``not_null=``, ``defaults=`` or ``column_order=`` referencing columns using different casing no longer creates an unwanted extra primary key column or raises a ``ValueError``.
|
||||||
|
|
||||||
|
Everything else:
|
||||||
|
|
||||||
|
- Fixed a bug where ``table.transform()`` could convert ``DEFAULT TRUE``, ``DEFAULT FALSE`` and ``DEFAULT NULL`` column defaults into quoted string defaults when rebuilding a table. Thanks, `Vincent Gao <https://github.com/gaoflow>`__. (`#764 <https://github.com/simonw/sqlite-utils/pull/764>`__)
|
||||||
|
|
||||||
.. _v4_0rc2:
|
.. _v4_0rc2:
|
||||||
|
|
||||||
4.0rc2 (2026-07-04)
|
4.0rc2 (2026-07-04)
|
||||||
|
|
|
||||||
|
|
@ -983,6 +983,11 @@ class Database:
|
||||||
if str(value).upper() in ("CURRENT_TIME", "CURRENT_DATE", "CURRENT_TIMESTAMP"):
|
if str(value).upper() in ("CURRENT_TIME", "CURRENT_DATE", "CURRENT_TIMESTAMP"):
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
if isinstance(value, str) and value.upper() in ("TRUE", "FALSE", "NULL"):
|
||||||
|
# Keyword literals must stay unquoted; quoting them would turn the
|
||||||
|
# default into a string ('TRUE' instead of 1, 'NULL' instead of null).
|
||||||
|
return value
|
||||||
|
|
||||||
if str(value).endswith(")"):
|
if str(value).endswith(")"):
|
||||||
# Expr
|
# Expr
|
||||||
return "({})".format(value)
|
return "({})".format(value)
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,11 @@ EXAMPLES = [
|
||||||
# Strings
|
# Strings
|
||||||
("TEXT DEFAULT 'CURRENT_TIMESTAMP'", "'CURRENT_TIMESTAMP'", "'CURRENT_TIMESTAMP'"),
|
("TEXT DEFAULT 'CURRENT_TIMESTAMP'", "'CURRENT_TIMESTAMP'", "'CURRENT_TIMESTAMP'"),
|
||||||
('TEXT DEFAULT "CURRENT_TIMESTAMP"', '"CURRENT_TIMESTAMP"', '"CURRENT_TIMESTAMP"'),
|
('TEXT DEFAULT "CURRENT_TIMESTAMP"', '"CURRENT_TIMESTAMP"', '"CURRENT_TIMESTAMP"'),
|
||||||
|
# Boolean and null keyword literals must stay unquoted
|
||||||
|
("INTEGER DEFAULT TRUE", "TRUE", "TRUE"),
|
||||||
|
("INTEGER DEFAULT FALSE", "FALSE", "FALSE"),
|
||||||
|
("INTEGER DEFAULT true", "true", "true"),
|
||||||
|
("TEXT DEFAULT NULL", "NULL", "NULL"),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -224,6 +224,40 @@ def test_transform_rename_pk(fresh_db):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_transform_preserves_keyword_literal_defaults(fresh_db):
|
||||||
|
# transform() used to requote keyword-literal defaults (DEFAULT TRUE became
|
||||||
|
# DEFAULT 'TRUE'), so a default insert stored the text 'TRUE' instead of the
|
||||||
|
# integer 1 -- silent value corruption on every rebuilt table.
|
||||||
|
fresh_db.execute(
|
||||||
|
"CREATE TABLE t ("
|
||||||
|
" id INTEGER PRIMARY KEY,"
|
||||||
|
" is_active INTEGER DEFAULT TRUE,"
|
||||||
|
" flag INTEGER DEFAULT FALSE,"
|
||||||
|
" note TEXT DEFAULT NULL"
|
||||||
|
")"
|
||||||
|
)
|
||||||
|
table = fresh_db["t"]
|
||||||
|
table.insert({"id": 1})
|
||||||
|
before = fresh_db.execute("SELECT is_active, flag, note FROM t").fetchone()
|
||||||
|
assert before == (1, 0, None)
|
||||||
|
|
||||||
|
# Rebuild the table via an unrelated change.
|
||||||
|
table.transform(rename={"note": "note2"})
|
||||||
|
|
||||||
|
# The keyword literals stay unquoted in the schema ...
|
||||||
|
assert "DEFAULT TRUE" in table.schema
|
||||||
|
assert "DEFAULT FALSE" in table.schema
|
||||||
|
assert "DEFAULT NULL" in table.schema
|
||||||
|
assert "'TRUE'" not in table.schema
|
||||||
|
|
||||||
|
# ... and a fresh default insert still yields 1 / 0 / NULL, not strings.
|
||||||
|
table.insert({"id": 2})
|
||||||
|
after = fresh_db.execute(
|
||||||
|
"SELECT is_active, flag, note2 FROM t WHERE id = 2"
|
||||||
|
).fetchone()
|
||||||
|
assert after == (1, 0, None)
|
||||||
|
|
||||||
|
|
||||||
def test_transform_not_null(fresh_db):
|
def test_transform_not_null(fresh_db):
|
||||||
dogs = fresh_db["dogs"]
|
dogs = fresh_db["dogs"]
|
||||||
dogs.insert({"id": 1, "name": "Cleo", "age": "5"}, pk="id")
|
dogs.insert({"id": 1, "name": "Cleo", "age": "5"}, pk="id")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue