Definition
OBQC stands for Ontology-Based Query Check. It is the deterministic safety layer in OrionBelt® Analytics (OBA) that validates SQL against the loaded RDF/OWL ontology before the query is allowed to reach the database.
OBQC makes no LLM calls and does no probabilistic reasoning. It parses the SQL with sqlglot, compares the query structure against the ontology's oba: annotations — tables, columns, SQL types, primary and foreign keys, join conditions, and relationship direction — and returns structured errors or warnings that the calling assistant can act on.
In one sentence: OBQC gives LLM-generated SQL a hard structural check against the ontology — errors block execution before the query reaches the database, warnings travel back with the result.
The Problem: Plausible SQL That Is Still Wrong
An LLM will happily produce SQL that reads perfectly and computes the wrong thing. The recurring failure modes are structural, not stylistic:
- Misspelled or hallucinated identifiers — a column that does not exist in that table, or a table that does not exist at all.
- Joins that do not follow the foreign keys — plausible-looking
ONconditions that pair the wrong columns. - Missing
GROUP BYcolumns — an aggregation that the database rejects, or worse, silently accepts with an arbitrary value. - Analytical fan-traps — the query runs, returns a number, and that number is inflated because rows multiplied before aggregation.
The first three fail loudly at some point. The last one does not: it produces a confident, wrong answer that looks like a correct one. That is the class of error a deterministic check is uniquely good at catching, and a language model is uniquely bad at.
How OBQC Works
OBQC runs inside the session, against the ontology OBA generated or loaded for the connected database:
- OBA connects to a database and discovers the schema.
generate_ontology()orload_my_ontology()creates or loads an ontology carryingoba:annotations for tables, columns, SQL types, primary keys, foreign keys, join conditions, and relationship direction.- OBA creates a session-local validator from that ontology.
- When
execute_sql_query()receives SQL, OBQC parses it with sqlglot. - OBQC extracts the referenced tables, columns, joins, aggregations, aliases, and join anchors.
- Those parts are validated against the ontology cache.
- If there are no blocking errors, OBA executes the query and attaches any warnings to the response.
connect_database()
-> discover_schema()
-> generate_ontology()
-> execute_sql_query()
-> OBQC parses SQL
-> OBQC checks ontology rules
-> errors block, warnings attach
-> database execution only if valid
execute_sql_query() is the only entry point that runs OBQC. If OBQC returns an error, OBA returns an obqc_error response instead of executing the SQL — the database never sees the query.
Severity: What Blocks and What Warns
| Severity | Effect on execution |
|---|---|
| Error | Query is blocked — never sent to the database. The response carries "success": false and the issue details. |
| Warning | Query executes normally. Warnings are attached to the response so the assistant can self-correct or tell the user. |
| Info | Informational note, no effect on execution. |
The overall is_valid flag follows one rule: any ERROR-level issue makes the result invalid.
What OBQC Checks
| Check | Severity | Purpose |
|---|---|---|
| Table existence | Error | Every table in FROM and JOIN must exist in the ontology. |
| Column existence | Error | Qualified and unqualified columns must resolve to real columns. |
| Ambiguous column | Warning | An unqualified name that exists in more than one referenced table. |
| Missing join condition | Error | Multiple tables in one SELECT with no JOIN … ON — a Cartesian product. |
| Non-matching join | Warning | A join condition that matches no declared foreign-key relationship. |
| Type compatibility | Warning | WHERE and ON comparisons should use compatible types. |
| Aggregation correctness | Error | Non-aggregated selected columns must appear in GROUP BY. |
| Fan-trap risk | Warning | Aggregation across two or more fan-out joins. |
Table and column existence
When a table is not found, OBQC suggests available table names from the ontology, so the assistant can correct the name rather than guess again.
-- ERROR: Table 'cusotmers' not found in ontology
SELECT * FROM cusotmers;
-- ERROR: Column 'emial' not found in table 'customers'
SELECT emial FROM customers;
Qualified references (table.column) are checked against that specific table. Unqualified references are resolved within their own scope — the SELECT they appear in, plus any enclosing ones, so a correlated subquery can still use the outer query's tables. A subquery's tables never resolve names in the outer query.
Join validity
-- ERROR: Multiple tables without explicit JOIN (Cartesian product)
SELECT * FROM customers, orders;
-- WARNING: JOIN condition may not match declared FK relationship
-- Suggested: orders.customer_id = customers.id
SELECT * FROM customers JOIN orders ON customers.name = orders.note;
The Cartesian-product rule is judged per SELECT, so SELECT id FROM customers WHERE id IN (SELECT customer_id FROM orders) is fine — one table per scope.
Aggregation correctness
-- ERROR: Column 'name' in SELECT with aggregation but no GROUP BY
SELECT name, COUNT(*) FROM customers;
-- ERROR: Column 'status' not in GROUP BY clause
SELECT status, region, SUM(amount) FROM orders GROUP BY region;
Grouping by an alias satisfies the check on the column it stands for, and the rule is evaluated per SELECT: an aggregate inside a subquery belongs to that subquery.
Fan-Trap Protection
A fan-trap happens when a query aggregates after joining across multiple one-to-many paths. The SQL runs successfully, but totals are inflated because rows were multiplied before aggregation.
-- WARNING: Potential fan-trap: 2 one-to-many joins with aggregation
SELECT c.name, SUM(o.amount), COUNT(r.id)
FROM customers c
JOIN orders o ON c.id = o.customer_id
JOIN reviews r ON c.id = r.customer_id
GROUP BY c.name;
OBQC detects this in two ways. It first uses ontology axioms such as owl:disjointWith for sibling fact tables that share a dimension. Where those axioms are absent, it falls back to relationship metadata and join direction to count fan-out joins in the actual query.
Direction is what makes this precise. Joining from a fact table to a dimension is a lookup and multiplies nothing; joining from a dimension out to multiple fact tables can multiply rows. OBQC judges fan-out per join, against the table that join's ON condition attaches to — not by asking whether a table sits on the many side of some relationship elsewhere in the schema. Walking a chain of many-to-one lookups (sales → clients → countries) is never flagged, however many foreign keys those dimensions carry.
When a fan-trap is flagged, OBQC suggests aggregating each fact table separately with UNION ALL, or pre-aggregating in CTEs before joining — the same correction the Composite Fact Layer (CFL) applies automatically in the OrionBelt® Semantic Layer.
What OBQC Deliberately Does Not Flag
Three things sit outside the rules, because treating them as violations blocked correct SQL:
- Database catalog schemas are exempt from the table rule.
information_schema,pg_catalog,system,performance_schema,snowflakeandsysdescribe the database itself, so they are never part of an ontology of user data. MySQL'smysqlschema is deliberately not exempt — it holds accounts and grants rather than metadata, and OBA's security layer blocks those tables outright. SELECTaliases are not columns.ORDER BY revenueoverSUM(total) AS revenueresolves to the select list. Where an alias may be referenced varies by database, so OBQC follows each dialect: PostgreSQL accepts one inGROUP BYandORDER BYbut notHAVING, while DuckDB resolves aliases in every clause includingWHERE.- Rules apply per
SELECT. Tables, columns, and aggregation in a subquery belong to that subquery. A name in one scope is not resolved against another — except that a correlated subquery may still see its enclosing query's tables.
A validator that cries wolf gets switched off. These exemptions exist so that every issue OBQC reports is worth reading.
How the AI Agent Uses OBQC
OBQC results are returned as structured data in the tool response, not rendered for the user. The calling LLM receives:
obqc_valid— overall pass/fail;obqc_issues— each issue with type, severity, message, location, and suggestion;fan_trap_risk— boolean flag;obqc_error_count/obqc_warning_count— summary counts.
When a query is blocked, the agent sees the error details and suggestions and can revise the SQL and retry — often without the user ever seeing the failed attempt. When warnings come back alongside successful results, the agent decides whether to surface them ("this query joins across two one-to-many relationships, which may inflate totals").
Requirements
OBQC relies on oba: namespace annotations in the ontology, which are included automatically by generate_ontology and by load_my_ontology for an OrionBelt®-generated ontology:
- Per OWL Class (table):
oba:tableName,oba:schemaName. - Per DatatypeProperty (column):
oba:columnName,oba:tableName,oba:sqlDataType,oba:isPrimaryKey,oba:isForeignKey, and anrdfs:rangeXSD type for the type-compatibility checks. - Per ObjectProperty (relationship):
oba:foreignKeyColumn,oba:referencedTable,oba:referencedColumn,oba:relationshipType(many_to_oneorone_to_many), andoba:sqlJoinCondition.
If a loaded ontology lacks these annotations — a generic OWL ontology, say — OBQC reports ontology_compatible: false and falls back to basic syntax checking only. The full annotation vocabulary is published at the oba: namespace.
Related OrionBelt® Concepts
- Governed Text-to-SQL — where OBQC sits in the generate-validate-execute loop.
- Composite Fact Layer (CFL) — the Semantic Layer's answer to the same fan-trap problem: multi-fact queries decomposed and recomposed so they are correct by construction rather than merely checked.
- Agentic AI Data Access — why agents need deterministic guardrails around a probabilistic generator.
- Glossary — all coined terms in one place.
Frequently Asked Questions
What does OBQC stand for?
Ontology-Based Query Check — the deterministic safety layer in OrionBelt® Analytics (OBA) that checks SQL against the loaded RDF/OWL ontology before the query reaches the database.
Does OBQC use an LLM?
No. OBQC makes no LLM calls and does no probabilistic reasoning. It parses the SQL with sqlglot, compares the structure against the ontology, and returns structured errors and warnings. The guarantees are structural — which is exactly what LLM-generated SQL lacks.
What does OBQC check?
Table existence, column existence, ambiguous unqualified columns, join validity (missing join conditions and joins that do not match a declared foreign key), type compatibility in WHERE and ON comparisons, aggregation correctness, and fan-trap risk when a query aggregates across two or more one-to-many joins.
What is the difference between an error and a warning?
An error blocks execution: the query is never sent to the database and the tool returns the issue details instead. A warning lets the query run and is attached to the response, so the agent can revise it or explain the caveat. Info issues are informational only.
How does OBQC detect a fan-trap?
First through ontology axioms such as owl:disjointWith for sibling fact tables that share a dimension. Where those are absent, through relationship metadata and join direction — judging fan-out per join, against the table that join's ON condition attaches to, so a chain of many-to-one lookups is not flagged.
What does OBQC need in order to run?
An ontology carrying oba: namespace annotations, produced by generate_ontology or loaded with load_my_ontology. Without one — or with a generic OWL ontology lacking those annotations — OBQC reports ontology_compatible: false and falls back to basic syntax checking only.