
When we shipped the first internal build of what would eventually become SynapCores, the query that convinced me we were on the right track was three lines long:
SELECT GENERATE('Summarize: ' || body)
FROM tickets
WHERE COSINE_SIMILARITY(embedding, EMBED('shipping delays')) > 0.7;
That query does in one SQL statement what would take a Python service, a vector database, and an LLM API in the traditional stack. The reason it can do that isn't because we wrote a magic optimizer — it's because we extended SQL itself to treat embedding, similarity, and generation as first-class operations rather than as foreign function calls.
That extension is what we call SQLv2. This post is the explanation of what SQLv2 is, why we think SQL needed a v2 in the first place, and what the dialect actually contains. The examples are SynapCores' implementation, but the broader argument — that SQL is the right query language for AI workloads if you grow it correctly — applies whether you adopt our dialect or someone else's.
SQL is 50 years old. Why now?
SQL was published in 1974, codified in 1986, and has been the most widely deployed query language on Earth ever since. It survived OOP, NoSQL, MapReduce, and the cloud. Every generation of "SQL is dead" predictions has been wrong because SQL — declarative set-oriented queries over typed tabular data — turns out to be a remarkably durable abstraction.
What's changed is the kind of data we put in those tables and the kind of operations we want to do on them. The original SQL surface had:
- Numeric and text types.
- Set operations (joins, unions, intersections).
- Aggregations.
- Sorting and projection.
Modern AI workloads add:
- Vector types (768-dimensional floats, not just scalars).
- Semantic similarity (not just equality and ranges).
- Embedding generation (compute the vector from text).
- Text generation (compute text from text via an LLM).
- Graph traversal (follow typed edges, not just JOIN keys).
- ML training and inference (CREATE MODEL, PREDICT).
You can graft these onto classic SQL via UDFs and extensions, and people have — pgvector, MADlib, PostGIS as a precedent for spatial types. But the grafting shows. Vector operators look like punctuation (<->, <=>). LLM calls become procedural Python wrappers. The optimizer doesn't understand any of it, so you can't fuse operations across the AI/SQL boundary.
SQLv2 is the position that AI primitives deserve to be part of the language, not extensions bolted on. The grammar gets a few new tokens; the optimizer gets new operator costs; the type system gets new primitives. That's it. It's still SQL — declarative, set-oriented, planner-driven. It just admits that 2026 SQL has different built-ins than 1986 SQL did.
The four pillars of SQLv2

