DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Newsletter
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Query SQL and NoSQL Databases Using Artificial Intelligence
  • PostgreSQL vs Oracle: When Higher Cost Means Better Performance
  • Grounding AI Agents in Governed Data
  • Engineering Self-Healing SQL Pipelines With LLMs: Validation, Guardrails, and Safe Recovery

Trending

  • Java Virtual Threads Under Load: Three Production Limits
  • How Cache Invalidation Caused Memory Pressure and API Failures at Scale
  • Node and Capacity Spillover: What Happens When Kubernetes Runs Out of Room
  • The LLM Evaluation Score Improved. Production Quality Didn't.
  1. DZone
  2. Coding
  3. Languages
  4. The English WHERE Clause: Plain-English Filters in Couchbase SQL++ Using Jev Model

The English WHERE Clause: Plain-English Filters in Couchbase SQL++ Using Jev Model

Learn how to add plain-English filters to Couchbase SQL++ with jev() and cb_decide(), returning typed answers for filtering, ranking, and classification.

By 
Keshav Murthy user avatar
Keshav Murthy
DZone Core CORE ·
Oct. 21, 26 · Tutorial
Likes (0)
Comment
Save
Tweet
Share
175 Views

Join the DZone community and get the full member experience.

Join For Free

Originally, SQL was SEQUEL — Structured English Query Language. Couchbase supports NL2SQL natively, but the WHERE clause only supports language-defined filters: range, semantic search, and text search. Exploiting Typesafe.ai's Jev models, we now show how you can easily add plain old English filters into the SQL++ WHERE clause. This is a step towards the original vision of making data processing natural and human. DuckDB showed Jev() functions, and Databricks added ai_decide() functions. For Couchbase, this article will show that two function families, jev() and cb_decide(), can be easily added to Couchbase and let a Couchbase query judge documents and return typed answers with the Couchbase Server! This method shows you how to use any model, not just Jev, in Couchbase SQL++. This article explains the sample UDFs and SQL++ published in the cb-jev-functions. In fact, Couchbase users have been doing it since 2018 to interact with external systems.

Ask a database for the products under $500, and the answer arrives before you finish the sentence. Ask it for the ones that would make a good gift for a frequent flyer, and you have a project.

Someone labels a few thousand examples. Someone trains a classifier, builds an embedding pipeline, or writes a prompt and a parser for the output. Weeks later, it works until the catalog changes. Nothing about the question is hard. A person could answer it by reading each description. The indexes support range predicates, text, or semantic searches.

Here is the same question as a Couchbase query with the jev() function:

SQL
 
SELECT name, price
FROM products
WHERE price < 500
  AND jev(products, 'a good gift for a frequent flyer');

Results:
[
  {
    "name": "Noise-Cancelling Headphones",
    "price": 299
  },
  {
    "name": "Leather Weekender Bag",
    "price": 450
  }
]


On the sample catalog in the project described below, you should expect the weekender bag, the noise-canceling headphones and the USB-C cable, and not the cookware set. Two details matter more than the result. The condition is a sentence, not a column or an index. And the function returns a boolean, not a paragraph.

That second detail is the whole story. A model that talks cannot sit in a WHERE clause. A model that returns types can. This article explains how that works, how to run it in Couchbase today with two function families, jev() and cb_decide(), and what architects should ask before this gets near production.

A Model That Answers in Types

On September 15, 2026, TypeSafe AI released Jev, which it calls a System One model. You send it a state (text or JSON) and a set of typed questions. It returns typed answers with calibrated probabilities and never writes a sentence. Each question type lines up with something a database already has:

Question type Answer SQL analogue
noul Probability that a statement is true, 0 to 1 A BOOLEAN predicate in WHERE
choice One option from a list you define (up to 255) An enum you can GROUP BY
score A position on an ordered rubric An ordinal you can ORDER BY


Noul is TypeSafe's name for the yes/no type. Its answer is a number such as 0.93, the probability that the statement is true. It is not a degree. A 0.9 for "a good gift for a frequent flyer" means the model is 90% sure, not that the product is 90% giftable. For degrees, ask a score question.

All questions in one request run on the qualified documents in the post-scan filter.  In the query above, the functions jev() run on all the documents that qualify for the (price < 500) predicate. TypeSafe quotes 70 to 500 ms end-to-end and $0.042 per million input tokens, with output tokens free. Treat those as vendor figures: the headline 190x speedup claims come from TypeSafe's own evaluations.

I've previously written on calling a chat model from Couchbase SQL++. What you got back was prose, which a query cannot sort, threshold, or join on. A number can be sorted, thresholded, and audited, and nothing needs parsing. That is the change. Prior to that, I've also written about Couchbase's ability to call an external REST endpoint. We now exploit the same mechanism. 

