Expand description
Builds and runs parameterized INSERT/UPDATE/DELETE against an
already-introspected table, plus the deliberately-unrestricted raw-SQL
path the dashboard’s own “Run SQL” page uses.
Two different security postures, on purpose. The structured
functions (insert_row, update_row, delete_row,
fetch_row) only ever interpolate a table or column name into SQL
text after validating it against that table’s own freshly-introspected
column list (introspect::table_columns) - every value is always a
bound parameter via [codec::bind_any], never interpolated as text.
Column-name validation is enforced inside this module, not left to
the caller: values/settable are filtered against columns before
use, and pk is checked by [require_known_pk_columns] - the latter
closes a real SQL-injection gap found in a security review (pk’s
column names previously reached a WHERE clause unchecked, since they
originate from a request’s raw field names, not values, which
nothing upstream ever validated). The raw functions (run_raw,
run_script) are the deliberate opposite: they execute whatever SQL
text they’re given, unrestricted, the same way phpMyAdmin’s own SQL tab
and Import feature are - their safety is the dashboard’s existing
double gate (DB_DASHBOARD_PASSWORD + APP_DEBUG-gated registration),
not query validation, because restricting either would defeat the
entire point of those features.
Functions§
- delete_
row - Deletes one row identified by
pk(composite-safe, same shape asupdate_row). - fetch_
row - Fetches one row identified by
pk, if it still exists - used to prefill an edit form.pkoriginates from a query string a user can tamper with, so both its column names ([require_known_pk_columns]) and its values (bind_any) are validated/bound before any SQL is built - unlikerun_raw’s deliberately-unrestricted user-typed SQL. - insert_
row - Inserts one row.
valueskeys not found incolumnsare silently ignored (a form field the caller shouldn’t have sent) rather than erroring - the caller (the HTTP handler) only ever buildsvaluesfromcolumnsin the first place, so this is a defense-in-depth backstop, not the primary guard. - run_raw
- Runs arbitrary, user-typed SQL and renders whatever comes back - see
this module’s own doc comment for why this one is deliberately
unrestricted.
fetch_allworks uniformly for both queries and statements: aSELECTreturns real rows;INSERT/UPDATE/DELETE/ DDL return zero rows on success, so the same call reports “0 rows” rather than needing to sniff the statement’s kind from its text first. - run_
script - Runs an uploaded
.sqlfile’s full contents (the SQL Import feature) - unlikerun_raw, this doesn’t return or render rows, because an imported file is typically many statements, not one query to display. Usessqlx::raw_sqlrather thanfetch_all, the same primitivelarust_orm::migrate::runalready relies on for exactly this reason: it delegates statement parsing to the backend itself, so multi-statement files, comments, and string literals containing semicolons all work correctly (splitting on;by hand does not - a real bug this framework already hit once in the migration runner). Same deliberately- unrestricted security posture asrun_raw. - update_
row - Updates one row identified by
pk(composite-safe - pass more than one entry for a composite key). Any entry invalueswhose column is also part ofpkis skipped for theSETclause (the primary key itself is never rewritten by an edit form).