The extensions are smaller than people expect. There are four conceptual additions:
1. Vector types and operators
A VECTOR(n) is a fixed-dimension float vector. Functions over it:
EMBED(text) -- text → vector (uses configured embedding model)
COSINE_SIMILARITY(v1, v2) -- vector × vector → float [-1, 1]
EUCLIDEAN_DISTANCE(v1, v2) -- vector × vector → float
DOT_PRODUCT(v1, v2) -- vector × vector → float
These behave like any other scalar function: planner-aware, indexable (via HNSW — see HNSW Explained), composable in WHERE, ORDER BY, SELECT, JOIN ON.
2. Generation and prediction functions
These are the big leap from classic SQL:
GENERATE(prompt, [model_name]) -- text → text via an LLM
PREDICT(model, features) -- features → prediction via in-DB model
EXTRACT_ENTITIES(text) -- text → set of (entity, type)
CLASSIFY(text, label_set) -- zero-shot classifier
The reason they belong in SQL is that they're just functions — pure-ish, side-effect-free (with caching), composable. The reason this is a big deal is that the planner can now reason about LLM calls: which rows really need them, can the call be batched, can we cache identical prompts.
For the deeper take on what changes when ML primitives are SQL functions, see What Is In-Database Machine Learning?.
3. Graph clauses in SQL
The third pillar is the addition of GRAPH MATCH (or MATCH ... CONNECTED BY, the syntax varies by implementation) as a clause that walks typed edges:
SELECT t.body, a.name, o.id
FROM tickets t
GRAPH MATCH (t)-[:FILED_BY]->(a:Account)-[:BELONGS_TO]->(o:Org)
WHERE o.industry = 'healthcare';
The grammar exposes the graph engine inside SQL. You can mix GRAPH MATCH with JOIN, WHERE, ORDER BY, LIMIT — the planner treats the graph walk as another join shape and optimizes accordingly. This is what makes GraphRAG expressible as one query instead of an orchestration of three.
4. Model DDL
CREATE MODEL is the DDL counterpart to CREATE TABLE:
CREATE MODEL fraud_scorer
TYPE classification
TARGET is_fraud
FROM transactions
WHERE created_at < '2026-05-01'
ALGORITHM autoML
WITH (max_trials = 50);
ALTER MODEL fraud_scorer RETRAIN
FROM transactions
WHERE created_at < CURRENT_DATE;
DROP MODEL fraud_scorer;
The model is a database object. It's versioned, audited, replicated, backed up, GRANT/REVOKE-able. This is what makes the model lifecycle survivable in production — see the in-DB ML post for the longer treatment.
A side-by-side: same query, classic SQL vs SQLv2
A small but realistic question: "For each open ticket about MRI integration, find the closest past resolution from any account in the same parent org, and let the LLM summarize what we did."
Classic SQL + extensions + Python service
-- Step 1: vector search in DB (pgvector style)
SELECT t.id, t.body
FROM tickets t
WHERE t.status = 'open'
AND t.embedding <-> (
SELECT embedding FROM keywords WHERE term = 'MRI integration'
) < 0.4
ORDER BY t.embedding <-> ... LIMIT 20;
# Step 2: pull rows, do graph traversal manually
for row in rows:
# query Neo4j for parent org
parent = neo4j.run("MATCH (a:Account {id: $a})-[:BELONGS_TO]->(o) RETURN o", a=row['account_id'])
# query past tickets in same org
past = neo4j.run("MATCH (o)<-[:BELONGS_TO]-(a2)<-[:FILED_BY]-(t2) WHERE o.id = $o RETURN t2", o=parent['id'])
# call vector DB again for closest past
closest = pinecone.query(vector=row['embedding'], filter={'id': [p['id'] for p in past]})
# call LLM
summary = openai.chat.completions.create(...)
# write back to DB
db.execute("INSERT INTO ticket_suggestions ...")
Three services, two languages, six round-trips per ticket. The Python is fine; the assembly is the bug surface.
SQLv2
WITH open_tickets AS (
SELECT t.id, t.body, t.embedding, t.account_id
FROM tickets t
WHERE t.status = 'open'
AND COSINE_SIMILARITY(t.embedding, EMBED('MRI integration')) > 0.6
)
INSERT INTO ticket_suggestions (ticket_id, summary)
SELECT ot.id,
GENERATE(
'Summarize what was done in this past resolution: ' || past_r.body
)
FROM open_tickets ot
GRAPH MATCH
(Account {id: ot.account_id})-[:BELONGS_TO]->(o:Org)
<-[:BELONGS_TO]-(:Account)
<-[:FILED_BY]-(past_t:Ticket)
-[:HAS_RESOLUTION]->(past_r:Resolution)
ORDER BY COSINE_SIMILARITY(past_t.embedding, ot.embedding) DESC
LIMIT 1;
One query, one transaction, one round-trip from the application. The query is longer than a classic SQL statement because it's doing more — but it's all in one engine, which means it can be optimized as one plan.
What the planner can do that a pipeline can't
The reason putting these in SQL matters isn't ergonomic. It's that the planner can now reason about the whole operation.
Three concrete optimizations the SynapCores planner does that a multi-service pipeline can't:
-
Predicate pushdown into the embedding step. If your query has
WHERE created_at > NOW() - INTERVAL '7 days', the planner can filter rows before callingEMBED(), saving thousands of embedding calls per query. A pipeline would call EMBED on every row, then filter. -
Batching across rows. When
GENERATE()is called over a set, the planner batches the LLM calls (5-50 per request) instead of issuing one HTTP per row. Latency drops by an order of magnitude for batch inference. -
Vector + graph fusion. When
GRAPH MATCHis followed byORDER BY COSINE_SIMILARITY(...), the planner can use the vector index during the graph walk instead of after, drastically reducing the candidate set.
None of these are exotic. They're standard planner optimizations applied to operations the planner wouldn't have known about in classic SQL.
What SQLv2 is not
A few things I want to make explicit because the phrase gets misused:
- It's not a replacement for SQL. Every classic SQL statement still works. SELECT, JOIN, WHERE, GROUP BY, HAVING, window functions — all unchanged. SQLv2 adds; it doesn't subtract.
- It's not vendor lock-in by another name. The four pillars (vector types, generation/prediction functions, graph clauses, model DDL) are conceptually present in multiple engines now — BigQuery ML, Redshift ML, Snowflake Cortex, Databricks SQL functions, our own SynapCores. The syntax varies; the category is converging.
- It's not "SQL with AI buzzwords." It's a small grammar extension and a real planner update. If a vendor claims SQLv2 features but the AI calls are just opaque UDFs the planner can't reason about, they're claiming the marketing without the engineering.
How we got the dialect
The SynapCores SQLv2 dialect was built around four design rules:
- If a real engineer would reach for a UDF, make it a function. Don't invent new syntax when a function call works.
- If the optimizer needs to know about it, make it a typed primitive. That's why
VECTOR(n)is a type, not aJSONBblob. - If it's a new lifecycle, make it DDL. Models get
CREATE MODEL, not procedural API calls, because lifecycles need ALTER/DROP/RENAME. - Stay set-oriented. Every new operator works over sets, not single rows. This is what keeps SQL recognizable as SQL.
The complete reference lives in our AI-Native Database SQL Reference post — that's the long-form spec. This post is the why.
For the comparative angle against vanilla Postgres, see SQLv2 vs PostgreSQL.
Adoption path
If you want to try SQLv2 without changing your stack: install the SynapCores Community Edition, point a DBeaver/psql client at it, and translate one of your existing AI queries. The translation is usually obvious — EMBED(text) replaces a Python service call; GRAPH MATCH replaces a recursive CTE or a Neo4j roundtrip; GENERATE() replaces an OpenAI HTTP call.
The honest version of this is: the queries get shorter, the deployment gets simpler, and the gotchas move. Instead of fighting four services to stay in sync, you fight one engine to express what you want. Different problem, smaller surface area.
The honest version
We don't have customer logos yet. SQLv2 is the dialect we built for ourselves because we got tired of writing the same pipeline glue across products. If you want help porting your AI queries into SQLv2 on your real data, the Agent Memory JumpStart is the 2-4 week sprint where we wire SynapCores into your workflow — including translating your existing Python orchestration into queries the planner can actually optimize. Free Design Partner track or fixed-fee Paid Pilot from $5,000.
For where SQLv2 stops being theoretical: download the CE and run SELECT GENERATE('Hello') once. That's the smallest, most boring SQLv2 query, and it should not work in classic SQL. That it does — without a Python service — is the whole point.