Corrige LE vrai bug : un nom de champ mot-réservé SQL (ex. "order") faisait disparaître l'objet entier
Reproduit à l'identique le cas signalé (objet "dialog" avec les champs
order/spiker/text/level_id/parcour_id) : le champ "order" est un mot
réservé SQL — "CREATE TABLE dialog (order INTEGER, ...)" plante avec
"OperationalError: near \"order\": syntax error". Comme ce crash survient
APRÈS l'INSERT de la ligne _definitions mais AVANT le commit(), rien
n'était jamais persisté : l'objet ENTIER disparaissait, malgré des champs
parfaitement remplis — d'où "j'ai tout rempli comme il faut et aucun objet
n'est créé". Mon précédent correctif (champ "Relation" sans cible) était
réel mais ne couvrait pas ce cas précis.
Cause de fond : chaque nom de colonne (dérivé du nom de champ tapé par
l'utilisateur, via slugify) était interpolé TEL QUEL dans du SQL brut
(CREATE TABLE, INSERT, UPDATE, ALTER TABLE ADD/DROP/RENAME COLUMN) sans
jamais être encadré de guillemets — n'importe quel nom de champ qui soit
aussi un mot réservé SQLite (order, group, index, select, where, table,
key, default, check, references, unique...) déclenchait exactement le
même crash-et-perte-de-transaction, dans n'importe laquelle de ces
opérations.
Correctif général (pas un simple contournement pour "order") :
db/quote_ident.py encadre tout identifiant de colonne de guillemets
doubles (forme standard SQL, supportée par SQLite) — appliqué partout où
un nom de colonne utilisateur est interpolé dans du SQL brut :
create_definition, add_field_to_definition, delete_field, update_field
(RENAME COLUMN), insert_row, update_row, update_row_field,
rows_referencing. Les noms de TABLE n'ont pas besoin de cette protection
(table_name_for.py les préfixe toujours "obj_", donc jamais un mot réservé
à eux seuls).
Trois nouveaux tests (tests/test_reserved_sql_keyword_field_names.py) :
création avec un champ "order" + insertion/lecture/mise à jour d'une
ligne, renommage d'un champ vers/depuis un mot réservé ("group"), ajout
d'un champ "select" à un objet existant — les trois confirmés en échec
sur l'ancien code (même erreur reproduite) puis au vert avec le
correctif. 136 tests au vert au total.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
c14f7e9ba5
commit
5c069ae1fe
@@ -1,4 +1,5 @@
|
||||
from ..connection import connect
|
||||
from ..quote_ident import quote_ident
|
||||
from .row_columns_and_values import row_columns_and_values
|
||||
|
||||
|
||||
@@ -8,7 +9,8 @@ def insert_row(slug, definition, form_data):
|
||||
cols, values = row_columns_and_values(definition, form_data)
|
||||
conn = connect(slug)
|
||||
placeholders = ["?"] * len(cols)
|
||||
sql = f"INSERT INTO {definition['table_name']} ({', '.join(cols)}) VALUES ({', '.join(placeholders)})"
|
||||
quoted_cols = ", ".join(quote_ident(c) for c in cols)
|
||||
sql = f"INSERT INTO {definition['table_name']} ({quoted_cols}) VALUES ({', '.join(placeholders)})"
|
||||
conn.execute(sql, values)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from ..connection import connect
|
||||
from ..quote_ident import quote_ident
|
||||
from ..slugify import slugify
|
||||
from ..definitions.definitions_referencing import definitions_referencing
|
||||
from ..definitions.get_definition import get_definition
|
||||
@@ -18,7 +19,7 @@ def rows_referencing(slug, definition_id, row_id):
|
||||
col = slugify(f["name"]).replace("-", "_") + "_id"
|
||||
conn = connect(slug)
|
||||
count = conn.execute(
|
||||
f"SELECT COUNT(*) AS c FROM {full['table_name']} WHERE {col} = ?", (row_id,)
|
||||
f"SELECT COUNT(*) AS c FROM {full['table_name']} WHERE {quote_ident(col)} = ?", (row_id,)
|
||||
).fetchone()["c"]
|
||||
conn.close()
|
||||
if count:
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from ..connection import connect
|
||||
from ..quote_ident import quote_ident
|
||||
from .row_columns_and_values import row_columns_and_values
|
||||
|
||||
|
||||
@@ -7,7 +8,7 @@ def update_row(slug, definition, row_id, form_data):
|
||||
valeurs saisies dans le même formulaire généré que pour la création."""
|
||||
cols, values = row_columns_and_values(definition, form_data)
|
||||
conn = connect(slug)
|
||||
set_clause = ", ".join(f"{c} = ?" for c in cols)
|
||||
set_clause = ", ".join(f"{quote_ident(c)} = ?" for c in cols)
|
||||
conn.execute(
|
||||
f"UPDATE {definition['table_name']} SET {set_clause} WHERE id = ?",
|
||||
values + [row_id],
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from ..connection import connect
|
||||
from ..quote_ident import quote_ident
|
||||
from ..slugify import slugify
|
||||
|
||||
|
||||
@@ -10,6 +11,6 @@ def update_row_field(slug, definition, row_id, field_def, new_value):
|
||||
fname = slugify(field_def["name"]).replace("-", "_")
|
||||
col = f"{fname}_id" if field_def["type"] == "relation" else fname
|
||||
conn = connect(slug)
|
||||
conn.execute(f"UPDATE {definition['table_name']} SET {col} = ? WHERE id = ?", (new_value, row_id))
|
||||
conn.execute(f"UPDATE {definition['table_name']} SET {quote_ident(col)} = ? WHERE id = ?", (new_value, row_id))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
Reference in New Issue
Block a user