<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Maayan Levy</title>
    <description>The latest articles on DEV Community by Maayan Levy (@maayanlevy).</description>
    <link>https://dev.to/maayanlevy</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1629542%2F89c5b3e3-3dea-491d-ac2f-cf6b9dd7558f.png</url>
      <title>DEV Community: Maayan Levy</title>
      <link>https://dev.to/maayanlevy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/maayanlevy"/>
    <language>en</language>
    <item>
      <title>Jev was the tipping point - I built the last MySQL search statement</title>
      <dc:creator>Maayan Levy</dc:creator>
      <pubDate>Sun, 20 Sep 2026 11:14:52 +0000</pubDate>
      <link>https://dev.to/maayanlevy/why-i-built-a-mysql-plugin-that-understands-natural-language-46c1</link>
      <guid>https://dev.to/maayanlevy/why-i-built-a-mysql-plugin-that-understands-natural-language-46c1</guid>
      <description>&lt;p&gt;The problem that kept finding me was mapping unstructured data into an existing corpus. Supplier products that needed to match a catalog, text that described something already in the database, but with no ID to join on and no guaranteed format. You've been there too.&lt;/p&gt;

&lt;p&gt;The options were limited: a pile of LIKE conditions that break in reality, or an external pipeline that exports the data and embeds it somewhere else.&lt;/p&gt;

&lt;p&gt;So I built AILIKE. &lt;/p&gt;

&lt;p&gt;It lets you filter rows with natural-language conditions, right inside the query:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT * FROM film
WHERE description AILIKE 'The story takes place somewhere in Asia';
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And it does the thing I actually needed all along - comparing two values inside a JOIN, so the database itself can answer "do these two descriptions mean the same product?":&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT a.id AS supplier_product_id, b.id AS catalog_product_id
FROM supplier_products AS a
JOIN catalog_products AS b
  ON a.category_id = b.category_id
 AND ailike(a.description, b.description,
            'Left and right describe the same product type, allowing different wording.');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff1hjwt3908498ttujxxt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff1hjwt3908498ttujxxt.png" alt="Matching supplier products to a catalog with ailike inside a JOIN" width="800" height="583"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why inside MySQL
&lt;/h2&gt;

&lt;p&gt;Simplicity. The plugin installs into the server you already run, and then it's just SQL - nothing separate to deploy or run. There's a second reason: if an agent is writing your queries, it already knows SQL. A predicate inside the query is something it can use on its own, instead of you teaching it another layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Jev
&lt;/h2&gt;

&lt;p&gt;The conditions are judged by TypeSafe Jev, and this was the straightforward part. &lt;/p&gt;

&lt;p&gt;Jev does exactly what a filter predicate needs: hand it values and a natural-language condition, get back a judgment. The judgment comes back structured, in one pass. No prompt scaffolding, no parsing free text out of a chat model. &lt;br&gt;
The answer comes back shaped the way a query needs it. 1 for match, 0 for mismatch, NULL for null values.&lt;/p&gt;

&lt;h2&gt;
  
  
  The contract with your query planner
&lt;/h2&gt;

&lt;p&gt;Each uncached comparison makes one API call to Jev, which is an API call and will always be the heavy part of any SQL statement. &lt;/p&gt;

&lt;p&gt;So the deal is simple, narrow with ordinary SQL first - indexes, ranges, categories - and let Jev judge the survivors. The JOIN example above works because the category join cuts the candidate pairs down before a single call is made. Point it at a million-row table and you're paying for a million judgments (so don't :))&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6i353urw6l7hyz4olppp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6i353urw6l7hyz4olppp.png" alt="Filtering film descriptions with AILIKE" width="800" height="361"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The hardest part
&lt;/h2&gt;

&lt;p&gt;Making column AILIKE 'prompt' work safely inside an existing MySQL server. The rewrite must understand enough SQL to handle quotes, comments, identifiers, and precedence without changing unrelated queries. A particularly awkward constraint: MySQL's preparse rewrite resets recognition of bound parameters. That's why prepared queries must use the function form.&lt;/p&gt;

&lt;p&gt;Another subtle problem was failure handling: setting a MySQL UDF's error flag alone produces NULL. In a filter, that could silently exclude rows when the API fails. We explicitly raise a statement error instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why open source
&lt;/h2&gt;

&lt;p&gt;This code runs inside someone's database and sends selected values to an external service. Users should be able to inspect that behavior, build it themselves, fix problems, and maintain their own version. That makes openness useful to adoption and trust. The plugin is open source. Jev remains a hosted dependency.&lt;/p&gt;

&lt;p&gt;The code is at &lt;a href="https://github.com/maayanlevy/mysql-ailike" rel="noopener noreferrer"&gt;https://github.com/maayanlevy/mysql-ailike&lt;/a&gt;. If you try it, I'd like to know: where does it break for you?&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>mysql</category>
      <category>jev</category>
    </item>
  </channel>
</rss>
