Skip to main content

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



Originally, SQL was SEQUEL - Structured English Query Language.  Couchbase supports NL2SQL natively, but the WHERE clause is 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 SQL++ WHERE clause.  This is a progress 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 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 will show you how to exploit 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 jev() function:

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-cancelling 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 typeAnswerSQL analogue
noulProbability that a statement is true, 0 to 1A BOOLEAN predicate in WHERE
choiceOne option from a list you define (up to 255)An enum you can GROUP BY
scoreA position on an ordered rubricAn 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() runs 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 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:

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:

# 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

FamilyFunctionsShapeModeled on
jevjev, jev_prob, jev_choice, jev_scoreOne question, one value, readable inside WHERE, ORDER BY and GROUP BYpg-jev and the DuckDB extensions
cb_decidecb_decide, cb_decide_prob, cb_decide_choice, cb_decide_scoreMany questions in one request, returned as an object you can storeDatabricks 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.

-- 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:

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:

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:

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 needUse
A condition inside WHEREjev(doc, 'condition')
A number to sort or thresholdjev_prob
One label to group onjev_choice, or cb_decide_choice
An ordered ratingjev_score, or cb_decide_score
Several judgments about one document in a single requestcb_decide
Decisions you will store, index and reusecb_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 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:

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() takes 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


Images of Couchbase SQL++ with Jev functions.












Comments

Popular posts from this blog

MongoDB to Couchbase for Developers: Part 2: Database Objects

    MongoDB developers and DBAs work with physical clusters, machines, instances, storage systems, disks, etc. All MongoDB users, developers, and their applications work with logical entities: databases, collections, documents, fields, shards, users, data types.  There are a lot of similarities with Couchbase since both are document (JSON) oriented databases.  Let’s compare and contrast the two with respect to database objects.  Here’s the first part of this series comparing the architecture.   MongoDB Organization A full list of MongoDB schema objects is listed here and here . A database instance can have many databases; A database can have many collections; Each collection can have many indexes.  Each collection can be sharded based into multiple chunks on multiple nodes of a cluster using hash-sharding strategy or index sharding strategy.  The MongoDB indexes are local to their data.  Therefore, the indexes use the same strategy as ...

Swami Vivekananda: The Monk That Nobody Sent to Chicago

  There’s a saying in Chicago: “We don’t want nobody that nobody sent.” This was the cold reception Swami Vivekananda faced when he arrived in the windy city in July 1893, determined to attend the World Parliament of Religions that September. He belonged to no organization, carried no letter of recommendation, his countrymen were nobody, and represented an alien religion to the Western world. As the days passed, his hope of attending the parliament dwindled. With money running out and the odds stacked against him, he left the Windy City and went to Boston, praying for a glimmer of opportunity.  Swamiji came to America to share India’s most profound gift: the wisdom of the Hindu sages, preserved through centuries of oral tradition and embodied by its monks. This was 1893, not 1993—India was under the British grip, its resources drained, and its spirit subdued. Swamiji’s mission was not just a cultural exchange; it was a bold step toward envisioning a future where India could re...

MongoDB to Couchbase for Developers: Part 1: Architecture

  Introduction By any measure, MongoDB is a popular document-oriented JSON database. In the last dozen years, it has grown from its humble beginnings of a single lock per database to a modern multi-document transaction with snapshot isolation. MongoDB University has trained a large number of developers to develop the MongoDB database. There are many JSON databases now. While it's easy to start with MongoDB to learn NoSQL and flexible JSON schema, many customers choose Couchbase for performance, scale, and SQL. As you progress in your database evaluation and evolution, it's easier to start from what you know now. That's the idea for this series. Use your MongoDB knowledge to easily learn and eventually master Couchbase.  Don't despair if you don't know MongoDB do know any one of the RDBMS. This Oracle to Couchbase series will help you learn by comparison.  Many nations are known by their language: French, German, Spanish). Many databases are known for their language...