mirror of
https://github.com/simonw/datasette.git
synced 2026-05-30 13:46:59 +02:00
Detect VACUUM in SQL analysis
Refs https://github.com/simonw/datasette/pull/2749#issuecomment-4559073803
This commit is contained in:
parent
1932f8429f
commit
951f5a9f30
4 changed files with 108 additions and 1 deletions
|
|
@ -720,6 +720,7 @@ def operation_is_write(operation: Operation) -> bool:
|
|||
"pragma",
|
||||
"analyze",
|
||||
"reindex",
|
||||
"vacuum",
|
||||
"unknown",
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ SQLOperation = Literal[
|
|||
"pragma",
|
||||
"analyze",
|
||||
"reindex",
|
||||
"vacuum",
|
||||
"unknown",
|
||||
]
|
||||
SQLTargetType = Literal[
|
||||
|
|
@ -423,10 +424,40 @@ def analyze_sql_tables(
|
|||
|
||||
conn.set_authorizer(authorizer)
|
||||
try:
|
||||
conn.execute("EXPLAIN " + sql, params if params is not None else {}).fetchall()
|
||||
explain_rows = conn.execute(
|
||||
"EXPLAIN " + sql, params if params is not None else {}
|
||||
).fetchall()
|
||||
finally:
|
||||
conn.set_authorizer(None)
|
||||
|
||||
if not operations:
|
||||
vacuum_row = next((row for row in explain_rows if row[1] == "Vacuum"), None)
|
||||
if vacuum_row is not None:
|
||||
schema_by_index = {
|
||||
row[0]: row[1] for row in conn.execute("PRAGMA database_list")
|
||||
}
|
||||
sqlite_schema = schema_by_index.get(vacuum_row[2])
|
||||
database = database_for_schema(sqlite_schema)
|
||||
record(
|
||||
"vacuum",
|
||||
"database",
|
||||
database=database,
|
||||
table=None,
|
||||
sqlite_schema=sqlite_schema,
|
||||
target=database,
|
||||
source=None,
|
||||
)
|
||||
else:
|
||||
record(
|
||||
"unknown",
|
||||
"statement",
|
||||
database=database_name,
|
||||
table=None,
|
||||
sqlite_schema=None,
|
||||
target=None,
|
||||
source=None,
|
||||
)
|
||||
|
||||
has_schema_operation = any(
|
||||
key.target_type in {"table", "index", "view", "trigger", "virtual-table"}
|
||||
and key.operation in {"create", "alter", "drop"}
|
||||
|
|
|
|||
|
|
@ -2011,6 +2011,37 @@ async def test_execute_write_rejects_function_operations():
|
|||
assert (await db.execute("select name from dogs")).dicts() == []
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_execute_write_rejects_vacuum_operation():
|
||||
ds = Datasette(
|
||||
memory=True,
|
||||
default_deny=True,
|
||||
config={
|
||||
"databases": {
|
||||
"data": {
|
||||
"permissions": {
|
||||
"view-database": {"id": "writer"},
|
||||
"execute-write-sql": {"id": "writer"},
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
ds.add_memory_database("execute_write_vacuum_operation", name="data")
|
||||
await ds.invoke_startup()
|
||||
|
||||
denied_response = await ds.client.post(
|
||||
"/data/-/execute-write",
|
||||
actor={"id": "writer"},
|
||||
json={"sql": "vacuum"},
|
||||
)
|
||||
|
||||
assert denied_response.status_code == 403
|
||||
assert denied_response.json()["errors"] == [
|
||||
"Unsupported SQL operation: vacuum database"
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_execute_write_create_table_uses_create_table_permission():
|
||||
ds = Datasette(
|
||||
|
|
|
|||
|
|
@ -129,6 +129,50 @@ def test_analyze_create_table_operation():
|
|||
]
|
||||
|
||||
|
||||
def test_analyze_vacuum_operation():
|
||||
conn = sqlite3.connect(":memory:")
|
||||
try:
|
||||
analysis = analyze_sql_tables(conn, "vacuum", database_name="data")
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
assert [operation_dict(operation) for operation in analysis.operations] == [
|
||||
{
|
||||
"operation": "vacuum",
|
||||
"target_type": "database",
|
||||
"database": "data",
|
||||
"sqlite_schema": "main",
|
||||
"table": None,
|
||||
"target": "data",
|
||||
"columns": (),
|
||||
"source": None,
|
||||
"internal": False,
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def test_analyze_statement_with_no_authorizer_callbacks_is_unknown():
|
||||
conn = sqlite3.connect(":memory:")
|
||||
try:
|
||||
analysis = analyze_sql_tables(conn, "reindex", database_name="data")
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
assert [operation_dict(operation) for operation in analysis.operations] == [
|
||||
{
|
||||
"operation": "unknown",
|
||||
"target_type": "statement",
|
||||
"database": "data",
|
||||
"sqlite_schema": None,
|
||||
"table": None,
|
||||
"target": None,
|
||||
"columns": (),
|
||||
"source": None,
|
||||
"internal": False,
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def test_analyze_transaction_operation(conn):
|
||||
analysis = analyze_sql_tables(conn, "commit", database_name="data")
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue