The query language built for AI agents — deterministic queries across any database with built-in privacy
Project description
NexaQL
The query language built for AI agents. One unified syntax to deterministically query, join, and aggregate data across any database — with built-in privacy enforcement.
NexaQL is not a GraphQL wrapper. It's a standalone query engine that translates natural language questions into a single, deterministic query representation, then compiles that into the native dialect of whatever database holds the data — PostgreSQL, MySQL, DuckDB, Snowflake, BigQuery, or any SQL engine. Data living across different databases? NexaQL joins it in memory. Sensitive fields? The policy engine strips or masks them before results leave the server.
"Which customers spent the most last quarter?"
↓ Agent (deterministic)
NexaQL query
↓ Translator
Native SQL dialect (PostgreSQL, MySQL, DuckDB, ...)
↓ Adapter
Execute → Results
↓ Policy enforcer
Stripped / masked per user role
Why NexaQL?
| Problem | How NexaQL solves it |
|---|---|
| Agents generate unreliable SQL | NexaQL is a constrained, deterministic intermediate language — the agent generates NexaQL (not raw SQL), and the engine compiles it to correct, optimized SQL |
| Data scattered across databases | One query can traverse and join data from PostgreSQL, MySQL, DuckDB in a single request |
| No access control on agent queries | Built-in RBAC, field-level security, row-level security, and PII masking — enforced at the engine level, invisible to the agent |
| Schema drift breaks queries | Ontology-driven validation catches errors before execution, not after |
| Every database has different SQL | NexaQL compiles to 8 SQL dialects from one syntax |
Quick Start
pip install nexaql
nexaql init
nexaql serve
Open http://localhost:3717 — a playground with sample e-commerce data loads instantly. No external database needed.
How It Works
1. Write a query (or let an agent generate one)
{
order(status: "DELIVERED") @orderby(ordered_at, DESC) @limit(10) {
id
ordered_at
total_amount
customer {
name
email
}
items {
quantity
unit_price
product {
name
}
}
}
}
This single query traverses 4 tables (orders → customers, orders → order_items → products) with automatic join resolution. No manual JOINs, no subqueries, no dialect-specific syntax.
2. The engine translates it
NexaQL compiles the query into the native SQL dialect of your database:
SELECT o0.id, o0.ordered_at, o0.total_amount,
c1.name, c1.email,
oi2.quantity, oi2.unit_price,
p3.name
FROM orders o0
JOIN customers c1 ON c1.id = o0.customer_id
LEFT JOIN order_items oi2 ON oi2.order_id = o0.id
JOIN products p3 ON p3.id = oi2.product_id
WHERE o0.status = 'DELIVERED'
ORDER BY o0.ordered_at DESC
LIMIT 10
3. Privacy policies are enforced automatically
If the user is an analyst, the engine silently strips sensitive fields before translation:
# In the ontology:
customer:
visible_to: [analyst, manager, admin]
fields:
email:
visible_to: [manager, admin] # analysts can't see emails
pii: true
mask_with: email # managers see "a***@example.com"
lifetime_value:
visible_to: [manager, admin] # analysts can't see spend data
The analyst's query returns name but not email or lifetime_value. The manager sees all fields but with masked emails. The admin sees everything. The agent doesn't need to know about access control — the engine handles it.
Features
Full grammar reference with 20+ examples: docs/grammar.md
Query Language
- Filters — equality, comparison (
gt,lt,gte,lte),like,in,not_in, null checks - Edge traversals — traverse relationships across tables with automatic join resolution
- Aggregations —
sum(),avg(),min(),max(),count()with automatic GROUP BY - Computed fields —
calc(quantity * unit_price)with cross-entity references - Directives —
@limit,@offset,@orderby,@distinct - Special filters — named, reusable WHERE clauses defined in the ontology
Privacy & Access Control
- Node-level RBAC — control which roles can access which tables
- Field-level security — strip sensitive columns based on user role
- Row-level security (RLS) — auto-inject WHERE clauses based on user attributes (region, department, etc.)
- PII masking — mask emails, phones, names in query results (5 strategies)
- Policy-in-ontology — access rules live alongside schema definitions
Agent Integration
- Deterministic translation — natural language → NexaQL → SQL, no hallucinated queries
- Auto-retry — if the generated query fails validation, the agent corrects it
- Result summarization — agent generates a natural language summary of query results
- Ontology-aware prompts — the agent knows your schema, field types, and available filters
Multi-Database
- 8 SQL dialects — PostgreSQL, MySQL, DuckDB, Snowflake, BigQuery, Presto, Spark, MSSQL
- Cross-source joins — data from different databases joined in memory via DuckDB
- Pluggable adapters — add new databases by implementing a simple interface
Developer Experience
- Playground UI — Monaco editor with syntax highlighting, schema explorer, SQL preview
- Role switcher — test access control live in the playground
- CLI —
nexaql query,nexaql serve,nexaql init - Zero config — ships with sample data, works after
pip install
Query Syntax Reference
Filters
# Equality
order(status: "DELIVERED")
# Comparison operators (object style)
order(total_amount: { gt: 100, lt: 1000 })
# Suffix shorthand
order(total_amount_gt: 100)
# Special filters (defined in ontology)
order(large_order: 500)
# Calc filters — computed conditions across entities
order_item(calc(quantity * unit_price): { gt: 500 })
Edge Traversals
{
customer {
name
orders { # edge → order table (auto-joined)
total_amount
items { # edge → order_item table
quantity
product { # edge → product table
name
price
}
}
}
}
}
Aggregations & Computed Fields
{
order {
customer_id
total_orders: count()
total_revenue: sum(total_amount)
avg_order: avg(total_amount)
}
}
{
order_item {
quantity
unit_price
line_total: calc(quantity * unit_price)
}
}
Access Control
Define privacy policies directly in the ontology:
nodes:
customer:
visible_to: [analyst, manager, admin] # anonymous users can't query this
row_policies:
- condition: "region = '{user.region}'" # managers see only their region
roles: [manager]
except_roles: [admin]
fields:
email:
visible_to: [manager, admin]
pii: true
mask_with: email
Test with the role switcher in the playground, or via API headers:
# As analyst — email and amounts stripped
curl -H 'X-User-Context: {"user_id":"1","roles":["analyst"]}' \
-X POST localhost:3717/api/execute \
-d '{"query": "{ customer @limit(5) { name email lifetime_value } }"}'
# As admin — full access
curl -H 'X-User-Context: {"user_id":"1","roles":["admin"]}' \
-X POST localhost:3717/api/execute \
-d '{"query": "{ customer @limit(5) { name email lifetime_value } }"}'
Configuration
ontology:
path: ./ontologies/my_schema.yaml
datasources:
default:
type: duckdb
path: ":memory:"
seed_file: ./ontologies/sample_seed.sql
# Or connect to your database:
# default:
# type: postgresql
# url: postgresql://user:pass@localhost:5432/mydb
llm:
provider: anthropic
api_key: ${ANTHROPIC_API_KEY}
model: claude-sonnet-4-20250514
server:
host: 0.0.0.0
port: 3717
Ontology
The ontology is the single source of truth for your data graph. It defines what NexaQL can query, how tables relate, and who can see what:
version: "1"
domain: ecommerce
nodes:
customer:
table: customers
primary_key: id
visible_to: [analyst, manager, admin]
fields:
id: { type: integer, filterable: true }
name: { type: string, filterable: true }
email: { type: string, filterable: true, visible_to: [manager, admin], pii: true, mask_with: email }
edges:
orders:
node: order
join_steps:
- table: orders
alias_key: order
condition: "{order}.customer_id = {customer}.id"
special_filters:
active:
sql: "{customer}.status = 'ACTIVE'"
Architecture
┌─────────────┐
Natural Language │ Agent Chat │ "Which customers spent the most?"
└──────┬──────┘
↓
┌─────────────┐
NexaQL Query │ Parser │ { customer @limit(10) { name, total: sum(amount) } }
└──────┬──────┘
↓
┌─────────────┐
Access Control │ Enforcer │ Strip fields, inject RLS, flag PII masking
└──────┬──────┘
↓
┌─────────────┐
Validation │ Validator │ Check against ontology schema
└──────┬──────┘
↓
┌─────────────┐
Native SQL │ Translator │ SELECT ... FROM ... JOIN ... WHERE ...
└──────┬──────┘
↓
┌─────────────┐
Execution │ Adapter │ PostgreSQL / MySQL / DuckDB / ...
└──────┬──────┘
↓
┌─────────────┐
Post-processing │ Masker │ PII masking on result rows
└─────────────┘
nexaql/
├── src/nexaql/
│ ├── engine/ # Pure query engine (zero external deps)
│ │ ├── lexer.py # Tokenizer
│ │ ├── parser.py # Recursive descent parser → AST
│ │ ├── validator.py # AST validation against ontology
│ │ ├── translator.py # AST → SQL with join resolution
│ │ └── dialect.py # 8 SQL dialect translators
│ ├── policy/ # RBAC, field security, RLS, PII masking
│ ├── ontology/ # YAML schema + access policy loader
│ ├── adapters/ # PostgreSQL, DuckDB (+ pluggable base)
│ ├── api/ # FastAPI server
│ ├── chat/ # NL → NexaQL agent pipeline
│ └── cli.py # CLI: serve, init, query
├── frontend/ # React + Tailwind playground (bundled)
└── ontologies/ # Sample schema + seed data
Development
git clone https://github.com/karthikr004/nexaql
cd nexaql
pip install -e ".[dev]"
nexaql serve --reload
License
Apache 2.0
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file nexaql-0.2.0.tar.gz.
File metadata
- Download URL: nexaql-0.2.0.tar.gz
- Upload date:
- Size: 232.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
278852568661b9f127d4a46d3fea2cbe192362ccf98ec0cae54b5b8e5825cecd
|
|
| MD5 |
edb4271411bdeb0c6b4623eca515ebb1
|
|
| BLAKE2b-256 |
a3cdc6021281cd2981d9d09e72340fe7ee33507e7478fd190b6361b78efc1e52
|
File details
Details for the file nexaql-0.2.0-py3-none-any.whl.
File metadata
- Download URL: nexaql-0.2.0-py3-none-any.whl
- Upload date:
- Size: 164.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
be14381c00ec1fc27271640aed0301a40b365d0e8ef623c2d47281463a7ca08b
|
|
| MD5 |
22b72481e42a446ad7190d103dac6157
|
|
| BLAKE2b-256 |
1866fa31ee4fe317a3d04f37ad664ff0b57898ed9c3c6f02b2269f0e1c489975
|