Read-only enforcement for the native-query path (RunNativeQuery).
External Data Sources are read-only by contract, but a stored Query's fully-rendered SQL is
executed verbatim on a read/write connection. The provider-layer write backstop and
ReadOnlyExternalBaseEntity only cover Save/Delete — they do NOT see this path — so the
guarantee is enforced HERE, at the driver/engine boundary, not delegated to a caller.
The text is read with the source dialect's own lexical rules (MySQL # comments and its
---needs-whitespace rule, backticks, backslash escapes, Snowflake $$ strings and //
comments, Oracle q'…' literals, …). Fail-closed. Throws when the SQL:
contains a comment the database runs (MySQL /*! … */), or a backslash in a string
literal on a dialect where a server setting decides what a backslash means,
starts with a DCL verb (GRANT/REVOKE/DENY) after any leading comments,
cannot be parsed in the dialect's node-sql-parser grammar, contains any write/DDL statement
at any depth, is a SELECT ... INTO, or is not exactly one typed statement; or, for a dialect
without a grammar (Oracle), is not a single SELECT/WITH free of write keywords.
Known limitation — defense-in-depth, NOT a substitute for a least-privilege source credential:
A side-effecting routine invoked from a read shape (SELECT writing_func(),
SELECT nextval('s')) is indistinguishable from a pure read in the AST and is NOT blocked.
Configure External Data Sources with a read-only/least-privilege credential as the real authority;
this screen is the app-level backstop against the common write/DDL/injection vectors.
Read-only enforcement for the native-query path (
RunNativeQuery).External Data Sources are read-only by contract, but a stored Query's fully-rendered SQL is executed verbatim on a read/write connection. The provider-layer write backstop and
ReadOnlyExternalBaseEntityonly coverSave/Delete— they do NOT see this path — so the guarantee is enforced HERE, at the driver/engine boundary, not delegated to a caller.The text is read with the source dialect's own lexical rules (MySQL
#comments and its---needs-whitespace rule, backticks, backslash escapes, Snowflake$$strings and//comments, Oracleq'…'literals, …). Fail-closed. Throws when the SQL:/*! … */), or a backslash in a string literal on a dialect where a server setting decides what a backslash means,GRANT/REVOKE/DENY) after any leading comments,SELECT ... INTO, or is not exactly one typed statement; or, for a dialect without a grammar (Oracle), is not a single SELECT/WITH free of write keywords.Known limitation — defense-in-depth, NOT a substitute for a least-privilege source credential:
SELECT writing_func(),SELECT nextval('s')) is indistinguishable from a pure read in the AST and is NOT blocked. Configure External Data Sources with a read-only/least-privilege credential as the real authority; this screen is the app-level backstop against the common write/DDL/injection vectors.