Fix write query failing when a named parameter is called :sql (#2765)

Closes #2761
This commit is contained in:
Viraat Das 2026-06-10 20:15:03 -07:00 • committed by GitHub
commit 3c1012dcc2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 79 additions and 20 deletions

View file

@ -27,16 +27,20 @@ window.datasetteSqlParameters = (() => {
manager.section
.querySelectorAll("[data-parameter-control]")
.forEach((control) => {
manager.parameterState.set(control.name, controlState(control));
manager.parameterState.set(
control.dataset.parameterName,
controlState(control)
);
});
}
function createControl(parameter, id, state) {
function createControl(parameter, id, state, namePrefix) {
const control = document.createElement(state.expanded ? "textarea" : "input");
control.id = id;
control.name = parameter;
control.name = `${namePrefix || ""}${parameter}`;
control.value = state.value;
control.setAttribute("data-parameter-control", "");
control.dataset.parameterName = parameter;
if (state.expanded) {
control.rows = 5;
} else {
@ -53,10 +57,16 @@ window.datasetteSqlParameters = (() => {
value,
selectionStart
) {
const replacement = createControl(control.name, control.id, {
value: value === undefined ? control.value : value,
expanded: expand,
});
const parameter = control.dataset.parameterName;
const replacement = createControl(
parameter,
control.id,
{
value: value === undefined ? control.value : value,
expanded: expand,
},
manager.namePrefix
);
button.textContent = expand ? "Collapse" : "Expand";
button.setAttribute("aria-expanded", expand ? "true" : "false");
control.replaceWith(replacement);
@ -64,7 +74,7 @@ window.datasetteSqlParameters = (() => {
if (selectionStart !== undefined && replacement.setSelectionRange) {
replacement.setSelectionRange(selectionStart, selectionStart);
}
manager.parameterState.set(replacement.name, controlState(replacement));
manager.parameterState.set(parameter, controlState(replacement));
}
function renderParameters(manager, parameters) {
@ -99,7 +109,7 @@ window.datasetteSqlParameters = (() => {
label.htmlFor = id;
label.textContent = parameter;
const control = createControl(parameter, id, state);
const control = createControl(parameter, id, state, manager.namePrefix);
row.append(label, control);
if (manager.allowExpand) {
@ -124,7 +134,10 @@ window.datasetteSqlParameters = (() => {
if (!control.matches || !control.matches("[data-parameter-control]")) {
return;
}
manager.parameterState.set(control.name, controlState(control));
manager.parameterState.set(
control.dataset.parameterName,
controlState(control)
);
});
if (!manager.allowExpand) {
@ -230,6 +243,7 @@ window.datasetteSqlParameters = (() => {
? section.dataset.allowExpand === "1"
: false
: options.allowExpand,
namePrefix: section ? section.dataset.parameterNamePrefix || "" : "",
parameterState: new Map(),
};
if (section) {

View file

@ -1,9 +1,10 @@
<div id="{{ sql_parameters_section_id|default("sql-parameters-section") }}" class="sql-parameters-section" data-sql-parameters-section{% if sql_parameters_allow_expand|default(false) %} data-allow-expand="1"{% endif %}>
{% set sql_parameter_name_prefix = sql_parameter_name_prefix|default("") %}
<div id="{{ sql_parameters_section_id|default("sql-parameters-section") }}" class="sql-parameters-section" data-sql-parameters-section{% if sql_parameter_name_prefix %} data-parameter-name-prefix="{{ sql_parameter_name_prefix }}"{% endif %}{% if sql_parameters_allow_expand|default(false) %} data-allow-expand="1"{% endif %}>
{% if parameter_names %}
<h2>Parameters</h2>
{% for parameter in parameter_names %}
{% set parameter_id = (sql_parameter_id_prefix|default("qp")) ~ loop.index %}
<p class="sql-parameter-row"><label for="{{ parameter_id }}">{{ parameter }}</label> <input type="text" id="{{ parameter_id }}" name="{{ parameter }}" value="{{ parameter_values.get(parameter, "") }}" data-parameter-control>{% if sql_parameters_allow_expand|default(false) %} <button type="button" class="sql-parameter-toggle" data-parameter-toggle aria-controls="{{ parameter_id }}" aria-expanded="false">Expand</button>{% endif %}</p>
<p class="sql-parameter-row"><label for="{{ parameter_id }}">{{ parameter }}</label> <input type="text" id="{{ parameter_id }}" name="{{ sql_parameter_name_prefix }}{{ parameter }}" value="{{ parameter_values.get(parameter, "") }}" data-parameter-control data-parameter-name="{{ parameter }}">{% if sql_parameters_allow_expand|default(false) %} <button type="button" class="sql-parameter-toggle" data-parameter-toggle aria-controls="{{ parameter_id }}" aria-expanded="false">Expand</button>{% endif %}</p>
{% endfor %}
{% endif %}
</div>

View file

@ -9,6 +9,7 @@ from .base import BaseView, _error
from .database import display_rows as display_query_rows
from .query_helpers import (
QueryValidationError,
SQL_PARAMETER_FORM_PREFIX,
_analysis_is_write,
_analysis_rows,
_analysis_rows_with_permissions,
@ -295,6 +296,7 @@ class ExecuteWriteView(BaseView):
"execute_write_columns": execute_write_columns,
"execute_write_display_rows": execute_write_display_rows,
"execute_write_truncated": execute_write_truncated,
"sql_parameter_name_prefix": SQL_PARAMETER_FORM_PREFIX,
"execute_disabled": bool(execute_disabled_reason),
"execute_disabled_reason": execute_disabled_reason,
"table_columns": table_columns,

View file

@ -49,6 +49,8 @@ _query_write_fields = {
"on_error_redirect",
}
SQL_PARAMETER_FORM_PREFIX = "_sql_param_"
class QueryValidationError(Exception):
def __init__(self, message, status=400, *, flash=False):
@ -289,11 +291,13 @@ def _coerce_execute_write_payload(data, is_json):
)
params = data.get("params") or {}
else:
params = {
key: value
for key, value in data.items()
if key not in {"sql", "csrftoken", "_json"}
}
params = {}
for key, value in data.items():
if key in {"sql", "csrftoken", "_json"}:
continue
if key.startswith(SQL_PARAMETER_FORM_PREFIX):
key = key[len(SQL_PARAMETER_FORM_PREFIX) :]
params[key] = value
if not isinstance(params, dict):
raise QueryValidationError("params must be a dictionary")
return data.get("sql"), params