Databricks, DuckDB With Jev. 

Databricks released ai_decide in Beta on September 30. It takes text and one or more questions and returns a probability, a choice from named criteria, or a score. You call it from SQL for batch work or from a REST API for real-time use, and its launch examples are model routing, tagging reviews, and evaluating agents. Databricks has said the motivation was privacy: many enterprise customers could not send data to Jev, so ai_decide runs on governed data inside the platform. Keep that in mind. It is the first question an architect should ask.

The community moved too. pg-jev, a Postgres extension, introduced a jev() function that turns a plain-English condition into a WHERE clause, and several DuckDB ports kept its names: jev, jev_prob, jev_choice, and jev_score. The DuckDB team's write-up covers them.

Notice that the two camps chose different shapes. The pg-jev and DuckDB family asks one question per call and returns one value: a predicate, a probability, a label, or a score. ai_decide asks several questions in one call and returns a structured result. Both fit SQL. 

Couchbase can offer both, because the plumbing underneath is general. Because the infrastructure is open, you can do more with it. Right now!

The Plumbing Couchbase Already Had

CURL()

CURL() has been part of SQL++ since Couchbase 5.0. It calls an HTTP or HTTPS endpoint that returns JSON and gives you the response as data, usable in projections, WHERE clauses, or the FROM clause. In 2018, I used it to run Google's Vision, Translation, and Natural Language APIs against the travel-sample hotel reviews (Using Google Artificial Intelligence Services in Couchbase N1QL). The CURL() documentation sets the rules that matter for model calls:

  • The endpoint must return JSON, the method must be GET or POST, and redirects are not allowed. Responses are capped by the result-cap setting, 20 MB by default.
  • A Full Administrator must put a URL on the CURL() access list before anyone can call it, and matching is by prefix. Other users also need the QUERY_EXTERNAL_ACCESS role.

A JavaScript Function Around It

JavaScript user-defined functions arrived in Couchbase 7.0. You upload JavaScript to a library through the Query Functions REST API, expose a function from it with CREATE FUNCTION, and call it like a built-in. Inside the function, N1QL() runs a SQL++ statement, including SELECT CURL(...). In 2023, I used exactly this pattern to wrap OpenAI as a function called chatGPT3(). One practical rule: Couchbase JavaScript UDFs cannot rely on global variables, so each function declares its own constants.

Here is the core of cb_decide(). It is short because the work is in the question object you pass:

JavaScript
 
function cb_decide(state, questions) {
  var KEY = "your-typesafe-key";
  var URL = "https://api.typesafe.ai/v1/systemone";

  var opts = {
    "request": "POST",
    "header": ["Authorization: Bearer " + KEY, "Content-Type: application/json"],
    "data": JSON.stringify({ model: "jev-latest", state: state, questions: questions })
  };

  var q = N1QL("SELECT CURL($1, $2) AS r", [URL, opts]);
  var resp = null;
  for (const row of q) { resp = row.r; }
  q.close();

  if (!resp || !resp.answers) { throw new Error("cb_decide: " + JSON.stringify(resp)); }
  return resp.answers;
}


Three steps install it. The project's install.sh does the first two and injects your key at install time, so it never lands in source control:

Shell
 
# 1. Upload the library to the Query service
curl -u Administrator:password -X POST \
  http://localhost:8093/evaluator/v1/libraries/decide_lib \
  --data-binary @decide_lib.js

# 2. Allow the endpoint for CURL()
curl -u Administrator:password -X POST \
  http://localhost:8091/settings/querySettings/curlWhitelist \
  -d '{"all_access": false, "allowed_urls": ["https://api.typesafe.ai"]}'

-- 3. Expose the function to SQL++
CREATE OR REPLACE FUNCTION cb_decide(state, questions)
  LANGUAGE JAVASCRIPT AS "cb_decide" AT "decide_lib";


The access list can also be set in the Query Workbench under Settings. The REST endpoint is named curlWhitelist in the documentation.

Two Function Families, One Engine

Family Functions Shape Modeled on
jev jev, jev_prob, jev_choice, jev_score One question, one value, readable inside WHERE, ORDER BY, and GROUP BY pg-jev and the DuckDB extensions
cb_decide cb_decide, cb_decide_prob, cb_decide_choice, cb_decide_score Many questions in one request, returned as an object you can store Databricks ai_decide


The signature of cb_decide() is my own design. It follows the idea of ai_decide, not its exact syntax, and the project is not affiliated with Databricks, TypeSafe AI, or DuckDB.

jev(): one question, one value

Each function in this family bakes one question type into the wrapper, so a query reads like plain SQL. The sample collections are products and person.

