Refuse a mutating query from the graph console

`debug/query-session!` ran whatever it was handed against the session
connection. A session graph is a projection of a file, rebuilt from that
file by Reload, so a mutation from the console produces a graph no
rebuild reproduces and no query result explains.

Bind the statement against the live schema first. A statement that does
not bind reports the binder's own message and executes nothing, which
also turns a misspelt table or property into an immediate error instead
of an empty result. A statement that binds runs only when the engine's
own read/write analysis calls it read-only.

The console's query box is labelled read-only. Load, Reload, Unload and
live sync are unaffected: they are separate handlers and do not go
through this path.

AI-assisted-by: mixed models
This commit is contained in:
Álvaro Tejero Cantero 2026-08-07 01:15:18 +02:00
parent d8bb1ae39a
commit e802a32ec7
No known key found for this signature in database
2 changed files with 21 additions and 5 deletions

View File

@ -88,8 +88,8 @@ Graph Console
</fieldset>
<fieldset>
<legend>Query graph (<a href="https://docs.ladybugdb.com/cypher/"
target="_blank">LadybugDB Cypher</a>)</legend>
<legend>Query graph, read-only (<a href="https://docs.ladybugdb.com/cypher/"
target="_blank">LadybugDB Cypher</a>)</legend>
<form id="graph-query-form" method="post" action="/dbg/actions/graph-query">
<div class="row">
<textarea name="query" rows="8" style="width:100%; font-family: monospace;"

View File

@ -200,7 +200,14 @@
(throw cause)))))
(defn query-session!
"Run `statement` against the in-memory graph for `profile-id`."
"Run a read-only `statement` against the in-memory graph for `profile-id`.
The statement is bound against the live schema before it runs, so a query
naming a table or a property that does not exist reports the binder's own
message and executes nothing. The engine's read/write analysis then decides
whether it may run at all: the console is an inspection surface, and a
session graph is rebuilt from the file by Reload, so a mutation from here
would produce a graph no rebuild reproduces."
[profile-id statement]
(when (str/blank? statement)
(ex/raise :type :validation
@ -208,8 +215,17 @@
:hint "cypher query is required"))
(if-let [{:keys [conn lock]} (get @sessions (session-key profile-id))]
(locking lock
(-> (ladybug/query-on-connection! conn statement)
format-query-result))
(let [{:keys [ok? error read-only?]} (ladybug/validate-on-connection! conn statement)]
(when-not ok?
(ex/raise :type :validation
:code :graph-query-invalid
:hint error))
(when-not read-only?
(ex/raise :type :validation
:code :graph-query-not-read-only
:hint "the graph console runs read-only queries"))
(-> (ladybug/query-on-connection! conn statement)
format-query-result)))
(ex/raise :type :not-found
:code :graph-session-not-loaded
:hint "load a file graph before running queries")))