{"id":3240,"date":"2026-05-04T12:29:03","date_gmt":"2026-05-04T04:29:03","guid":{"rendered":"https:\/\/www.dpriver.com\/blog\/?p=3240"},"modified":"2026-05-04T13:08:25","modified_gmt":"2026-05-04T05:08:25","slug":"llm-sql-guard-architecture-parser-catalog-policy-engine-audit-log","status":"publish","type":"post","link":"https:\/\/www.dpriver.com\/blog\/llm-sql-guard-architecture-parser-catalog-policy-engine-audit-log\/","title":{"rendered":"LLM SQL Guard Architecture: Parser, Catalog, Policy Engine, Audit Log"},"content":{"rendered":"<p><strong>Length:<\/strong> About 4,000 words \u00b7 <strong>Reading time:<\/strong> about 18\u201322 minutes<\/p>\n<p>An <strong>LLM SQL Guard architecture<\/strong> is a deterministic safety layer between a Text-to-SQL model and the database. It checks generated SQL before execution by parsing the query, resolving tables and columns against catalog metadata, applying user and field-level policies, scoring query risk, returning an <code>allow<\/code>, <code>warn<\/code>, <code>deny<\/code>, or <code>repair<\/code> decision, and recording an audit log.<\/p>\n<p>This architecture matters because production Text-to-SQL systems cannot rely on prompts alone. A model can generate SQL that is syntactically valid but semantically wrong, too expensive, non-compliant, or unauthorized for the requesting user. The guard is the layer that turns generated SQL from \u201cplausible text\u201d into a governed database operation.<\/p>\n<h2>Short Answer<\/h2>\n<p>A production LLM SQL Guard usually has seven core parts:<\/p>\n<ol>\n<li><strong>SQL parser<\/strong> \u2014 turns generated SQL text into a structured representation.<\/li>\n<li><strong>Catalog binding<\/strong> \u2014 resolves table names, column names, aliases, CTEs, functions, and dialect-specific syntax against real metadata.<\/li>\n<li><strong>Policy engine<\/strong> \u2014 checks user, role, table, field, row, purpose, and environment rules.<\/li>\n<li><strong>Sensitive-field and lineage analysis<\/strong> \u2014 detects direct and derived use of restricted fields.<\/li>\n<li><strong>Risk scoring<\/strong> \u2014 estimates the operational and compliance risk of running the query.<\/li>\n<li><strong>Decision and repair loop<\/strong> \u2014 returns <code>allow<\/code>, <code>warn<\/code>, <code>deny<\/code>, or <code>repair<\/code> with structured feedback.<\/li>\n<li><strong>Audit log<\/strong> \u2014 records the prompt, generated SQL, decision, policy hits, metadata context, and execution outcome.<\/li>\n<\/ol>\n<p>In practice, the guard should run synchronously before database execution. If it cannot understand the SQL, it should fail safely with a clear diagnostic instead of silently allowing the query.<\/p>\n<h2>Key Takeaways<\/h2>\n<ul>\n<li>Text-to-SQL security needs a deterministic control layer, not only prompt instructions.<\/li>\n<li>A SQL parser is necessary but not sufficient; production validation also needs catalog binding, permissions, sensitive-field metadata, lineage, risk scoring, and audit output.<\/li>\n<li>The guard should make explicit decisions: <code>allow<\/code>, <code>warn<\/code>, <code>deny<\/code>, or <code>repair<\/code>.<\/li>\n<li>Catalog-aware validation catches problems that syntax checks miss, including hallucinated columns, ambiguous references, wrong joins, and unsupported dialect features.<\/li>\n<li>Field-level permissions are essential because sensitive fields can appear in projections, filters, joins, aggregations, derived columns, and downstream lineage.<\/li>\n<li>Audit logs turn Text-to-SQL from an opaque model action into a reviewable enterprise workflow.<\/li>\n<\/ul>\n<h2>Why Architecture Matters for Text-to-SQL Security<\/h2>\n<p>Many teams start Text-to-SQL with a simple pattern:<\/p>\n<pre><code class=\"language-text\">User question \u2192 LLM \u2192 generated SQL \u2192 database\n<\/code><\/pre>\n<p>That path is useful for a demo, but risky for production. The database sees only the final SQL. It does not know whether the query came from a user, a model, an agent, a dashboard, or a scheduled workflow. It also does not know whether the model misunderstood the request, invented a column, selected restricted data, or created a query that is too expensive for an interactive session.<\/p>\n<p>A safer architecture inserts a guard before execution:<\/p>\n<pre><code class=\"language-text\">User question\n  \u2193\nLLM generates SQL\n  \u2193\nLLM SQL Guard\n  \u251c\u2500 parse SQL\n  \u251c\u2500 resolve catalog metadata\n  \u251c\u2500 validate tables and columns\n  \u251c\u2500 check permissions and sensitive fields\n  \u251c\u2500 inspect lineage and dependency roles\n  \u251c\u2500 estimate risk and cost\n  \u251c\u2500 return allow \/ warn \/ deny \/ repair\n  \u2514\u2500 write audit log\n  \u2193\nDatabase execution, repair loop, approval, or rejection\n<\/code><\/pre>\n<p>The goal is not to make the model perfect. The goal is to ensure the system never treats generated SQL as trusted just because it looks reasonable.<\/p>\n<h2>Reference Architecture<\/h2>\n<p>A practical LLM SQL Guard can be implemented as an API service, library, middleware component, or gateway in front of SQL execution. The exact deployment model varies, but the logical architecture is similar.<\/p>\n<table>\n<thead>\n<tr>\n<th>Layer<\/th>\n<th>Main responsibility<\/th>\n<th>Typical input<\/th>\n<th>Typical output<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Request context<\/td>\n<td>Identify user, role, purpose, session, tenant, and environment<\/td>\n<td>User identity, prompt, app context<\/td>\n<td>Normalized request envelope<\/td>\n<\/tr>\n<tr>\n<td>SQL parser<\/td>\n<td>Convert SQL text into structured syntax<\/td>\n<td>SQL text, dialect<\/td>\n<td>AST or structured SQL model<\/td>\n<\/tr>\n<tr>\n<td>Catalog binding<\/td>\n<td>Resolve names against real metadata<\/td>\n<td>AST, schema\/catalog, dialect<\/td>\n<td>Bound tables, columns, aliases, scopes<\/td>\n<\/tr>\n<tr>\n<td>Semantic validation<\/td>\n<td>Detect invalid or ambiguous SQL meaning<\/td>\n<td>Bound SQL, metadata<\/td>\n<td>Semantic errors and warnings<\/td>\n<\/tr>\n<tr>\n<td>Policy engine<\/td>\n<td>Apply table, field, row, purpose, and environment rules<\/td>\n<td>Bound SQL, user, metadata labels<\/td>\n<td>Policy violations and obligations<\/td>\n<\/tr>\n<tr>\n<td>Lineage and dependency analysis<\/td>\n<td>Determine which source fields affect outputs and filters<\/td>\n<td>Bound SQL, lineage model<\/td>\n<td>Column dependencies and roles<\/td>\n<\/tr>\n<tr>\n<td>Risk scoring<\/td>\n<td>Estimate operational and compliance risk<\/td>\n<td>SQL facts, policies, statistics<\/td>\n<td>Risk level and reason codes<\/td>\n<\/tr>\n<tr>\n<td>Decision engine<\/td>\n<td>Choose allow\/warn\/deny\/repair\/approval<\/td>\n<td>Errors, policies, risk<\/td>\n<td>Decision JSON<\/td>\n<\/tr>\n<tr>\n<td>Audit log<\/td>\n<td>Record what happened and why<\/td>\n<td>Request, SQL, decision, outcome<\/td>\n<td>Reviewable audit event<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>This design separates concerns. The parser should not be responsible for user permissions. The policy engine should not parse SQL using string matching. The audit layer should not infer meaning after the fact. Each layer should receive structured facts from the previous layer and produce explicit output for the next layer.<\/p>\n<h2>Component 1: Request Context<\/h2>\n<p>The guard needs more than SQL text. The same query can be acceptable for one user and blocked for another. A finance analyst may be allowed to query revenue by region. A customer support agent may be allowed to view a customer record but not export all customer emails. A developer may run broader queries in staging but not in production.<\/p>\n<p>A good request envelope includes:<\/p>\n<pre><code class=\"language-json\">{\n  &quot;request_id&quot;: &quot;req_2026_05_03_001&quot;,\n  &quot;user&quot;: {\n    &quot;id&quot;: &quot;u_12345&quot;,\n    &quot;roles&quot;: [&quot;sales_ops_analyst&quot;],\n    &quot;department&quot;: &quot;sales_operations&quot;\n  },\n  &quot;purpose&quot;: &quot;interactive_chatbi&quot;,\n  &quot;environment&quot;: &quot;production_readonly&quot;,\n  &quot;dialect&quot;: &quot;postgresql&quot;,\n  &quot;natural_language_request&quot;: &quot;Show quarterly pipeline by region for this year.&quot;,\n  &quot;generated_sql&quot;: &quot;SELECT region, DATE_TRUNC('quarter', close_date) AS quarter, SUM(amount) AS pipeline FROM opportunities GROUP BY region, DATE_TRUNC('quarter', close_date);&quot;\n}\n<\/code><\/pre>\n<p>This context lets the guard answer questions that SQL alone cannot answer:<\/p>\n<ul>\n<li>Who is requesting the data?<\/li>\n<li>Is this interactive analysis, a scheduled job, or an agent action?<\/li>\n<li>Is the target environment production or staging?<\/li>\n<li>Which SQL dialect should be used?<\/li>\n<li>Should the query be read-only?<\/li>\n<li>Which data domains and policy rules apply?<\/li>\n<\/ul>\n<p>Without request context, a guard can only validate the query in isolation. Enterprise Text-to-SQL needs user-aware and purpose-aware validation.<\/p>\n<h2>Component 2: SQL Parser<\/h2>\n<p>The SQL parser is the first deterministic step. It checks whether the generated text is SQL and converts the query into a structured representation that downstream systems can inspect.<\/p>\n<p>A parser should identify:<\/p>\n<ul>\n<li>statement type: <code>SELECT<\/code>, <code>INSERT<\/code>, <code>UPDATE<\/code>, <code>DELETE<\/code>, DDL, procedure call, and so on;<\/li>\n<li>clauses: <code>SELECT<\/code>, <code>FROM<\/code>, <code>JOIN<\/code>, <code>WHERE<\/code>, <code>GROUP BY<\/code>, <code>HAVING<\/code>, <code>ORDER BY<\/code>, <code>LIMIT<\/code>;<\/li>\n<li>expressions, functions, aliases, subqueries, CTEs, set operations, and window functions;<\/li>\n<li>dialect-specific syntax;<\/li>\n<li>parse errors with locations and messages.<\/li>\n<\/ul>\n<p>For security, the parser should fail closed on unsupported or dangerous statements. For example, many Text-to-SQL systems should reject or require special approval for:<\/p>\n<pre><code class=\"language-sql\">DROP TABLE customers;\nDELETE FROM orders;\nUPDATE users SET role = 'admin';\nCREATE TABLE temp_export AS SELECT * FROM customer_pii;\n<\/code><\/pre>\n<p>However, parsing is only the beginning. A parser can tell that <code>customer_email<\/code> appears in a query. It cannot, by itself, know whether that column exists, whether it is sensitive, or whether the user is allowed to access it. That is why the next layer is catalog binding.<\/p>\n<h2>Component 3: Catalog Binding<\/h2>\n<p>Catalog binding connects SQL text to the real database environment. It resolves each table and column reference against metadata.<\/p>\n<p>For example, consider:<\/p>\n<pre><code class=\"language-sql\">SELECT\n  c.name,\n  o.total_amount\nFROM customers c\nJOIN orders o ON c.customer_id = o.customer_id\nWHERE o.order_date &gt;= DATE '2026-01-01';\n<\/code><\/pre>\n<p>A catalog-aware guard should resolve:<\/p>\n<table>\n<thead>\n<tr>\n<th>SQL reference<\/th>\n<th>Bound object<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>customers c<\/code><\/td>\n<td>table <code>sales.customers<\/code> with alias <code>c<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>orders o<\/code><\/td>\n<td>table <code>sales.orders<\/code> with alias <code>o<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>c.name<\/code><\/td>\n<td>column <code>sales.customers.name<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>o.total_amount<\/code><\/td>\n<td>column <code>sales.orders.total_amount<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>c.customer_id<\/code><\/td>\n<td>column <code>sales.customers.customer_id<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>o.customer_id<\/code><\/td>\n<td>column <code>sales.orders.customer_id<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>o.order_date<\/code><\/td>\n<td>column <code>sales.orders.order_date<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>This step catches errors a syntax checker cannot catch:<\/p>\n<ul>\n<li>table does not exist;<\/li>\n<li>column does not exist;<\/li>\n<li>unqualified column is ambiguous;<\/li>\n<li>CTE output column does not match later references;<\/li>\n<li>function or type is invalid for the chosen dialect;<\/li>\n<li>generated SQL uses a development schema instead of production schema;<\/li>\n<li>the model used a plausible metric name that is not in the catalog.<\/li>\n<\/ul>\n<p>A validation result might look like this:<\/p>\n<pre><code class=\"language-json\">{\n  &quot;semantic_status&quot;: &quot;invalid&quot;,\n  &quot;errors&quot;: [\n    {\n      &quot;code&quot;: &quot;UNKNOWN_COLUMN&quot;,\n      &quot;reference&quot;: &quot;customers.lifetime_value&quot;,\n      &quot;message&quot;: &quot;Column lifetime_value does not exist in sales.customers.&quot;,\n      &quot;repair_hint&quot;: &quot;Use customer_metrics.ltv_usd or ask the user to choose a lifetime value metric.&quot;\n    }\n  ]\n}\n<\/code><\/pre>\n<p>This is especially important for LLM-generated SQL because hallucinated columns often sound correct. The model may generate <code>customer_lifetime_value<\/code>, <code>is_active_customer<\/code>, or <code>net_revenue<\/code> even when the real schema uses different names or requires a join to a metric table.<\/p>\n<h2>Component 4: Semantic Validation<\/h2>\n<p>Semantic validation goes beyond object existence. It asks whether the SQL meaning is valid and safe for the use case.<\/p>\n<p>Examples of semantic checks include:<\/p>\n<ul>\n<li>Does every selected column have a clear source?<\/li>\n<li>Are aliases and scopes resolved correctly across CTEs and subqueries?<\/li>\n<li>Are join keys plausible for the intended relationship?<\/li>\n<li>Does an aggregation mix row-level and aggregate fields incorrectly?<\/li>\n<li>Does the query use required business filters, such as tenant, region, or active status?<\/li>\n<li>Does the query use dialect-specific functions correctly?<\/li>\n<li>Is <code>SELECT *<\/code> prohibited for this environment?<\/li>\n<li>Does a row limit apply to interactive queries?<\/li>\n<\/ul>\n<p>Consider this query:<\/p>\n<pre><code class=\"language-sql\">SELECT\n  c.region,\n  SUM(o.amount) AS revenue\nFROM customers c\nJOIN orders o ON c.name = o.customer_name\nGROUP BY c.region;\n<\/code><\/pre>\n<p>The query may parse and the columns may exist. But the join may be semantically risky if the real relationship should use <code>customer_id<\/code>, not customer name. Depending on metadata, the guard might return a warning:<\/p>\n<pre><code class=\"language-json\">{\n  &quot;decision&quot;: &quot;warn&quot;,\n  &quot;risk_level&quot;: &quot;medium&quot;,\n  &quot;warnings&quot;: [\n    {\n      &quot;code&quot;: &quot;NON_KEY_JOIN&quot;,\n      &quot;message&quot;: &quot;The query joins customers to orders using names instead of customer_id.&quot;,\n      &quot;suggested_join&quot;: &quot;customers.customer_id = orders.customer_id&quot;\n    }\n  ]\n}\n<\/code><\/pre>\n<p>Semantic validation should be careful not to overclaim. Some checks require business metadata that may not exist yet. The architecture should support a gradual path: start with table and column binding, ambiguity detection, read-only enforcement, restricted statements, and sensitive-field checks; then add richer business rules as metadata improves.<\/p>\n<h2>How Natural-Language Intent Maps to Catalog Fields<\/h2>\n<p>There is one important boundary: a SQL parser alone does not know that the phrase \u201cactive customers\u201d means <code>customers.status = 'active'<\/code>, or that \u201cnet revenue\u201d means <code>SUM(orders.amount) - SUM(refunds.amount)<\/code>. Those mappings require a separate semantic layer.<\/p>\n<p>A practical implementation usually combines several techniques:<\/p>\n<table>\n<thead>\n<tr>\n<th>Technique<\/th>\n<th>What it does<\/th>\n<th>Why it matters<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Business glossary \/ metric store<\/td>\n<td>Stores approved definitions such as <code>net_revenue = gross_revenue - refunds<\/code><\/td>\n<td>Makes business terms explicit and reviewable<\/td>\n<\/tr>\n<tr>\n<td>Catalog metadata<\/td>\n<td>Stores tables, columns, descriptions, labels, owners, and relationships<\/td>\n<td>Grounds SQL in real database objects<\/td>\n<\/tr>\n<tr>\n<td>Embedding retrieval<\/td>\n<td>Finds candidate tables, columns, metrics, and glossary terms related to the user request<\/td>\n<td>Helps map natural language to catalog vocabulary<\/td>\n<\/tr>\n<tr>\n<td>LLM reranking \/ intent extraction<\/td>\n<td>Interprets phrases such as \u201cactive customers\u201d or \u201ctop accounts\u201d and ranks candidate mappings<\/td>\n<td>Uses the model where it is strongest: language understanding<\/td>\n<\/tr>\n<tr>\n<td>Deterministic SQL binding<\/td>\n<td>Checks what the generated SQL actually references<\/td>\n<td>Prevents the model from becoming the enforcement layer<\/td>\n<\/tr>\n<tr>\n<td>Rule \/ policy evaluation<\/td>\n<td>Compares generated SQL facts with approved definitions and policies<\/td>\n<td>Produces auditable warnings, denials, and repair hints<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>In other words, an LLM can help interpret the user\u2019s natural-language intent, but it should be treated as an optional upstream assistant, not as a required dependency of the guard. The guard itself can remain deterministic and model-agnostic:<\/p>\n<pre><code class=\"language-text\">Optional upstream Text-to-SQL \/ intent layer\n  \u2193\nGenerated SQL + optional structured intent\n  \u2193\nSQL Guard Core\n  \u251c\u2500 parse and bind SQL\n  \u251c\u2500 compare SQL facts with catalog \/ policy \/ metric definitions\n  \u251c\u2500 produce allow \/ warn \/ deny \/ repair \/ approval_required\n  \u2514\u2500 write audit log\n<\/code><\/pre>\n<p>If a deployment has a semantic layer or metric store, the guard can compare the generated SQL against those approved definitions. If a deployment also has an LLM-based intent extractor, the guard can consume its structured intent as input. But the enforcement decision should not depend on the guard calling an LLM internally.<\/p>\n<p>A precise warning such as \u201cNet revenue requires subtracting refunds\u201d should not come from the parser by itself. It should come from an approved metric definition or business glossary entry. The parser and semantic binder only prove what the SQL actually does. The metric layer says what the SQL should have done. The guard compares the two.<\/p>\n<h2>Component 5: Policy Engine<\/h2>\n<p>The policy engine decides whether the bound SQL is allowed for the requesting user and purpose. It should consume structured SQL facts, not raw strings.<\/p>\n<p>A useful policy model can include:<\/p>\n<ul>\n<li><strong>statement policy<\/strong> \u2014 read-only only, or allow controlled writes in approved workflows;<\/li>\n<li><strong>table policy<\/strong> \u2014 which users or roles can access each table;<\/li>\n<li><strong>field policy<\/strong> \u2014 which columns are restricted, masked, aggregated, or approval-gated;<\/li>\n<li><strong>row policy<\/strong> \u2014 required tenant, region, owner, or department filters;<\/li>\n<li><strong>purpose policy<\/strong> \u2014 different rules for dashboarding, ad hoc analysis, export, model training, or agent actions;<\/li>\n<li><strong>environment policy<\/strong> \u2014 stricter rules for production than staging;<\/li>\n<li><strong>query-shape policy<\/strong> \u2014 no <code>SELECT *<\/code>, required <code>LIMIT<\/code>, no Cartesian joins, no high-risk functions.<\/li>\n<\/ul>\n<p>A simple policy rule might say:<\/p>\n<pre><code class=\"language-text\">Users with role sales_ops_analyst may query opportunities.amount and opportunities.region,\nbut may not query customers.email, customers.phone, or customers.ssn unless the purpose is approved_customer_support_case.\n<\/code><\/pre>\n<p>If the generated SQL is:<\/p>\n<pre><code class=\"language-sql\">SELECT\n  c.name,\n  c.email,\n  c.phone,\n  SUM(o.amount) AS total_spend\nFROM customers c\nJOIN orders o ON c.customer_id = o.customer_id\nGROUP BY c.name, c.email, c.phone;\n<\/code><\/pre>\n<p>The guard should not only say \u201cthis is a valid query.\u201d It should identify policy violations:<\/p>\n<pre><code class=\"language-json\">{\n  &quot;decision&quot;: &quot;deny&quot;,\n  &quot;risk_level&quot;: &quot;high&quot;,\n  &quot;policy_violations&quot;: [\n    {\n      &quot;code&quot;: &quot;FIELD_ACCESS_DENIED&quot;,\n      &quot;field&quot;: &quot;customers.email&quot;,\n      &quot;reason&quot;: &quot;Email is labeled PII and is not allowed for role sales_ops_analyst.&quot;\n    },\n    {\n      &quot;code&quot;: &quot;FIELD_ACCESS_DENIED&quot;,\n      &quot;field&quot;: &quot;customers.phone&quot;,\n      &quot;reason&quot;: &quot;Phone is labeled PII and is not allowed for role sales_ops_analyst.&quot;\n    }\n  ],\n  &quot;repair_hint&quot;: &quot;Remove direct identifiers or aggregate by non-PII dimensions such as region or customer_segment.&quot;\n}\n<\/code><\/pre>\n<p>This is where Text-to-SQL security becomes an enterprise governance problem. The system must know not just what the query says, but who is asking, why they are asking, and which data they are allowed to use.<\/p>\n<h2>Component 6: Sensitive-Field and Lineage Analysis<\/h2>\n<p>Sensitive fields are not always visible in the final <code>SELECT<\/code> list. They can influence a result through filters, joins, derived expressions, aggregates, or intermediate CTEs.<\/p>\n<p>For example:<\/p>\n<pre><code class=\"language-sql\">WITH vip_customers AS (\n  SELECT customer_id\n  FROM customers\n  WHERE annual_income &gt; 250000\n)\nSELECT\n  o.region,\n  COUNT(*) AS vip_orders\nFROM orders o\nJOIN vip_customers v ON o.customer_id = v.customer_id\nGROUP BY o.region;\n<\/code><\/pre>\n<p>The final output does not show <code>annual_income<\/code>. But the result depends on it. If <code>customers.annual_income<\/code> is sensitive, the guard should know that it influenced the result through a filter inside the CTE.<\/p>\n<p>This is why field-level dependency analysis matters. The guard should classify dependency roles such as:<\/p>\n<table>\n<thead>\n<tr>\n<th>Dependency role<\/th>\n<th>Example<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Projection<\/td>\n<td>A field appears directly in the output.<\/td>\n<\/tr>\n<tr>\n<td>Filter<\/td>\n<td>A field restricts which rows are included.<\/td>\n<\/tr>\n<tr>\n<td>Join<\/td>\n<td>A field connects two datasets.<\/td>\n<\/tr>\n<tr>\n<td>Grouping<\/td>\n<td>A field defines aggregation groups.<\/td>\n<\/tr>\n<tr>\n<td>Aggregation input<\/td>\n<td>A field is summarized by <code>SUM<\/code>, <code>COUNT<\/code>, <code>AVG<\/code>, etc.<\/td>\n<\/tr>\n<tr>\n<td>Ordering<\/td>\n<td>A field affects result ranking.<\/td>\n<\/tr>\n<tr>\n<td>Derived expression<\/td>\n<td>A field contributes to a computed output.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>For sensitive data, filter and join dependencies can matter as much as projection dependencies. A system that checks only selected columns may miss indirect disclosure risks.<\/p>\n<h2>Component 7: Risk Scoring<\/h2>\n<p>Not every issue requires the same response. Some queries should be blocked. Some should be allowed with a warning. Some should be repaired automatically. Some should be routed to human approval.<\/p>\n<p>Risk scoring helps the system make consistent decisions.<\/p>\n<p>A simple scoring model can consider:<\/p>\n<ul>\n<li>statement type: read-only vs write\/DDL;<\/li>\n<li>sensitive fields: direct or indirect use;<\/li>\n<li>permission violations;<\/li>\n<li>unknown tables or columns;<\/li>\n<li>ambiguous references;<\/li>\n<li>missing required filters;<\/li>\n<li>estimated scan size or cost;<\/li>\n<li>absence of <code>LIMIT<\/code> for interactive queries;<\/li>\n<li>cross-domain joins;<\/li>\n<li>export intent;<\/li>\n<li>production vs staging environment;<\/li>\n<li>model confidence or number of repair attempts.<\/li>\n<\/ul>\n<p>A decision table might look like this:<\/p>\n<table>\n<thead>\n<tr>\n<th>Condition<\/th>\n<th>Example<\/th>\n<th>Suggested decision<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Parse error<\/td>\n<td>Invalid SQL grammar<\/td>\n<td><code>repair<\/code><\/td>\n<\/tr>\n<tr>\n<td>Unknown column<\/td>\n<td>Hallucinated metric<\/td>\n<td><code>repair<\/code><\/td>\n<\/tr>\n<tr>\n<td>Ambiguous reference<\/td>\n<td><code>name<\/code> exists in two joined tables<\/td>\n<td><code>repair<\/code> or <code>deny<\/code><\/td>\n<\/tr>\n<tr>\n<td>Restricted field selected<\/td>\n<td><code>customers.ssn<\/code><\/td>\n<td><code>deny<\/code><\/td>\n<\/tr>\n<tr>\n<td>Sensitive field used in filter<\/td>\n<td><code>WHERE income &gt; ...<\/code><\/td>\n<td><code>warn<\/code>, <code>deny<\/code>, or approval depending on policy<\/td>\n<\/tr>\n<tr>\n<td>No limit on large interactive query<\/td>\n<td>Full table scan<\/td>\n<td><code>warn<\/code> or <code>repair<\/code><\/td>\n<\/tr>\n<tr>\n<td>DDL or destructive DML<\/td>\n<td><code>DROP<\/code>, <code>DELETE<\/code>, <code>UPDATE<\/code><\/td>\n<td><code>deny<\/code> or approval-only<\/td>\n<\/tr>\n<tr>\n<td>Allowed aggregate query<\/td>\n<td>Revenue by region<\/td>\n<td><code>allow<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>The risk score should be explainable. A black-box \u201chigh risk\u201d label is not enough for enterprise review. The output should include the reasons, affected fields, policy IDs, and repair options.<\/p>\n<h2>Component 8: Decision and Repair Loop<\/h2>\n<p>A good LLM SQL Guard does not only block queries. It should help the application recover safely when possible.<\/p>\n<p>The decision model can be:<\/p>\n<table>\n<thead>\n<tr>\n<th>Decision<\/th>\n<th>Meaning<\/th>\n<th>Example action<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>allow<\/code><\/td>\n<td>Query is valid and permitted<\/td>\n<td>Execute the SQL<\/td>\n<\/tr>\n<tr>\n<td><code>warn<\/code><\/td>\n<td>Query is allowed but has reviewable risk<\/td>\n<td>Execute with notice or require user confirmation<\/td>\n<\/tr>\n<tr>\n<td><code>deny<\/code><\/td>\n<td>Query violates a hard rule<\/td>\n<td>Do not execute<\/td>\n<\/tr>\n<tr>\n<td><code>repair<\/code><\/td>\n<td>Query has fixable semantic or policy issues<\/td>\n<td>Ask the model to regenerate using structured feedback<\/td>\n<\/tr>\n<tr>\n<td><code>approval_required<\/code><\/td>\n<td>Query may be valid but needs human approval<\/td>\n<td>Route to workflow<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>For LLM applications, <code>repair<\/code> is especially useful. Instead of returning a raw database error, the guard can provide precise feedback:<\/p>\n<pre><code class=\"language-json\">{\n  &quot;decision&quot;: &quot;repair&quot;,\n  &quot;risk_level&quot;: &quot;medium&quot;,\n  &quot;errors&quot;: [\n    {\n      &quot;code&quot;: &quot;UNKNOWN_COLUMN&quot;,\n      &quot;reference&quot;: &quot;orders.revenue&quot;,\n      &quot;message&quot;: &quot;orders.revenue does not exist.&quot;\n    },\n    {\n      &quot;code&quot;: &quot;MISSING_LIMIT&quot;,\n      &quot;message&quot;: &quot;Interactive queries must include LIMIT 1000 or less.&quot;\n    }\n  ],\n  &quot;repair_instructions_for_model&quot;: [\n    &quot;Use orders.amount instead of orders.revenue.&quot;,\n    &quot;Add LIMIT 1000.&quot;,\n    &quot;Do not include PII fields.&quot;\n  ]\n}\n<\/code><\/pre>\n<p>The application can pass this feedback to the model and request a corrected query. The repaired query should go through the guard again. The system should cap repair attempts to avoid loops.<\/p>\n<h2>Component 9: Audit Log<\/h2>\n<p>The audit log is what makes Text-to-SQL reviewable. Without it, teams may know that a model ran SQL, but not why a query was allowed, denied, or repaired.<\/p>\n<p>An audit event should capture:<\/p>\n<ul>\n<li>request ID and timestamp;<\/li>\n<li>user, role, tenant, and application;<\/li>\n<li>natural-language request;<\/li>\n<li>generated SQL;<\/li>\n<li>SQL dialect and environment;<\/li>\n<li>parsed tables, columns, and dependency roles;<\/li>\n<li>policy rules evaluated;<\/li>\n<li>violations, warnings, and risk score;<\/li>\n<li>decision and repair instructions;<\/li>\n<li>final SQL executed, if any;<\/li>\n<li>database execution status;<\/li>\n<li>row count or cost metadata when available.<\/li>\n<\/ul>\n<p>Example:<\/p>\n<pre><code class=\"language-json\">{\n  &quot;event_type&quot;: &quot;llm_sql_guard_decision&quot;,\n  &quot;request_id&quot;: &quot;req_2026_05_03_001&quot;,\n  &quot;user_id&quot;: &quot;u_12345&quot;,\n  &quot;dialect&quot;: &quot;postgresql&quot;,\n  &quot;environment&quot;: &quot;production_readonly&quot;,\n  &quot;decision&quot;: &quot;deny&quot;,\n  &quot;risk_level&quot;: &quot;high&quot;,\n  &quot;tables&quot;: [&quot;customers&quot;, &quot;orders&quot;],\n  &quot;columns&quot;: [\n    {&quot;name&quot;: &quot;customers.email&quot;, &quot;role&quot;: &quot;projection&quot;, &quot;labels&quot;: [&quot;pii&quot;]},\n    {&quot;name&quot;: &quot;orders.amount&quot;, &quot;role&quot;: &quot;aggregation_input&quot;, &quot;labels&quot;: []}\n  ],\n  &quot;policy_violations&quot;: [&quot;FIELD_ACCESS_DENIED&quot;],\n  &quot;executed&quot;: false\n}\n<\/code><\/pre>\n<p>Audit logs are useful for security reviews, compliance evidence, debugging, product analytics, and improving model prompts. They also help teams understand which guard rules are too strict, too loose, or missing metadata.<\/p>\n<h2>End-to-End Example<\/h2>\n<p>Suppose a user asks:<\/p>\n<pre><code class=\"language-text\">Show me the top customers by revenue this quarter, including email, so the sales team can contact them.\n<\/code><\/pre>\n<p>The model generates:<\/p>\n<pre><code class=\"language-sql\">SELECT\n  c.customer_id,\n  c.name,\n  c.email,\n  SUM(o.amount) AS revenue\nFROM customers c\nJOIN orders o ON c.customer_id = o.customer_id\nWHERE o.order_date &gt;= DATE '2026-04-01'\nGROUP BY c.customer_id, c.name, c.email\nORDER BY revenue DESC\nLIMIT 50;\n<\/code><\/pre>\n<p>The SQL is syntactically valid. It has a reasonable join, aggregation, order, and limit. But the guard should evaluate more than syntax.<\/p>\n<p>A possible result:<\/p>\n<pre><code class=\"language-json\">{\n  &quot;decision&quot;: &quot;repair&quot;,\n  &quot;risk_level&quot;: &quot;high&quot;,\n  &quot;semantic_status&quot;: &quot;valid&quot;,\n  &quot;tables&quot;: [&quot;customers&quot;, &quot;orders&quot;],\n  &quot;column_dependencies&quot;: [\n    {&quot;column&quot;: &quot;customers.customer_id&quot;, &quot;role&quot;: &quot;projection&quot;},\n    {&quot;column&quot;: &quot;customers.name&quot;, &quot;role&quot;: &quot;projection&quot;},\n    {&quot;column&quot;: &quot;customers.email&quot;, &quot;role&quot;: &quot;projection&quot;, &quot;labels&quot;: [&quot;pii&quot;]},\n    {&quot;column&quot;: &quot;orders.amount&quot;, &quot;role&quot;: &quot;aggregation_input&quot;},\n    {&quot;column&quot;: &quot;orders.order_date&quot;, &quot;role&quot;: &quot;filter&quot;},\n    {&quot;column&quot;: &quot;orders.customer_id&quot;, &quot;role&quot;: &quot;join&quot;}\n  ],\n  &quot;policy_violations&quot;: [\n    {\n      &quot;code&quot;: &quot;PII_PROJECTION_NOT_ALLOWED&quot;,\n      &quot;field&quot;: &quot;customers.email&quot;,\n      &quot;message&quot;: &quot;The requesting role may rank customers by revenue but may not return direct contact identifiers.&quot;\n    }\n  ],\n  &quot;repair_instructions_for_model&quot;: [\n    &quot;Remove customers.email from SELECT and GROUP BY.&quot;,\n    &quot;If outreach is required, return customer_id and route the result to an approved CRM workflow.&quot;\n  ]\n}\n<\/code><\/pre>\n<p>The important point is that the guard did not simply reject a valid analytical question. It preserved the business intent while changing the execution path to avoid exposing direct identifiers.<\/p>\n<h2>Deployment Patterns<\/h2>\n<p>There are several ways to deploy an LLM SQL Guard.<\/p>\n<h3>Pattern 1: In-Application Middleware<\/h3>\n<p>The application calls the model, receives SQL, sends it to the guard, and executes only if allowed.<\/p>\n<pre><code class=\"language-text\">ChatBI app \u2192 LLM \u2192 SQL Guard \u2192 database\n<\/code><\/pre>\n<p>This pattern is simple and works well for a single application team. The risk is that other applications may bypass the guard unless the organization standardizes the pattern.<\/p>\n<h3>Pattern 2: Central SQL Guard Service<\/h3>\n<p>Multiple applications call a shared guard API before execution.<\/p>\n<pre><code class=\"language-text\">ChatBI app\nAgent workflow       \u2192 SQL Guard API \u2192 database or approval path\nBI assistant\n<\/code><\/pre>\n<p>This pattern is better for enterprise platforms because policies, metadata, audit logs, and repair behavior can be managed centrally.<\/p>\n<h3>Pattern 3: Database Proxy or Query Gateway<\/h3>\n<p>The guard sits close to the database access layer. It can enforce controls even if different applications generate SQL.<\/p>\n<pre><code class=\"language-text\">Apps and agents \u2192 SQL query gateway \u2192 guard decision \u2192 database\n<\/code><\/pre>\n<p>This provides stronger enforcement but requires more careful engineering around latency, connection handling, supported protocols, and failure modes.<\/p>\n<h3>Pattern 4: Offline Review and CI<\/h3>\n<p>Teams can also use SQL validation outside the request path, for example in prompt testing, agent evaluation, dbt model review, or pull request checks.<\/p>\n<pre><code class=\"language-text\">Generated SQL test set \u2192 guard validation \u2192 regression report\n<\/code><\/pre>\n<p>This pattern helps teams improve prompts and policies before production traffic reaches the database.<\/p>\n<h2>What to Build First<\/h2>\n<p>A complete SQL governance system can be large, but the first useful guard does not need to solve everything. A practical starting scope is:<\/p>\n<ol>\n<li>Parse generated SQL for the target dialect.<\/li>\n<li>Reject destructive statements for Text-to-SQL flows.<\/li>\n<li>Bind tables, columns, aliases, CTEs, and subqueries against catalog metadata.<\/li>\n<li>Detect unknown and ambiguous references.<\/li>\n<li>Enforce read-only and no-<code>SELECT *<\/code> rules.<\/li>\n<li>Check sensitive field labels in projection, filters, joins, and derived outputs.<\/li>\n<li>Require row limits or cost controls for interactive queries.<\/li>\n<li>Return structured <code>allow<\/code>, <code>warn<\/code>, <code>deny<\/code>, or <code>repair<\/code> decisions.<\/li>\n<li>Log every decision.<\/li>\n<\/ol>\n<p>This starting point creates immediate value because it catches the most common failures: hallucinated schema, unsafe statements, PII exposure, ambiguous references, and unbounded queries.<\/p>\n<p>More advanced capabilities can follow:<\/p>\n<ul>\n<li>row-level policy checks;<\/li>\n<li>purpose-based access;<\/li>\n<li>query cost estimation;<\/li>\n<li>business metric validation;<\/li>\n<li>human approval workflows;<\/li>\n<li>lineage export to catalog systems;<\/li>\n<li>model evaluation and prompt regression tests;<\/li>\n<li>cross-dialect policy normalization.<\/li>\n<\/ul>\n<h2>Common Questions<\/h2>\n<h3>Is an LLM SQL Guard the same as a SQL parser?<\/h3>\n<p>No. A SQL parser is one component of the guard. The parser understands SQL structure. The guard uses that structure with catalog metadata, user permissions, sensitive-field labels, risk rules, and audit requirements to decide whether the generated SQL should run.<\/p>\n<h3>Can prompt engineering replace a SQL Guard?<\/h3>\n<p>No. Prompts can guide generation, but they are not enforcement. A model can ignore, misunderstand, or be manipulated around instructions. A SQL Guard applies deterministic checks after SQL is generated and before it reaches the database.<\/p>\n<h3>Should the guard run before or after query execution?<\/h3>\n<p>The main guard decision should run before execution. Some telemetry, such as row count or actual cost, is available only after execution, but permission, safety, semantic validation, and risk checks should happen before the database runs the query.<\/p>\n<h3>What should happen when the guard cannot understand a query?<\/h3>\n<p>For production Text-to-SQL, the safest default is to fail closed with an <code>unsupported<\/code> or <code>repair<\/code> decision. The response should explain what was unsupported, such as a dialect construct, dynamic SQL, stored procedure call, or ambiguous reference.<\/p>\n<h3>Does this architecture require a data catalog?<\/h3>\n<p>It requires catalog-like metadata. That can come from a data catalog, database information schema, dbt artifacts, manually curated schema files, or a metadata service. The guard needs reliable information about tables, columns, labels, relationships, and policies.<\/p>\n<h3>How does column-level lineage help SQL Guard decisions?<\/h3>\n<p>Column-level lineage shows which source fields affect outputs, filters, joins, groups, and derived columns. This helps detect indirect sensitive-field use, explain why a query was blocked, and produce an audit trail that reviewers can understand.<\/p>\n<h2>Quick Reference<\/h2>\n<table>\n<thead>\n<tr>\n<th>Question<\/th>\n<th>Practical answer<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Where does the guard sit?<\/td>\n<td>Between the LLM and database execution.<\/td>\n<\/tr>\n<tr>\n<td>What is the minimum input?<\/td>\n<td>User context, SQL text, dialect, catalog metadata, and policy context.<\/td>\n<\/tr>\n<tr>\n<td>What does it output?<\/td>\n<td><code>allow<\/code>, <code>warn<\/code>, <code>deny<\/code>, <code>repair<\/code>, or <code>approval_required<\/code>, plus reasons.<\/td>\n<\/tr>\n<tr>\n<td>What is the first technical step?<\/td>\n<td>Parse SQL into a structured representation.<\/td>\n<\/tr>\n<tr>\n<td>What catches hallucinated columns?<\/td>\n<td>Catalog binding and semantic validation.<\/td>\n<\/tr>\n<tr>\n<td>What catches sensitive data exposure?<\/td>\n<td>Field labels, policy checks, and lineage\/dependency analysis.<\/td>\n<\/tr>\n<tr>\n<td>What makes the system auditable?<\/td>\n<td>Decision logs with SQL facts, policy hits, risk reasons, and execution outcome.<\/td>\n<\/tr>\n<tr>\n<td>What should happen on unsupported SQL?<\/td>\n<td>Fail safely with structured diagnostics.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h2>Summary<\/h2>\n<p>An LLM SQL Guard architecture gives enterprise Text-to-SQL systems a deterministic safety layer. The model can propose SQL, but the guard decides whether that SQL is valid, permitted, low-risk, repairable, or blocked.<\/p>\n<p>The essential design is straightforward: parse the SQL, bind it to catalog metadata, validate its meaning, apply policy, inspect sensitive-field dependencies, score risk, return a clear decision, and write an audit log. The details matter, but the architectural principle is simple: generated SQL should never reach production data without a structured pre-execution check.<\/p>\n<p>For teams building ChatBI, AI analytics agents, or internal Text-to-SQL workflows, this architecture provides a practical path from prototype to production. Start with deterministic parsing, catalog-aware validation, field-level policy checks, and audit logs. Then expand into richer lineage, risk scoring, approval workflows, and governance integrations as the system matures.<\/p>\n<h2>Try SQL Guard-Style Validation<\/h2>\n<p>If you are evaluating Text-to-SQL or ChatBI for enterprise use, try SQL Guard-style validation with a generated query and review what should be checked before execution:<\/p>\n<p><a href=\"https:\/\/www.dpriver.com\/pp\/sqlformat.htm?utm_source=dpriver_blog&amp;utm_medium=blog_cta&amp;utm_campaign=llm_sql_guard&amp;utm_content=sqlguard_test\">Test an LLM-generated SQL query<\/a><\/p>\n<p>For architecture review or a production pilot, prepare a small set of representative SQL examples, including safe queries, hallucinated columns, sensitive-field access, joins, aggregations, and large scans. These examples make it much easier to evaluate whether a guard is ready for your environment.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>A practical reference architecture for securing LLM-generated SQL before execution, covering parser, catalog binding, policy engine, risk scoring, repair loops, and audit logs.<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[170,172,171],"tags":[169,178,164,180,181,165],"blocksy_meta":{"styles_descriptor":{"styles":{"desktop":"","tablet":"","mobile":""},"google_fonts":[],"version":5}},"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v19.4 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>LLM SQL Guard Architecture: Parser, Catalog, Policy Engine, Audit Log<\/title>\n<meta name=\"description\" content=\"LLM SQL Guard Architecture: Parser, Catalog, Policy Engine, Audit Log\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.dpriver.com\/blog\/llm-sql-guard-architecture-parser-catalog-policy-engine-audit-log\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"LLM SQL Guard Architecture: Parser, Catalog, Policy Engine, Audit Log\" \/>\n<meta property=\"og:description\" content=\"LLM SQL Guard Architecture: Parser, Catalog, Policy Engine, Audit Log\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.dpriver.com\/blog\/llm-sql-guard-architecture-parser-catalog-policy-engine-audit-log\/\" \/>\n<meta property=\"og:site_name\" content=\"SQL and Data Blog\" \/>\n<meta property=\"article:published_time\" content=\"2026-05-04T04:29:03+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-05-04T05:08:25+00:00\" \/>\n<meta name=\"author\" content=\"James\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"James\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"23 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Organization\",\"@id\":\"https:\/\/www.dpriver.com\/blog\/#organization\",\"name\":\"SQL and Data Blog\",\"url\":\"https:\/\/www.dpriver.com\/blog\/\",\"sameAs\":[],\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.dpriver.com\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/www.dpriver.com\/blog\/wp-content\/uploads\/2022\/07\/sqlpp-character.png\",\"contentUrl\":\"https:\/\/www.dpriver.com\/blog\/wp-content\/uploads\/2022\/07\/sqlpp-character.png\",\"width\":251,\"height\":72,\"caption\":\"SQL and Data Blog\"},\"image\":{\"@id\":\"https:\/\/www.dpriver.com\/blog\/#\/schema\/logo\/image\/\"}},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.dpriver.com\/blog\/#website\",\"url\":\"https:\/\/www.dpriver.com\/blog\/\",\"name\":\"SQL and Data Blog\",\"description\":\"SQL related blog for database professional\",\"publisher\":{\"@id\":\"https:\/\/www.dpriver.com\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.dpriver.com\/blog\/?s={search_term_string}\"},\"query-input\":\"required name=search_term_string\"}],\"inLanguage\":\"en-US\"},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.dpriver.com\/blog\/llm-sql-guard-architecture-parser-catalog-policy-engine-audit-log\/\",\"url\":\"https:\/\/www.dpriver.com\/blog\/llm-sql-guard-architecture-parser-catalog-policy-engine-audit-log\/\",\"name\":\"LLM SQL Guard Architecture: Parser, Catalog, Policy Engine, Audit Log\",\"isPartOf\":{\"@id\":\"https:\/\/www.dpriver.com\/blog\/#website\"},\"datePublished\":\"2026-05-04T04:29:03+00:00\",\"dateModified\":\"2026-05-04T05:08:25+00:00\",\"description\":\"LLM SQL Guard Architecture: Parser, Catalog, Policy Engine, Audit Log\",\"breadcrumb\":{\"@id\":\"https:\/\/www.dpriver.com\/blog\/llm-sql-guard-architecture-parser-catalog-policy-engine-audit-log\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.dpriver.com\/blog\/llm-sql-guard-architecture-parser-catalog-policy-engine-audit-log\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.dpriver.com\/blog\/llm-sql-guard-architecture-parser-catalog-policy-engine-audit-log\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/www.dpriver.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"LLM SQL Guard Architecture: Parser, Catalog, Policy Engine, Audit Log\"}]},{\"@type\":\"Article\",\"@id\":\"https:\/\/www.dpriver.com\/blog\/llm-sql-guard-architecture-parser-catalog-policy-engine-audit-log\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/www.dpriver.com\/blog\/llm-sql-guard-architecture-parser-catalog-policy-engine-audit-log\/\"},\"author\":{\"name\":\"James\",\"@id\":\"https:\/\/www.dpriver.com\/blog\/#\/schema\/person\/7bbdbb6e79c5dd9747d08c59d5992b04\"},\"headline\":\"LLM SQL Guard Architecture: Parser, Catalog, Policy Engine, Audit Log\",\"datePublished\":\"2026-05-04T04:29:03+00:00\",\"dateModified\":\"2026-05-04T05:08:25+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/www.dpriver.com\/blog\/llm-sql-guard-architecture-parser-catalog-policy-engine-audit-log\/\"},\"wordCount\":3365,\"publisher\":{\"@id\":\"https:\/\/www.dpriver.com\/blog\/#organization\"},\"keywords\":[\"ai-data-governance\",\"catalog-aware-validation\",\"llm-sql-guard\",\"sql-guardrails\",\"sql-policy-engine\",\"text-to-sql-security\"],\"articleSection\":[\"AI Data Governance\",\"Data Lineage\",\"SQL Parser\"],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/www.dpriver.com\/blog\/#\/schema\/person\/7bbdbb6e79c5dd9747d08c59d5992b04\",\"name\":\"James\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.dpriver.com\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/eeddf4ca7bdafa37ab025068efdc7302?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/eeddf4ca7bdafa37ab025068efdc7302?s=96&d=mm&r=g\",\"caption\":\"James\"},\"sameAs\":[\"http:\/\/www.dpriver.com\"],\"url\":\"https:\/\/www.dpriver.com\/blog\/author\/james\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"LLM SQL Guard Architecture: Parser, Catalog, Policy Engine, Audit Log","description":"LLM SQL Guard Architecture: Parser, Catalog, Policy Engine, Audit Log","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.dpriver.com\/blog\/llm-sql-guard-architecture-parser-catalog-policy-engine-audit-log\/","og_locale":"en_US","og_type":"article","og_title":"LLM SQL Guard Architecture: Parser, Catalog, Policy Engine, Audit Log","og_description":"LLM SQL Guard Architecture: Parser, Catalog, Policy Engine, Audit Log","og_url":"https:\/\/www.dpriver.com\/blog\/llm-sql-guard-architecture-parser-catalog-policy-engine-audit-log\/","og_site_name":"SQL and Data Blog","article_published_time":"2026-05-04T04:29:03+00:00","article_modified_time":"2026-05-04T05:08:25+00:00","author":"James","twitter_card":"summary_large_image","twitter_misc":{"Written by":"James","Est. reading time":"23 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Organization","@id":"https:\/\/www.dpriver.com\/blog\/#organization","name":"SQL and Data Blog","url":"https:\/\/www.dpriver.com\/blog\/","sameAs":[],"logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.dpriver.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/www.dpriver.com\/blog\/wp-content\/uploads\/2022\/07\/sqlpp-character.png","contentUrl":"https:\/\/www.dpriver.com\/blog\/wp-content\/uploads\/2022\/07\/sqlpp-character.png","width":251,"height":72,"caption":"SQL and Data Blog"},"image":{"@id":"https:\/\/www.dpriver.com\/blog\/#\/schema\/logo\/image\/"}},{"@type":"WebSite","@id":"https:\/\/www.dpriver.com\/blog\/#website","url":"https:\/\/www.dpriver.com\/blog\/","name":"SQL and Data Blog","description":"SQL related blog for database professional","publisher":{"@id":"https:\/\/www.dpriver.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.dpriver.com\/blog\/?s={search_term_string}"},"query-input":"required name=search_term_string"}],"inLanguage":"en-US"},{"@type":"WebPage","@id":"https:\/\/www.dpriver.com\/blog\/llm-sql-guard-architecture-parser-catalog-policy-engine-audit-log\/","url":"https:\/\/www.dpriver.com\/blog\/llm-sql-guard-architecture-parser-catalog-policy-engine-audit-log\/","name":"LLM SQL Guard Architecture: Parser, Catalog, Policy Engine, Audit Log","isPartOf":{"@id":"https:\/\/www.dpriver.com\/blog\/#website"},"datePublished":"2026-05-04T04:29:03+00:00","dateModified":"2026-05-04T05:08:25+00:00","description":"LLM SQL Guard Architecture: Parser, Catalog, Policy Engine, Audit Log","breadcrumb":{"@id":"https:\/\/www.dpriver.com\/blog\/llm-sql-guard-architecture-parser-catalog-policy-engine-audit-log\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.dpriver.com\/blog\/llm-sql-guard-architecture-parser-catalog-policy-engine-audit-log\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/www.dpriver.com\/blog\/llm-sql-guard-architecture-parser-catalog-policy-engine-audit-log\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.dpriver.com\/blog\/"},{"@type":"ListItem","position":2,"name":"LLM SQL Guard Architecture: Parser, Catalog, Policy Engine, Audit Log"}]},{"@type":"Article","@id":"https:\/\/www.dpriver.com\/blog\/llm-sql-guard-architecture-parser-catalog-policy-engine-audit-log\/#article","isPartOf":{"@id":"https:\/\/www.dpriver.com\/blog\/llm-sql-guard-architecture-parser-catalog-policy-engine-audit-log\/"},"author":{"name":"James","@id":"https:\/\/www.dpriver.com\/blog\/#\/schema\/person\/7bbdbb6e79c5dd9747d08c59d5992b04"},"headline":"LLM SQL Guard Architecture: Parser, Catalog, Policy Engine, Audit Log","datePublished":"2026-05-04T04:29:03+00:00","dateModified":"2026-05-04T05:08:25+00:00","mainEntityOfPage":{"@id":"https:\/\/www.dpriver.com\/blog\/llm-sql-guard-architecture-parser-catalog-policy-engine-audit-log\/"},"wordCount":3365,"publisher":{"@id":"https:\/\/www.dpriver.com\/blog\/#organization"},"keywords":["ai-data-governance","catalog-aware-validation","llm-sql-guard","sql-guardrails","sql-policy-engine","text-to-sql-security"],"articleSection":["AI Data Governance","Data Lineage","SQL Parser"],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/www.dpriver.com\/blog\/#\/schema\/person\/7bbdbb6e79c5dd9747d08c59d5992b04","name":"James","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.dpriver.com\/blog\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/eeddf4ca7bdafa37ab025068efdc7302?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/eeddf4ca7bdafa37ab025068efdc7302?s=96&d=mm&r=g","caption":"James"},"sameAs":["http:\/\/www.dpriver.com"],"url":"https:\/\/www.dpriver.com\/blog\/author\/james\/"}]}},"_links":{"self":[{"href":"https:\/\/www.dpriver.com\/blog\/wp-json\/wp\/v2\/posts\/3240"}],"collection":[{"href":"https:\/\/www.dpriver.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.dpriver.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.dpriver.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.dpriver.com\/blog\/wp-json\/wp\/v2\/comments?post=3240"}],"version-history":[{"count":1,"href":"https:\/\/www.dpriver.com\/blog\/wp-json\/wp\/v2\/posts\/3240\/revisions"}],"predecessor-version":[{"id":3241,"href":"https:\/\/www.dpriver.com\/blog\/wp-json\/wp\/v2\/posts\/3240\/revisions\/3241"}],"wp:attachment":[{"href":"https:\/\/www.dpriver.com\/blog\/wp-json\/wp\/v2\/media?parent=3240"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.dpriver.com\/blog\/wp-json\/wp\/v2\/categories?post=3240"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.dpriver.com\/blog\/wp-json\/wp\/v2\/tags?post=3240"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}