SQL
 
-- Filter: a boolean predicate
SELECT name, price
FROM products
WHERE jev(products, 'a good gift for a frequent flyer');
[
  {
    "name": "Leather Weekender Bag",
    "price": 450
  },
  {
    "name": "Cashmere Overcoat",
    "price": 1800
  },
  {
    "name": "Noise-Cancelling Headphones",
    "price": 299
  }
]
-- Rank: a probability you can sort on
SELECT p.name, jev_prob(p, 'a good gift for a frequent flyer') AS prob
FROM products AS p
ORDER BY prob DESC;

[
  {
    "name": "Noise-Cancelling Headphones",
    "prob": 0.82
  },
  {
    "name": "Leather Weekender Bag",
    "prob": 0.75
  },
  {
    "name": "Cashmere Overcoat",
    "prob": 0.54
  },
  {
    "name": "Swiss Automatic Watch",
    "prob": 0.48
  },
... Results trucated for brevity here.

-- Classify, then group (the subquery computes each label once)
SELECT c.category, COUNT(*) AS n
FROM (
  SELECT jev_choice(p, 'which category fits best?',
                    ['travel gear', 'audio', 'apparel', 'kitchen']) AS category
  FROM products AS p
) AS c
GROUP BY c.category;
[
  {
    "category": "apparel",
    "n": 3
  },
  {
    "category": "audio",
    "n": 1
  },
  {
    "category": "kitchen",
    "n": 2
  },
  {
    "category": "travel gear",
    "n": 2
  }
]
-- Score: a number on an ordered rubric
SELECT p.name,
       jev_score(p, 'how luxurious is this product?',
                 ['budget', 'mid-range', 'premium', 'luxury']) AS luxury
FROM products AS p
ORDER BY luxury DESC;
[
  {
    "name": "Cashmere Overcoat",
    "luxury": 2.91
  },
  {
    "name": "Swiss Automatic Watch",
    "luxury": 2.72
  },
  {
    "name": "Leather Weekender Bag",
    "luxury": 2.02
  },
  {
    "name": "Noise-Cancelling Headphones",
    "luxury": 1.77
  },
...Results trucated for brevity


The score comes back as a number and can be fractional, so check one real response to confirm the scale before you rely on specific values.

cb_decide(): Many Questions, One Trip

Sometimes you want several judgments about the same document. Asking them one at a time means one round trip each. cb_decide() sends them together and returns an object with one answer per question name:

SQL
 
SELECT s.name,
       s.d.gift.noul        AS gift,
       s.d.category.choice  AS category,
       s.d.luxury.score     AS luxury
FROM (
  SELECT p.name,
         cb_decide(p, {
           "gift":     {"type": "noul",
                        "instructions": "Would this make a good gift for a frequent flyer?"},
           "category": {"type": "choice",
                        "instructions": "Which category fits best?",
                        "criteria": {"travel gear": "Bags and trip accessories",
                                     "audio":       "Headphones and speakers",
                                     "apparel":     "Clothing and accessories",
                                     "kitchen":     "Cookware and kitchen items"}},
           "luxury":   {"type": "score",
                        "instructions": "How luxurious is this product?",
                        "criteria": ["budget", "mid-range", "premium", "luxury"]}
         }) AS d
  FROM products AS p
) AS s
ORDER BY gift DESC;

[
  {
    "category": "audio",
    "gift": 0.88,
    "luxury": 1.75,
    "name": "Noise-Cancelling Headphones"
  },
  {
    "category": "travel gear",
    "gift": 0.79,
    "luxury": 2.03,
    "name": "Leather Weekender Bag"
  },
  {
    "category": "apparel",
    "gift": 0.63,
    "luxury": 2.9,
    "name": "Cashmere Overcoat"
  },
  {
    "category": "apparel",
    "gift": 0.62,
    "luxury": 2.76,
    "name": "Swiss Automatic Watch"
  },
  {
    "category": "travel gear",
    "gift": 0.52,
    "luxury": 0.01,
    "name": "USB-C Cable"
  },
  {
    "name": "Everyday Cotton T-Shirt",
    "gift": 0.36,
    "category": "apparel",
    "luxury": 0.02
  },
  {
    "category": "kitchen",
    "gift": 0.31,
    "luxury": 0,
    "name": "Plastic Water Bottle"
  },
  {
    "category": "kitchen",
    "gift": 0.17,
    "luxury": 0.92,
    "name": "Stainless Steel Cookware Set"
  }
]


A question object has a type, instructions, and, for choice and score, criteria. For a choice, criteria is an object of label to description. For a score, it is an ordered array of 2 to 10 levels.

This is also where the Databricks examples translate directly. Model routing, for instance, becomes one call that judges each prompt twice, then a CASE expression picks the tier:

SQL
 
SELECT s.prompt_id,
       s.d.reasoning.noul   AS needs_reasoning,
       s.d.difficulty.score AS difficulty,
       CASE WHEN s.d.reasoning.noul > 0.5 THEN "large-model" ELSE "small-model" END AS route
FROM (
  SELECT p.prompt_id,
         cb_decide(p.prompt, {
           "reasoning":  {"type": "noul",
                          "instructions": "Does answering this require multi-step reasoning?"},
           "difficulty": {"type": "score",
                          "instructions": "How difficult is this request?",
                          "criteria": ["trivial", "easy", "moderate", "hard"]}
         }) AS d
  FROM prompts AS p
) AS s;


The project's samples also include an agent-evaluation query that asks whether each answer follows a policy and how completely it answers the question.

The strongest use of cb_decide() is to decide once and keep the result. Write the whole decision object back to the document, then index the part you filter on:

SQL
 
UPDATE products AS p
SET p.decision = cb_decide(p, {
  "gift": {"type": "noul", "instructions": "Would this make a good gift for a frequent flyer?"}
})
WHERE p.decision IS MISSING
LIMIT 50;

CREATE INDEX idx_gift ON products(decision.gift.noul);

SELECT name FROM products WHERE decision.gift.noul > 0.8;


You pay for each document once. Later queries filter on an ordinary field that an index can serve. Run the UPDATE on a small batch first, and rerun it to continue, since the WHERE clause skips documents that already have a decision.

Which One Should I Use?

You need Use
A condition inside WHERE jev(doc, 'condition')
A number to sort or threshold jev_prob
One label to group on jev_choice, or cb_decide_choice
An ordered rating jev_score, or cb_decide_score
Several judgments about one document in a single request cb_decide
Decisions you will store, index and reuse cb_decide with an UPDATE


Can I Use a Different Model?

Yes, and that is the point of the design. A model is a function from JSON to JSON, and CURL() calls such functions over HTTPS. Changing the model means changing the URL and the request body inside one wrapper. The same pattern is called OpenAI in Select ChatGPT From SQL? You Bet! and Google's Vision, Translation and Natural Language APIs in the 2018 article above.

Try It

The cb-jev-functions project in couchbaselabs/agentic-apps holds two folders, cb-jev and cb-decide. Each has the library, an install script, sample data, runnable examples, and an offline test that needs no cluster. In either folder:

SQL
 
export TYPESAFE_API_KEY="your-typesafe-key"
export CB_PASSWORD="your-couchbase-admin-password"
./install.sh                      # uploads the library, allowlists the endpoint
# then run sql/01_register_functions.sql, sql/02_sample_data.sql and sql/03_examples.sql


Start with the sample data, not your own. Watch how many calls each query makes. Compare the labels against your own before you trust them. Jev was in early access behind a waitlist when the DuckDB team wrote about it, so check availability first.

Where This Is Heading

In 2024 I argued in Why Should Databases Go Natural? that databases need natural queries, and that translating English into SQL is a start, not the end state. Capella iQ takes that first step: you describe what you want, and it writes the SQL++ (iQ Interactive). jev() and cb_decide() take another step towards making the query natural and human. The English stays in the query as a typed predicate or a typed question, next to joins, GROUP BY, and window functions. The result is a boolean, an enum, or an ordinal, which SQL++ already knows how to handle.

References

  • CURL Function (Couchbase documentation)
  • JavaScript Functions for Query Reference (Couchbase documentation)
  • Query Functions REST API (Couchbase documentation)
  • Using Google Artificial Intelligence Services in Couchbase N1QL (ODBMS.org, 2018)
  • Select ChatGPT From SQL? You Bet! (DZone, 2023)
  • iQ Interactive: Cool Things for Developers on Couchbase Capella iQ (DZone, 2023)
  • Why Should Databases Go Natural? (DZone, 2024)
  • Introducing System One Models and Jev (TypeSafe AI)
  • Introducing ai_decide (Databricks)
  • Jev and DuckDB: Plain-English Conditions in SQL (DuckDB)


Images of Couchbase SQL++ With Jev functions

Couchbase SQL++ with Jev functions, image 1

Couchbase SQL++ with Jev functions, image 2


sql artificial intelligence

Opinions expressed by DZone contributors are their own.

Related

  • Query SQL and NoSQL Databases Using Artificial Intelligence
  • PostgreSQL vs Oracle: When Higher Cost Means Better Performance
  • Grounding AI Agents in Governed Data
  • Engineering Self-Healing SQL Pipelines With LLMs: Validation, Guardrails, and Safe Recovery

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook