<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="atom.xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://docs.gnok.io/blog</id>
    <title>Gnok Blog</title>
    <updated>2026-09-04T00:00:00.000Z</updated>
    <generator>https://github.com/jpmonette/feed</generator>
    <link rel="alternate" href="https://docs.gnok.io/blog"/>
    <subtitle>Gnok Blog</subtitle>
    <icon>https://docs.gnok.io/img/gnok-icon.svg</icon>
    <entry>
        <title type="html"><![CDATA[One ALTER TABLE for Millisecond Writes]]></title>
        <id>https://docs.gnok.io/blog/2026/09/04/low-latency-writes</id>
        <link href="https://docs.gnok.io/blog/2026/09/04/low-latency-writes"/>
        <updated>2026-09-04T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[A single-row INSERT into an Iceberg table is a strange thing to]]></summary>
        <content type="html"><![CDATA[<p>A single-row <code>INSERT</code> into an Iceberg table is a strange thing to
benchmark. The row is a few dozen bytes; the commit that lands it
rewrites metadata, swaps a pointer, and waits for the catalog to say
yes. On our own cluster that costs about <strong>712 ms</strong>, and no amount of
tuning changes the shape of it — you are paying for a catalog
transaction, once per statement, whatever the statement contains.</p>
<p>That is fine for the workload Iceberg was built for. It is not fine
for an application that edits one order, appends one comment, or
increments one counter and then wants to read it back.</p>
<p>Gnok now takes a different path for those writes, and turning it on
is one line of DDL:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ALTER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TABLE</span><span class="token plain"> orders </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SET</span><span class="token plain"> TBLPROPERTIES </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'gnok.write.mode'</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'command'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<p>That write now acknowledges in <strong>5–7 ms</strong>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="where-the-time-goes">Where the time goes<a href="https://docs.gnok.io/blog/2026/09/04/low-latency-writes#where-the-time-goes" class="hash-link" aria-label="Direct link to Where the time goes" title="Direct link to Where the time goes" translate="no">​</a></h2>
<p>Three paths exist, and the difference between them is <em>when the write
is durable enough to acknowledge</em>.</p>
<table><thead><tr><th>Path</th><th>What it does</th><th>p50</th></tr></thead><tbody><tr><td><code>direct</code> (default)</td><td>Commits to the catalog per statement</td><td>712 ms</td></tr><tr><td>staged</td><td>Buffers small writes, commits in the background</td><td>12–15 ms</td></tr><tr><td><code>command</code></td><td>Commits to a replicated log, publishes to Iceberg after</td><td><strong>5–7 ms</strong></td></tr></tbody></table>
<p>The last one is not a cache and not a queue you can lose. The write is
replicated before it is acknowledged; Gnok publishes it into Iceberg in
the background shortly after. What changed is the order of those two
facts, not whether both happen.</p>
<p>The 712 ms is per <em>statement</em>, not per row — a bulk load of ten million
rows pays it once and does not care. This is about the small write, and
the small write is what an application does all day.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-the-table-has-to-say">What the table has to say<a href="https://docs.gnok.io/blog/2026/09/04/low-latency-writes#what-the-table-has-to-say" class="hash-link" aria-label="Direct link to What the table has to say" title="Direct link to What the table has to say" translate="no">​</a></h2>
<p>The mode lives on the table, because it is a fact about the data:</p>
<table><thead><tr><th>Mode</th><th>Behavior</th></tr></thead><tbody><tr><td><code>direct</code> (default)</td><td>Ordinary Iceberg DML</td></tr><tr><td><code>command</code></td><td>Low-latency path; ordinary DML still permitted</td></tr><tr><td><code>command_only</code></td><td>Low-latency path; out-of-band DML refused</td></tr></tbody></table>
<p>The middle value is the one that makes a migration possible. While you
are moving writers over, both paths must work on the same table. When
the last writer has moved, <code>command_only</code> shuts the door so nothing can
modify the table behind the engine's back.</p>
<p>There is no service setting to change and nothing to keep in step
elsewhere: every part of Gnok reads the mode from the same place you
wrote it.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-a-fast-write-has-to-prove">What a fast write has to prove<a href="https://docs.gnok.io/blog/2026/09/04/low-latency-writes#what-a-fast-write-has-to-prove" class="hash-link" aria-label="Direct link to What a fast write has to prove" title="Direct link to What a fast write has to prove" translate="no">​</a></h2>
<p>A write that is acknowledged before it reaches the table has to be
replayable exactly once, and has to know which row it belongs to. So
the engine asks three things of the statement:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BEGIN</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SET</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">LOCAL</span><span class="token plain"> gnok</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">idempotency_key </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'01a06867-04e0-7f34-a545-d82c7e40e656'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SET</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">LOCAL</span><span class="token plain"> gnok</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">command_type    </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'order.edit'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain">      </span><span class="token comment" style="color:rgb(98, 114, 164)">-- optional label</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SET</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">LOCAL</span><span class="token plain"> gnok</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">expected_version </span><span class="token operator">=</span><span class="token plain"> </span><span class="token number">4</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain">                </span><span class="token comment" style="color:rgb(98, 114, 164)">-- optional compare-and-swap</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">MERGE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INTO</span><span class="token plain"> orders x</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">USING</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> CAST</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">1001</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BIGINT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> order_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">              CAST</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">99.50</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DECIMAL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">10</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token number">2</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> amount</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> s</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">   </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ON</span><span class="token plain"> x</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">order_id </span><span class="token operator">=</span><span class="token plain"> s</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">order_id</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHEN</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">MATCHED</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">THEN</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">UPDATE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SET</span><span class="token plain"> amount </span><span class="token operator">=</span><span class="token plain"> s</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">amount</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHEN</span><span class="token plain"> </span><span class="token operator">NOT</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">MATCHED</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">THEN</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INSERT</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">order_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> amount</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">VALUES</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">s</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">order_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> s</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">amount</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">COMMIT</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">RETURNING</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<p>An explicit transaction with an idempotency key — that key is the
opt-in, and a retry with the same key returns the original answer
rather than writing twice. A full-primary-key <code>MERGE</code> with every column
assigned, so the row's identity and its post-image are both known
without reading the table first. And a target the write path owns.</p>
<p>The <code>expected_version</code> carrier is optional and does what a
compare-and-swap does: if the row moved since you read it, the write is
refused as a stale version and your retry loop re-reads.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="reads-see-their-own-writes">Reads see their own writes<a href="https://docs.gnok.io/blog/2026/09/04/low-latency-writes#reads-see-their-own-writes" class="hash-link" aria-label="Direct link to Reads see their own writes" title="Direct link to Reads see their own writes" translate="no">​</a></h2>
<p>The obvious objection to acknowledging early is that the next query
will not see the row. It does. A read against a <code>command</code> table merges
in the rows that are ordered but not yet published, so read-your-writes
holds without waiting for the catalog. <code>COMMIT RETURNING</code> hands back a
commit token if you want to require a specific write to be visible on a
particular read.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-gets-refused-and-why-that-is-the-point">What gets refused, and why that is the point<a href="https://docs.gnok.io/blog/2026/09/04/low-latency-writes#what-gets-refused-and-why-that-is-the-point" class="hash-link" aria-label="Direct link to What gets refused, and why that is the point" title="Direct link to What gets refused, and why that is the point" translate="no">​</a></h2>
<p>Some statements cannot be replayed exactly once, and the engine says so
instead of quietly taking the slow path:</p>
<ul>
<li class="">a <code>MERGE</code> that leaves columns unassigned — the post-image is unknown;</li>
<li class="">a <code>MERGE</code> whose <code>ON</code> clause is not the whole primary key — the row's
identity is unknown;</li>
<li class="">an <code>UPDATE … WHERE</code> over a predicate — it may match anything;</li>
<li class="">two writes to the same table in one transaction — the second would
have to read the first's uncommitted effect;</li>
<li class="">any transaction touching a table outside the write path — refused
whole, before anything is admitted, because an acknowledged write
that cannot be published would block every write behind it.</li>
</ul>
<p>Each refusal names what could not be proven and what to change. A write
that is admitted is a write the engine can land.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="when-to-leave-it-alone">When to leave it alone<a href="https://docs.gnok.io/blog/2026/09/04/low-latency-writes#when-to-leave-it-alone" class="hash-link" aria-label="Direct link to When to leave it alone" title="Direct link to When to leave it alone" translate="no">​</a></h2>
<p><code>direct</code> is still right for most tables. Bulk loads amortize the
catalog commit to nothing. Analytics tables often have no primary key,
so there is no identity to key a command on. The low-latency path is
for the transactional core of an application — the tables it edits one
row at a time and reads back immediately — and for those, the
difference between 712 ms and 6 ms is the difference between a
background job and an interactive one.</p>]]></content>
        <author>
            <name>Gnok Team</name>
        </author>
        <category label="Engineering" term="Engineering"/>
        <category label="Performance" term="Performance"/>
        <category label="Iceberg" term="Iceberg"/>
        <category label="Insert" term="Insert"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Build an Incremental Inventory Change Feed with Gnok CDC Streams]]></title>
        <id>https://docs.gnok.io/blog/2026/08/27/inventory-cdc-streams</id>
        <link href="https://docs.gnok.io/blog/2026/08/27/inventory-cdc-streams"/>
        <updated>2026-08-27T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Use native Iceberg-backed streams to inspect and consume inserts, updates, and deletes without rescanning an entire table.]]></summary>
        <content type="html"><![CDATA[<p>An inventory table may contain millions of products, but a downstream stock monitor usually needs only the rows that changed. Repeatedly scanning the full table wastes compute and makes deletes especially difficult to detect.</p>
<p>Gnok CDC streams turn the Iceberg snapshots behind a table into a bounded change feed. In this tutorial, we will create an inventory stream, inspect inserts, updates, and deletes, and acknowledge a batch after processing it.</p>
<p>This is <strong>outbound table CDC</strong>: the source data already lives in Gnok. For inbound data, see <a class="" href="https://docs.gnok.io/docs/advanced/streaming-ingestion">Streaming Ingestion</a>. For historical snapshot ranges without a managed watermark, see <a class="" href="https://docs.gnok.io/docs/advanced/time-travel">Snapshots, Time Travel, and Rollback</a>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-use-case">The Use Case<a href="https://docs.gnok.io/blog/2026/08/27/inventory-cdc-streams#the-use-case" class="hash-link" aria-label="Direct link to The Use Case" title="Direct link to The Use Case" translate="no">​</a></h2>
<p>Suppose a retailer stores its current warehouse stock in an Iceberg table. Inventory writes continue normally, while an operations application periodically needs to answer:</p>
<ul>
<li class="">Which products were added?</li>
<li class="">Which stock levels changed?</li>
<li class="">Which products were removed?</li>
<li class="">Has anything changed since the previous acknowledged batch?</li>
</ul>
<p>The stream stores a watermark, not a second copy of the table. Gnok derives changes between that watermark and a fixed current Iceberg snapshot when the stream is queried.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="create-the-inventory-table">Create the Inventory Table<a href="https://docs.gnok.io/blog/2026/08/27/inventory-cdc-streams#create-the-inventory-table" class="hash-link" aria-label="Direct link to Create the Inventory Table" title="Direct link to Create the Inventory Table" translate="no">​</a></h2>
<p>Create a catalog, schema, and table for the example:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CREATE</span><span class="token plain"> CATALOG retail</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SCHEMA</span><span class="token plain"> retail</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">inventory</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TABLE</span><span class="token plain"> retail</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">inventory</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">stock_levels </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  sku </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">VARCHAR</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">PRIMARY</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">KEY</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  warehouse </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">VARCHAR</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  on_hand </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BIGINT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  updated_at </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TIMESTAMP</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<p>The primary key becomes an Iceberg identifier field. That gives CDC a stable logical row identity, which allows Gnok to recognize an update as a related <code>DELETE</code> and <code>INSERT</code> pair.</p>
<p>Seed the current state before creating the stream:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INSERT</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INTO</span><span class="token plain"> retail</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">inventory</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">stock_levels</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">sku</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> warehouse</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> on_hand</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> updated_at</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">VALUES</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'SKU-100'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'east'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">12</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TIMESTAMP</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'2026-08-27 09:00:00'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'SKU-200'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'west'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain">  </span><span class="token number">5</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TIMESTAMP</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'2026-08-27 09:00:00'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="start-tracking-changes">Start Tracking Changes<a href="https://docs.gnok.io/blog/2026/08/27/inventory-cdc-streams#start-tracking-changes" class="hash-link" aria-label="Direct link to Start Tracking Changes" title="Direct link to Start Tracking Changes" translate="no">​</a></h2>
<p>Create a standard stream in the same catalog and schema as its source table:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CREATE</span><span class="token plain"> STREAM retail</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">inventory</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">stock_changes</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ON</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TABLE</span><span class="token plain"> retail</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">inventory</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">stock_levels</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<p>By default, the current table snapshot becomes the stream's starting watermark. The two seed rows therefore establish the baseline and do not appear as new changes.</p>
<p>Now simulate normal inventory activity:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INSERT</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INTO</span><span class="token plain"> retail</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">inventory</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">stock_levels</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">sku</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> warehouse</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> on_hand</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> updated_at</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">VALUES</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'SKU-300'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'east'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">20</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TIMESTAMP</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'2026-08-27 09:05:00'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">UPDATE</span><span class="token plain"> retail</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">inventory</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">stock_levels</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SET</span><span class="token plain"> on_hand </span><span class="token operator">=</span><span class="token plain"> </span><span class="token number">9</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    updated_at </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TIMESTAMP</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'2026-08-27 09:06:00'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHERE</span><span class="token plain"> sku </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'SKU-100'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DELETE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> retail</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">inventory</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">stock_levels</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHERE</span><span class="token plain"> sku </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'SKU-200'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<p>Each committed write creates a new Iceberg snapshot. The source table continues to represent current inventory; the stream represents the changes since its watermark.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="check-and-preview-the-pending-batch">Check and Preview the Pending Batch<a href="https://docs.gnok.io/blog/2026/08/27/inventory-cdc-streams#check-and-preview-the-pending-batch" class="hash-link" aria-label="Direct link to Check and Preview the Pending Batch" title="Direct link to Check and Preview the Pending Batch" translate="no">​</a></h2>
<p>An inexpensive availability check can prevent polling clients from running an empty change query:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> SYSTEM$STREAM_HAS_DATA</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token string" style="color:rgb(255, 121, 198)">'retail.inventory.stock_changes'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<p>It returns one <code>has_data</code> value, which is now <code>true</code>.</p>
<p>Preview the pending changes with an ordinary <code>SELECT</code>:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  sku</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  warehouse</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  on_hand</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  METADATA$</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ACTION</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  METADATA$ISUPDATE</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  METADATA$ROW_ID</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> retail</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">inventory</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">stock_changes</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ORDER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> sku</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> METADATA$</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ACTION</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<p>The result has this shape:</p>
<table><thead><tr><th><code>sku</code></th><th><code>warehouse</code></th><th style="text-align:right"><code>on_hand</code></th><th><code>METADATA$ACTION</code></th><th><code>METADATA$ISUPDATE</code></th><th><code>METADATA$ROW_ID</code></th></tr></thead><tbody><tr><td>SKU-100</td><td>east</td><td style="text-align:right">12</td><td>DELETE</td><td>true</td><td>same ID as next row</td></tr><tr><td>SKU-100</td><td>east</td><td style="text-align:right">9</td><td>INSERT</td><td>true</td><td>same ID as previous row</td></tr><tr><td>SKU-200</td><td>west</td><td style="text-align:right">5</td><td>DELETE</td><td>false</td><td>stable row ID</td></tr><tr><td>SKU-300</td><td>east</td><td style="text-align:right">20</td><td>INSERT</td><td>false</td><td>stable row ID</td></tr></tbody></table>
<p>Iceberg represents an update as removal of the old row plus insertion of the new row. <code>METADATA$ISUPDATE = true</code> and the shared row ID let a consumer pair those two records. A standalone delete or insert has <code>METADATA$ISUPDATE = false</code>.</p>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>Previewing is non-consuming</div><div class="admonitionContent_BuS1"><p>Run the same <code>SELECT</code> again and it returns the same pending batch. Ordinary stream queries never advance the watermark, so they are safe for inspection, validation, and debugging.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="consume-the-batch">Consume the Batch<a href="https://docs.gnok.io/blog/2026/08/27/inventory-cdc-streams#consume-the-batch" class="hash-link" aria-label="Direct link to Consume the Batch" title="Direct link to Consume the Batch" translate="no">​</a></h2>
<p>After the consumer is ready to acknowledge this bounded batch, use the explicit consuming statement:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">CONSUME STREAM retail</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">inventory</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">stock_changes</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<p><code>CONSUME STREAM</code> returns the pending rows and then advances the watermark with a compare-and-swap operation. The watermark advances only after the change query succeeds. A concurrent consumer cannot silently overwrite a different watermark; one of the operations fails and must be retried.</p>
<p>Confirm that the stream is current:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> SYSTEM$STREAM_HAS_DATA</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token string" style="color:rgb(255, 121, 198)">'retail.inventory.stock_changes'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> </span><span class="token operator">*</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> retail</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">inventory</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">stock_changes</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<p>The availability check now returns <code>false</code>, and the preview returns no rows. Future table commits create a new pending batch.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="choose-the-right-delivery-pattern">Choose the Right Delivery Pattern<a href="https://docs.gnok.io/blog/2026/08/27/inventory-cdc-streams#choose-the-right-delivery-pattern" class="hash-link" aria-label="Direct link to Choose the Right Delivery Pattern" title="Direct link to Choose the Right Delivery Pattern" translate="no">​</a></h2>
<p>The stream watermark is transactional inside Gnok, but it cannot participate in the transaction of an unrelated external database, search index, or message broker. That boundary matters when designing a production consumer.</p>
<ul>
<li class="">Use ordinary <code>SELECT</code> for repeatable previews and validation. It never acknowledges data.</li>
<li class="">Use <code>CONSUME STREAM</code> when returning the change result is itself the consumption step, or when the surrounding application can reconcile its destination.</li>
<li class="">Make downstream operations idempotent where possible. <code>METADATA$ROW_ID</code>, <code>METADATA$ACTION</code>, and the business key are useful deduplication inputs.</li>
<li class="">Treat a lost client response as ambiguous: the server may have returned the rows and committed the watermark even if the client did not receive the final response.</li>
<li class="">For an external sink that requires replayable, application-managed checkpoints, use explicit <code>READ CHANGES ... BETWEEN SNAPSHOT ...</code> ranges and persist the completed snapshot boundary in that application.</li>
</ul>
<p>Gnok does not currently make a separate <code>INSERT ... SELECT FROM &lt;stream&gt;</code> or <code>MERGE ... USING &lt;stream&gt;</code> atomic with stream advancement. Do not describe that two-system workflow as exactly-once without an additional transactional handoff design.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="production-checklist">Production Checklist<a href="https://docs.gnok.io/blog/2026/08/27/inventory-cdc-streams#production-checklist" class="hash-link" aria-label="Direct link to Production Checklist" title="Direct link to Production Checklist" translate="no">​</a></h2>
<p>Before using a stream in a production workflow:</p>
<ol>
<li class=""><strong>Grant least privilege.</strong> The principal needs <code>CREATE STREAM</code> on the schema and read access to the source table. Stream reads re-check source-table authorization.</li>
<li class=""><strong>Use identifier fields.</strong> A primary key or valid Iceberg identifier fields provide stable row IDs and update pairing. Without them, changes remain valid insert/delete records, but updates cannot be distinguished from unrelated deletes and inserts.</li>
<li class=""><strong>Keep the stream beside its source.</strong> The stream and table must currently share a catalog and schema.</li>
<li class=""><strong>Retain required snapshots.</strong> A stream derives its delta from Iceberg snapshot history. Coordinate snapshot expiration with the oldest live stream watermark.</li>
<li class=""><strong>Monitor the watermark.</strong> Use <code>SHOW STREAMS ON TABLE retail.inventory.stock_levels</code> and <code>SYSTEM$STREAM_HAS_DATA</code> to observe lag and pending work.</li>
<li class=""><strong>Advance deliberately.</strong> <code>ALTER STREAM ... ADVANCE</code> discards the currently pending batch by moving the watermark without returning rows. Reserve it for an intentional skip or recovery procedure.</li>
<li class=""><strong>Avoid competing consumers.</strong> Compare-and-swap prevents silent watermark corruption, but independent consumers should generally have independent streams.</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="clean-up">Clean Up<a href="https://docs.gnok.io/blog/2026/08/27/inventory-cdc-streams#clean-up" class="hash-link" aria-label="Direct link to Clean Up" title="Direct link to Clean Up" translate="no">​</a></h2>
<p>When the example is no longer needed:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DROP</span><span class="token plain"> STREAM retail</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">inventory</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">stock_changes</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DROP</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TABLE</span><span class="token plain"> retail</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">inventory</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">stock_levels</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<p>For syntax, stream variants, operational limits, and task scheduling, see the <a class="" href="https://docs.gnok.io/docs/operations/streams-and-tasks">Streams &amp; Tasks guide</a>.</p>]]></content>
        <author>
            <name>Gnok Team</name>
        </author>
        <category label="Tutorial" term="Tutorial"/>
        <category label="Iceberg" term="Iceberg"/>
        <category label="Operations" term="Operations"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[From JSON to Iceberg: Bulk-Loading a Document Dataset into Gnok]]></title>
        <id>https://docs.gnok.io/blog/2026/06/05/bulk-loading-json-into-gnok</id>
        <link href="https://docs.gnok.io/blog/2026/06/05/bulk-loading-json-into-gnok"/>
        <updated>2026-06-05T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[We recently needed to land a large pile of JSON documents — roughly 50 GB across ~70 datasets, including a couple of 15+ GB monsters — into Gnok so it could be queried with SQL alongside everything else. The documents were schemaless and deeply nested; Gnok tables are columnar Iceberg. This post walks through the pipeline we landed on, and the handful of gotchas that shaped it.]]></summary>
        <content type="html"><![CDATA[<p>We recently needed to land a large pile of JSON documents — roughly <strong>50 GB across ~70 datasets</strong>, including a couple of 15+ GB monsters — into Gnok so it could be queried with SQL alongside everything else. The documents were schemaless and deeply nested; Gnok tables are columnar Iceberg. This post walks through the pipeline we landed on, and the handful of gotchas that shaped it.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-shape-of-the-problem">The shape of the problem<a href="https://docs.gnok.io/blog/2026/06/05/bulk-loading-json-into-gnok#the-shape-of-the-problem" class="hash-link" aria-label="Direct link to The shape of the problem" title="Direct link to The shape of the problem" translate="no">​</a></h2>
<p>Three constraints drove the design:</p>
<ol>
<li class=""><strong>The documents are heterogeneous and nested.</strong> Datasets like <code>users</code>, <code>orders</code>, <code>events</code>, and <code>sessions</code> share no schema, and individual documents carry sub-objects and arrays. Designing a bespoke relational schema per dataset would have been weeks of work.</li>
<li class=""><strong>Some datasets are huge.</strong> An <code>event_history</code>-style export alone was 17 GB compressed — far too big to expand onto a laptop and re-process from disk.</li>
<li class=""><strong>The data has to get from a laptop to the server.</strong> Gnok ingests from object storage, not from the SQL wire. So "loading" really means "get files into S3, then tell Gnok to read them."</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-pipeline">The pipeline<a href="https://docs.gnok.io/blog/2026/06/05/bulk-loading-json-into-gnok#the-pipeline" class="hash-link" aria-label="Direct link to The pipeline" title="Direct link to The pipeline" translate="no">​</a></h2>
<p>For each dataset we run a single streaming pass:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">gunzip -c &lt;dataset&gt;.json.gz \</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  | python loader.py &lt;table&gt;     # normalize, chunk, upload, COPY INTO</span><br></span></code></pre></div></div>
<p>The loader consumes <strong>newline-delimited JSON</strong> (NDJSON — one document per line) and never holds a whole dataset on disk: it converts on the fly and keeps exactly one chunk in flight. Here's what each chunk goes through.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-stream-straight-from-the-compressed-file">1. Stream straight from the compressed file<a href="https://docs.gnok.io/blog/2026/06/05/bulk-loading-json-into-gnok#1-stream-straight-from-the-compressed-file" class="hash-link" aria-label="Direct link to 1. Stream straight from the compressed file" title="Direct link to 1. Stream straight from the compressed file" translate="no">​</a></h3>
<p>Expanding 17 GB of gzipped JSON onto disk just to read it is wasteful — and on a laptop, sometimes impossible. Instead we pipe <code>gunzip -c</code> directly into the loader, so the giant datasets flow through memory a line at a time and never touch local disk in full.</p>
<p>If your export is one big pretty-printed JSON <em>array</em> rather than NDJSON, a streaming parser gets you the same line-at-a-time behavior — <code>jq -c '.[]'</code> (or an <code>ijson</code> pass) emits one compact document per line without materializing the whole array.</p>
<p>This is also a clean path for document databases: MongoDB, for instance, exports straight to NDJSON via <code>mongoexport --type=json</code>, and you can decode a raw <code>.bson</code> dump to the same JSON-lines stream with <code>bsondump</code> — no server to stand up. Either one drops directly into the pipeline below.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-one-schema-to-rule-them-all-id--doc-variant">2. One schema to rule them all: <code>id</code> + <code>doc VARIANT</code><a href="https://docs.gnok.io/blog/2026/06/05/bulk-loading-json-into-gnok#2-one-schema-to-rule-them-all-id--doc-variant" class="hash-link" aria-label="Direct link to 2-one-schema-to-rule-them-all-id--doc-variant" title="Direct link to 2-one-schema-to-rule-them-all-id--doc-variant" translate="no">​</a></h3>
<p>Rather than model each dataset, every table is just two columns:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TABLE</span><span class="token plain"> my_db</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">json</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">users_raw </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">id </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">VARCHAR</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> doc </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">VARCHAR</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<p><code>id</code> is the document's identifier; <code>doc</code> is the <strong>entire document as a JSON string</strong>. After load we wrap it in a view that parses the JSON into a queryable <code>VARIANT</code>:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">VIEW</span><span class="token plain"> my_db</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">json</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">users </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> PARSE_JSON</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">doc</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> doc </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> my_db</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">json</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">users_raw</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<p>Now nested access just works, with zero per-dataset schema work:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> doc:profile:email::</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">varchar</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> email</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">       doc:address:city::</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">varchar</span><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> city</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> my_db</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">json</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">users</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHERE</span><span class="token plain"> doc:</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">status</span><span class="token plain">::</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">varchar</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'active'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<p>One normalization detail worth calling out: many document exporters wrap scalars in <em>typed</em> objects rather than emitting plain values — an id might arrive as <code>{"$oid":"..."}</code> and a date as <code>{"$date":{"$numberLong":"..."}}</code>. If your source does this, rewrite those wrappers into plain values (<code>"..."</code>, <code>"2026-02-28T23:55:10Z"</code>) in the loader before storing, so queries see clean strings and ISO timestamps instead of wrapper objects.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-the-upload-presigned-urls-to-s3">3. The upload: presigned URLs to S3<a href="https://docs.gnok.io/blog/2026/06/05/bulk-loading-json-into-gnok#3-the-upload-presigned-urls-to-s3" class="hash-link" aria-label="Direct link to 3. The upload: presigned URLs to S3" title="Direct link to 3. The upload: presigned URLs to S3" translate="no">​</a></h3>
<p>This is the part worth understanding. Gnok doesn't accept bulk data over the SQL connection (<code>COPY ... FROM STDIN</code> isn't supported). Data lands in object storage first, then a SQL statement points Gnok at it. The flow per chunk:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">POST /v1/stages/presign   { stage, runId, filename }   -&gt;   { url, stageRef }</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">PUT  &lt;url&gt;  (the file bytes)                            -&gt;   S3 (200 OK)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">COPY INTO &lt;table&gt; FROM '&lt;stageRef&gt;' FILE_FORMAT=(TYPE='PARQUET')</span><br></span></code></pre></div></div>
<p>A <strong>presigned URL</strong> is a temporary, signed S3 URL that grants "you may PUT this one object, for the next few minutes." It's signed with Gnok's own cloud credentials, derived from your JWT — so the client uploads <strong>directly to S3 and never needs any AWS keys</strong>. The returned <code>stageRef</code> (e.g. <code>@stage/uploads/&lt;run&gt;/part_0003.parquet</code>) is the logical handle you hand back to <code>COPY INTO</code>.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-copy-into-ingests-the-staged-file">4. <code>COPY INTO</code> ingests the staged file<a href="https://docs.gnok.io/blog/2026/06/05/bulk-loading-json-into-gnok#4-copy-into-ingests-the-staged-file" class="hash-link" aria-label="Direct link to 4-copy-into-ingests-the-staged-file" title="Direct link to 4-copy-into-ingests-the-staged-file" translate="no">​</a></h3>
<p><code>COPY INTO</code> reads the staged file from S3 across Gnok's workers and writes it into the table's managed Iceberg location, computing column statistics and committing a snapshot. The <code>rows_affected</code> count comes back over the SQL connection — the only thing that actually travels through your database connection in the whole pipeline.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="three-things-that-bit-us-and-how-we-fixed-them">Three things that bit us (and how we fixed them)<a href="https://docs.gnok.io/blog/2026/06/05/bulk-loading-json-into-gnok#three-things-that-bit-us-and-how-we-fixed-them" class="hash-link" aria-label="Direct link to Three things that bit us (and how we fixed them)" title="Direct link to Three things that bit us (and how we fixed them)" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="json-format-copy-into-timed-out--csvparquet-didnt">JSON-format <code>COPY INTO</code> timed out — CSV/Parquet didn't<a href="https://docs.gnok.io/blog/2026/06/05/bulk-loading-json-into-gnok#json-format-copy-into-timed-out--csvparquet-didnt" class="hash-link" aria-label="Direct link to json-format-copy-into-timed-out--csvparquet-didnt" title="Direct link to json-format-copy-into-timed-out--csvparquet-didnt" translate="no">​</a></h3>
<p>Our first instinct was <code>FILE_FORMAT=(TYPE='JSON')</code>, since the source was already JSON. Every attempt died with a 60-second catalog timeout, even on a 3-row file. The identical load as <strong>CSV</strong> or <strong>Parquet</strong> succeeded in seconds. Lesson: for the whole-document-into-one-column pattern, stage as CSV or Parquet, not JSON — even though the data inside that column is JSON.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="we-were-upload-bound--parquet--zstd-fixed-it">We were upload-bound — Parquet + zstd fixed it<a href="https://docs.gnok.io/blog/2026/06/05/bulk-loading-json-into-gnok#we-were-upload-bound--parquet--zstd-fixed-it" class="hash-link" aria-label="Direct link to We were upload-bound — Parquet + zstd fixed it" title="Direct link to We were upload-bound — Parquet + zstd fixed it" translate="no">​</a></h3>
<p>Our first working version staged raw CSV. Each ~400 MB chunk took ~60 seconds, almost entirely <strong>upload bandwidth</strong> — a home uplink pushing 400 MB to S3. Switching the chunk format to <strong>zstd-compressed Parquet</strong> shrank a 400 MB chunk to ~20 MB (≈8×) and cut per-chunk time to ~4 seconds. A 20-million-row dataset went from a projected 15 minutes to about 90 seconds. If you're upload-bound, compress before you ship.</p>
<blockquote>
<p>Tip: <code>COPY INTO</code> does <strong>not</strong> transparently decompress gzipped CSV — it reads the gzip bytes as raw text and fails. The "compression" you want lives <em>inside</em> the Parquet file (zstd/snappy), not around the CSV.</p>
</blockquote>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="tokens-expire-compute-warms-up">Tokens expire; compute warms up<a href="https://docs.gnok.io/blog/2026/06/05/bulk-loading-json-into-gnok#tokens-expire-compute-warms-up" class="hash-link" aria-label="Direct link to Tokens expire; compute warms up" title="Direct link to Tokens expire; compute warms up" translate="no">​</a></h3>
<p>Two operational realities for a multi-hour load:</p>
<ul>
<li class=""><strong>JWTs expire every 15 minutes.</strong> The loader refreshes proactively and retries any <code>401</code>.</li>
<li class=""><strong>Compute can take a moment to become available.</strong> <code>COPY INTO</code> needs distributed workers and can return <code>503 No compute resources available</code> until they're ready, even while <code>SELECT</code> already works. The loader treats <code>503</code> (and transient catalog timeouts) as retryable with backoff, so a long load self-heals instead of aborting.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="copy-into-vs-register-files-copy-or-move"><code>COPY INTO</code> vs <code>REGISTER FILES</code>: copy or move?<a href="https://docs.gnok.io/blog/2026/06/05/bulk-loading-json-into-gnok#copy-into-vs-register-files-copy-or-move" class="hash-link" aria-label="Direct link to copy-into-vs-register-files-copy-or-move" title="Direct link to copy-into-vs-register-files-copy-or-move" translate="no">​</a></h2>
<p>A natural question: since we already uploaded Parquet, does <code>COPY INTO</code> reuse it or rewrite it? It <strong>rewrites</strong> — reading the staged file and writing fresh Parquet into the table's managed location, with full column statistics and partition routing. Your staged file is treated as a disposable source.</p>
<p>Gnok also offers <code>REGISTER FILES INTO ... FROM '...'</code>, which registers existing Parquet <strong>in place</strong> — it reads only the footers, leaves the files where they are, and moves no data. The trade-offs:</p>
<table><thead><tr><th></th><th><code>COPY INTO</code></th><th><code>REGISTER FILES</code></th></tr></thead><tbody><tr><td>Data movement</td><td>Reads + rewrites into managed location</td><td>None (footer read only)</td></tr><tr><td>File location</td><td>Table's managed path</td><td>Stays at the source path</td></tr><tr><td>Statistics</td><td>Full (min/max, nulls)</td><td>Null counts only</td></tr><tr><td>Needs prior snapshot</td><td>No</td><td>Yes</td></tr></tbody></table>
<p>For an <code>id</code> + <code>doc</code> schema, the missing min/max statistics cost nothing (you can't range-prune a JSON blob), which makes <code>REGISTER FILES</code> an appealing way to skip the rewrite for very large datasets — as long as you're comfortable with the table's data living at the staged path. <code>COPY INTO</code> is the safer default because the staged files remain disposable.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-takeaway-recipe">The takeaway recipe<a href="https://docs.gnok.io/blog/2026/06/05/bulk-loading-json-into-gnok#the-takeaway-recipe" class="hash-link" aria-label="Direct link to The takeaway recipe" title="Direct link to The takeaway recipe" translate="no">​</a></h2>
<p>For landing a nested document dataset into Gnok:</p>
<ol>
<li class="">Stream <code>gunzip -c</code> straight into the loader — no full decompress, flat disk usage. (Use <code>jq -c '.[]'</code> if your source is a single JSON array.)</li>
<li class="">Normalize any typed wrappers; store each doc as <code>id</code> + <code>doc</code> (JSON string), wrap in a <code>PARSE_JSON</code> view.</li>
<li class="">Chunk into <strong>zstd Parquet</strong>, presign, PUT to S3, <code>COPY INTO</code>.</li>
<li class="">Make the loader resilient: refresh tokens, retry <code>503</code>/catalog timeouts, keep one chunk on disk.</li>
<li class="">Reach for <code>REGISTER FILES</code> only when the rewrite cost dominates and the staged-file coupling is acceptable.</li>
</ol>
<p>The result: a schemaless 50 GB pile of JSON documents, queryable in Gnok SQL — <code>doc:any:nested:path</code> and all — without designing a single table by hand.</p>]]></content>
        <author>
            <name>Gnok Team</name>
        </author>
        <category label="Ingestion" term="Ingestion"/>
        <category label="Iceberg" term="Iceberg"/>
        <category label="Performance" term="Performance"/>
        <category label="Tutorial" term="Tutorial"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[From Data to SQL-Callable Model: An End-to-End Tour of Gnok Notebooks]]></title>
        <id>https://docs.gnok.io/blog/2026/05/27/python-notebooks</id>
        <link href="https://docs.gnok.io/blog/2026/05/27/python-notebooks"/>
        <updated>2026-05-27T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Gnok Studio now has an in-product Python notebook. You write Python against]]></summary>
        <content type="html"><![CDATA[<p>Gnok Studio now has an <strong>in-product Python notebook</strong>. You write Python against
governed Gnok data, train a model, and register it so it's <strong>callable from SQL
with <code>ML_PREDICT</code></strong> — the whole train→serve loop, in one place, with no data
movement and no separate notebook server to babysit.</p>
<p>This post builds a complete example end to end: read data, explore it, engineer
features, train a scikit-learn pipeline, register it, and score new rows from
SQL — then let <strong>Gnok AI</strong> write a cell for us.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-gnok-helper">The <code>gnok</code> helper<a href="https://docs.gnok.io/blog/2026/05/27/python-notebooks#the-gnok-helper" class="hash-link" aria-label="Direct link to the-gnok-helper" title="Direct link to the-gnok-helper" translate="no">​</a></h2>
<p>Every notebook kernel comes with a pre-installed <code>gnok</code> helper. You don't
<code>pip install</code> it and you don't pass a host or token — it <strong>auto-connects as the
signed-in user</strong>, so row-level security, masking, and vended credentials apply
exactly as they do everywhere else in Gnok.</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> gnok</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> gnok </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> ml</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># A query → pandas</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">df </span><span class="token operator">=</span><span class="token plain"> gnok</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">sql</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"SELECT 1 AS x, 2 AS y"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">fetch_df</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></span></code></pre></div></div>
<blockquote>
<p><code>gnok</code> wraps the standalone <a class="" href="https://docs.gnok.io/docs/connectivity/python-sdk"><code>pygnok</code></a> client
and adds a <code>gnok.ml</code> namespace. Full reference:
<a class="" href="https://docs.gnok.io/docs/ai-ml/notebooks">Python Notebooks</a>.</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-read-the-data">1. Read the data<a href="https://docs.gnok.io/blog/2026/05/27/python-notebooks#1-read-the-data" class="hash-link" aria-label="Direct link to 1. Read the data" title="Direct link to 1. Read the data" translate="no">​</a></h2>
<p>We'll predict whether a customer will churn. Start by pulling features into a
DataFrame — <code>read_table</code> for a whole table, or <code>gnok.sql(...)</code> for anything
shaped:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">customers </span><span class="token operator">=</span><span class="token plain"> gnok</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">sql</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token triple-quoted-string string" style="color:rgb(255, 121, 198)">"""</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token triple-quoted-string string" style="color:rgb(255, 121, 198)">    SELECT</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token triple-quoted-string string" style="color:rgb(255, 121, 198)">        tenure_months,</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token triple-quoted-string string" style="color:rgb(255, 121, 198)">        monthly_charges,</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token triple-quoted-string string" style="color:rgb(255, 121, 198)">        total_charges,</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token triple-quoted-string string" style="color:rgb(255, 121, 198)">        support_tickets,</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token triple-quoted-string string" style="color:rgb(255, 121, 198)">        churned                      -- 1 = churned, 0 = retained</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token triple-quoted-string string" style="color:rgb(255, 121, 198)">    FROM analytics.customers</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token triple-quoted-string string" style="color:rgb(255, 121, 198)">    WHERE total_charges IS NOT NULL</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token triple-quoted-string string" style="color:rgb(255, 121, 198)">"""</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">fetch_df</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">print</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">customers</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">shape</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">customers</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">head</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-explore">2. Explore<a href="https://docs.gnok.io/blog/2026/05/27/python-notebooks#2-explore" class="hash-link" aria-label="Direct link to 2. Explore" title="Direct link to 2. Explore" translate="no">​</a></h2>
<p>It's a normal pandas DataFrame, so all your usual tools work:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">customers</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">describe</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">customers</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">groupby</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"churned"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"tenure_months"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"monthly_charges"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">mean</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></span></code></pre></div></div>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> matplotlib</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">pyplot </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">as</span><span class="token plain"> plt</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">customers</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">boxplot</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">column</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"monthly_charges"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> by</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"churned"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">plt</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">title</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"Monthly charges by churn"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">plt</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">show</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></span></code></pre></div></div>
<p>Plots render inline in the cell output.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-train-a-model">3. Train a model<a href="https://docs.gnok.io/blog/2026/05/27/python-notebooks#3-train-a-model" class="hash-link" aria-label="Direct link to 3. Train a model" title="Direct link to 3. Train a model" translate="no">​</a></h2>
<p>A scikit-learn pipeline — scale the features, then a gradient-boosting model.
We use a <strong>regressor</strong> so the model outputs a continuous churn <em>propensity</em>
(0–1), which maps cleanly to a SQL <code>FLOAT</code>:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> numpy </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">as</span><span class="token plain"> np</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> sklearn</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">pipeline </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> Pipeline</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> sklearn</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">preprocessing </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> StandardScaler</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> sklearn</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">ensemble </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> GradientBoostingRegressor</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> sklearn</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">model_selection </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> train_test_split</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">FEATURES </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"tenure_months"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"monthly_charges"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"total_charges"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"support_tickets"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">X </span><span class="token operator">=</span><span class="token plain"> customers</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">FEATURES</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">to_numpy</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"float32"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">y </span><span class="token operator">=</span><span class="token plain"> customers</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"churned"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">to_numpy</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"float32"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">X_tr</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> X_te</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> y_tr</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> y_te </span><span class="token operator">=</span><span class="token plain"> train_test_split</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">X</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> y</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> test_size</span><span class="token operator">=</span><span class="token number">0.2</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> random_state</span><span class="token operator">=</span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">model </span><span class="token operator">=</span><span class="token plain"> Pipeline</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"scale"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> StandardScaler</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"gbr"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> GradientBoostingRegressor</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">n_estimators</span><span class="token operator">=</span><span class="token number">200</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> max_depth</span><span class="token operator">=</span><span class="token number">3</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> random_state</span><span class="token operator">=</span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">fit</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">X_tr</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> y_tr</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">print</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"R2 (holdout):"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">round</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">model</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">score</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">X_te</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> y_te</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">3</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-register-it-for-sql">4. Register it for SQL<a href="https://docs.gnok.io/blog/2026/05/27/python-notebooks#4-register-it-for-sql" class="hash-link" aria-label="Direct link to 4. Register it for SQL" title="Direct link to 4. Register it for SQL" translate="no">​</a></h2>
<p>One call exports the fitted pipeline to ONNX, uploads it to an internal stage,
and runs <code>CREATE OR REPLACE MODEL</code> — the model is now a SQL function:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">ml</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">register_model</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">model</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"analytics.imported.churn_propensity_v1"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> X</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></span></code></pre></div></div>
<p>A few things worth knowing (the helper handles the rest):</p>
<ul>
<li class=""><strong>Use a fully-qualified name</strong> (<code>catalog.schema.model</code>). The notebook has no
default schema, so an unqualified name would land somewhere <code>ML_PREDICT</code>
can't find.</li>
<li class="">The input signature <code>(FLOAT × 4)</code> is <strong>inferred from <code>X</code></strong> — one float per
feature, in column order.</li>
<li class=""><code>register_model(..., returns="FLOAT")</code> is the default; a <em>classifier's</em> label
output is an integer, so use <code>returns="BIGINT"</code> there.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="5-serve-it-from-sql">5. Serve it from SQL<a href="https://docs.gnok.io/blog/2026/05/27/python-notebooks#5-serve-it-from-sql" class="hash-link" aria-label="Direct link to 5. Serve it from SQL" title="Direct link to 5. Serve it from SQL" translate="no">​</a></h2>
<p>Now anyone — a dashboard, a scheduled query, a BI tool — can score rows. Note
the <code>CAST(... AS REAL)</code>: bare decimal literals are typed as <code>DECIMAL</code>, which the
ONNX input rejects.</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> ML_PREDICT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token string" style="color:rgb(255, 121, 198)">'analytics.imported.churn_propensity_v1'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  CAST</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">tenure_months </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">REAL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  CAST</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">monthly_charges </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">REAL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  CAST</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">total_charges </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">REAL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  CAST</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">support_tickets </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">REAL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> churn_score</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> analytics</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">customers</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ORDER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> churn_score </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DESC</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">LIMIT</span><span class="token plain"> </span><span class="token number">20</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<p>Materialize the scores for the rest of the business:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CREATE</span><span class="token plain"> </span><span class="token operator">OR</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">REPLACE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TABLE</span><span class="token plain"> analytics</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">churn_scores </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  customer_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  ML_PREDICT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'analytics.imported.churn_propensity_v1'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">             CAST</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">tenure_months </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">REAL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> CAST</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">monthly_charges </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">REAL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">             CAST</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">total_charges </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">REAL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> CAST</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">support_tickets </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">REAL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> churn_score</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> analytics</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">customers</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<p>…or score back from the notebook and write the result:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">scored </span><span class="token operator">=</span><span class="token plain"> gnok</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">sql</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"SELECT customer_id, ... FROM analytics.churn_scores"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">fetch_df</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">gnok</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">write</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">scored</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">query</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"churn_score &gt; 0.7"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"analytics.churn_watchlist"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></span></code></pre></div></div>
<p>That's the whole loop: the model trained in Python is now ordinary SQL the rest
of the platform can call — no model server, no export/import dance.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="6-let-gnok-ai-write-the-cell">6. Let Gnok AI write the cell<a href="https://docs.gnok.io/blog/2026/05/27/python-notebooks#6-let-gnok-ai-write-the-cell" class="hash-link" aria-label="Direct link to 6. Let Gnok AI write the cell" title="Direct link to 6. Let Gnok AI write the cell" translate="no">​</a></h2>
<p>Open the <strong>✨ Gnok AI</strong> panel and describe what you want:</p>
<blockquote>
<p>"Read 1,000 rows of analytics.customers into a DataFrame and plot churn rate by tenure bucket."</p>
</blockquote>
<p>It returns a ready-to-run cell — click <strong>＋ Add as cell</strong> and run it. The
assistant knows the <code>gnok</code> helper, so it reaches for <code>gnok.sql</code> / <code>read_table</code>
rather than generic boilerplate. When a cell errors, <strong>✨ Fix</strong> sends the
traceback back to the model and proposes a corrected cell.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="recap">Recap<a href="https://docs.gnok.io/blog/2026/05/27/python-notebooks#recap" class="hash-link" aria-label="Direct link to Recap" title="Direct link to Recap" translate="no">​</a></h2>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">read (gnok.sql / read_table) → explore (pandas) → train (sklearn)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">   → register (gnok.ml.register_model) → serve (ML_PREDICT in SQL)</span><br></span></code></pre></div></div>
<p>No <code>pip install</code>, no connection string, no data leaving Gnok. Dig into the
<a class="" href="https://docs.gnok.io/docs/ai-ml/notebooks">Python Notebooks reference</a> for the full <code>gnok</code> and
<code>gnok.ml</code> API, and <a class="" href="https://docs.gnok.io/docs/ai-ml/onnx-inference">ONNX Inference</a> for how
registered models execute.</p>]]></content>
        <author>
            <name>Gnok Team</name>
        </author>
        <category label="Tutorial" term="Tutorial"/>
        <category label="AI/ML" term="AI/ML"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[AI in the WHERE Clause: AI_FILTER and AI_FILTER_AGG]]></title>
        <id>https://docs.gnok.io/blog/2026/05/05/ai-filter</id>
        <link href="https://docs.gnok.io/blog/2026/05/05/ai-filter"/>
        <updated>2026-05-05T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[SELECT * FROM reviews WHERE AI_FILTER('is a complaint about shipping', body).]]></summary>
        <content type="html"><![CDATA[<p><code>SELECT * FROM reviews WHERE AI_FILTER('is a complaint about shipping', body)</code>.</p>
<p>If that line of SQL works, a whole class of "I just need to find
the rows where X holds, and X isn't a regex" problems
disappears. Gnok's AI_FILTER family lands the natural-language
predicate in the place where you'd actually write it — alongside
<code>=</code>, <code>LIKE</code>, and <code>BETWEEN</code>.</p>
<p>This post walks through the four new functions, the patterns
they unlock, and how to keep cost predictable when LLMs are
sitting in your hot path.</p>
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>Before you start</div><div class="admonitionContent_BuS1"><p>These functions call Gnok's AI provider. AI is off for an
organization until an organization administrator, in Studio under
<strong>Operations → AI Governance</strong>, saves a monthly token budget as
Available and enables a rollout policy (provider, model, allowed
operations, and egress and retention approvals). See
<a class="" href="https://docs.gnok.io/docs/ai-ml/ai-functions">AI Scalar Functions</a>.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-four-new-functions">The four new functions<a href="https://docs.gnok.io/blog/2026/05/05/ai-filter#the-four-new-functions" class="hash-link" aria-label="Direct link to The four new functions" title="Direct link to The four new functions" translate="no">​</a></h2>
<table><thead><tr><th>Function</th><th>Shape</th><th>Purpose</th></tr></thead><tbody><tr><td><code>AI_FILTER(predicate, text)</code></td><td>scalar <code>→ BOOLEAN</code></td><td>per-row predicate; one LLM call per distinct text</td></tr><tr><td><code>AI_FILTER_AGG(predicate, text_col)</code></td><td>aggregate <code>→ BOOLEAN</code></td><td>per-group predicate over concatenated rows; one LLM call per group</td></tr><tr><td><code>AI_CLASSIFY(text, labels)</code></td><td>scalar <code>→ VARCHAR</code></td><td>alias for <code>AI_CLASSIFY_TEXT</code> (common spelling)</td></tr><tr><td><code>AI_CLASSIFY_AGG(text_col, labels)</code></td><td>aggregate <code>→ VARCHAR</code></td><td>one classification per group</td></tr></tbody></table>
<p>These join the existing AI_* family (<code>AI_COMPLETE</code>, <code>AI_SUMMARIZE</code>,
<code>AI_SUMMARIZE_AGG</code>, <code>AI_TRANSLATE</code>, <code>AI_EXTRACT_ANSWER</code>,
<code>AI_AGG</code>). The unifying idea: the LLM is just a function. SQL
already knows how to push, parallelize, and de-duplicate function
calls — let it.</p>
<p>The argument order is <strong>prompt first, text second</strong> — the prompt
is a constant baked into the system prompt, the text is the
per-row column.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="ai_filter-as-a-row-predicate">AI_FILTER as a row predicate<a href="https://docs.gnok.io/blog/2026/05/05/ai-filter#ai_filter-as-a-row-predicate" class="hash-link" aria-label="Direct link to AI_FILTER as a row predicate" title="Direct link to AI_FILTER as a row predicate" translate="no">​</a></h2>
<p>The classic use case is finding rows that match a fuzzy condition
no regex catches:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">-- "Show me complaints about shipping"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> review_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> body</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> rating</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain">   reviews</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHERE</span><span class="token plain">  AI_FILTER</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'is a complaint about shipping or delivery'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> body</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token operator">AND</span><span class="token plain">  rating </span><span class="token operator">&lt;=</span><span class="token plain"> </span><span class="token number">3</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ORDER</span><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> created_at </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DESC</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">LIMIT</span><span class="token plain">  </span><span class="token number">20</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<p>Two things to notice:</p>
<ol>
<li class=""><strong>The model only sees rows that pass the cheap filter first.</strong>
The query planner keeps <code>rating &lt;= 3</code> predicates ahead of the
LLM call, so you only pay for the small slice that matters.</li>
<li class=""><strong>De-duplication.</strong> Under the hood, <code>AI_FILTER</code> rewrites to
<code>LOWER(AI_COMPLETE(&lt;prompt&gt; + body)) = 'yes'</code>. <code>AI_COMPLETE</code>
de-dupes identical inputs across a batch — if the same <code>body</code>
shows up twice, the model is asked once, so the number of
model calls is typically less than the row count.</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="ai_filter_agg--one-call-per-group">AI_FILTER_AGG — one call per group<a href="https://docs.gnok.io/blog/2026/05/05/ai-filter#ai_filter_agg--one-call-per-group" class="hash-link" aria-label="Direct link to AI_FILTER_AGG — one call per group" title="Direct link to AI_FILTER_AGG — one call per group" translate="no">​</a></h2>
<p>Some predicates are about the group, not the row. "Show me
products where reviews mention battery life":</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> product_id</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain">   reviews</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">GROUP</span><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> product_id</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">HAVING</span><span class="token plain"> AI_FILTER_AGG</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'reviews mention battery life'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> body</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token operator">AND</span><span class="token plain">  </span><span class="token function" style="color:rgb(80, 250, 123)">COUNT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token operator">*</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">&gt;=</span><span class="token plain"> </span><span class="token number">5</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<p>The aggregate concatenates the rows in each group (delimited by
the AISQL separator), runs <code>AI_FILTER</code> once over the combined
text, and returns the group-level boolean. One LLM call per
product instead of one per review.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="ai_classify_agg--group-level-labels">AI_CLASSIFY_AGG — group-level labels<a href="https://docs.gnok.io/blog/2026/05/05/ai-filter#ai_classify_agg--group-level-labels" class="hash-link" aria-label="Direct link to AI_CLASSIFY_AGG — group-level labels" title="Direct link to AI_CLASSIFY_AGG — group-level labels" translate="no">​</a></h2>
<p>Same pattern, classification flavor:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    product_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    AI_CLASSIFY_AGG</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">body</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> ARRAY</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">'quality_issue'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'shipping_issue'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'usability_issue'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'other'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> top_complaint</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain">    reviews</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHERE</span><span class="token plain">   rating </span><span class="token operator">&lt;=</span><span class="token plain"> </span><span class="token number">2</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">GROUP</span><span class="token plain">   </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> product_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<p>You get one label per product. The model sees up to N rows of
review text per group (clipped at the engine's per-call token
budget; longer groups truncate from the end with a warning).</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="patterns-that-work">Patterns that work<a href="https://docs.gnok.io/blog/2026/05/05/ai-filter#patterns-that-work" class="hash-link" aria-label="Direct link to Patterns that work" title="Direct link to Patterns that work" translate="no">​</a></h2>
<p><strong>Cheap filters first.</strong> Always pin AI_FILTER behind a
selective non-AI predicate. This isn't a query-planner hint;
it's a cost reality. The cheaper your pre-filter, the less you
pay.</p>
<p><strong>Materialize for stability.</strong> AI calls are non-deterministic.
If you need consistent answers across dashboard refreshes, write
the filter to a materialized view:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CREATE</span><span class="token plain"> MATERIALIZED </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">VIEW</span><span class="token plain"> shipping_complaints </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> review_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> body</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> rating</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain">   reviews</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHERE</span><span class="token plain">  rating </span><span class="token operator">&lt;=</span><span class="token plain"> </span><span class="token number">3</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token operator">AND</span><span class="token plain">  AI_FILTER</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'is a complaint about shipping or delivery'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> body</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">REFRESH MATERIALIZED </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">VIEW</span><span class="token plain"> shipping_complaints</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<p>Stable IDs flow into your downstream joins and the cost is paid
once per refresh, not once per dashboard view.</p>
<p><strong>LIMIT before AI_FILTER when you can.</strong> If the question is
"show me 20 examples of X", an <code>ORDER BY ts DESC LIMIT 1000</code>
ahead of the AI predicate caps your worst-case spend at 1,000
LLM calls. The planner can't always do this for you because it
doesn't know <code>AI_FILTER</code> is expensive — be explicit.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="keeping-an-eye-on-usage">Keeping an eye on usage<a href="https://docs.gnok.io/blog/2026/05/05/ai-filter#keeping-an-eye-on-usage" class="hash-link" aria-label="Direct link to Keeping an eye on usage" title="Direct link to Keeping an eye on usage" translate="no">​</a></h2>
<p>AI_* calls draw on your organization's monthly AI token budget.
Studio shows the budget's used, reserved, and remaining tokens
under <strong>Operations → AI Governance</strong>. Because identical inputs
within a batch are sent to the model once, a query usually makes
fewer model calls than it has rows.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="bind-time-validation">Bind-time validation<a href="https://docs.gnok.io/blog/2026/05/05/ai-filter#bind-time-validation" class="hash-link" aria-label="Direct link to Bind-time validation" title="Direct link to Bind-time validation" translate="no">​</a></h2>
<p>The functions are special-cased in the binder so syntax errors
fire fast:</p>
<ul>
<li class=""><code>AI_FILTER</code> / <code>AI_FILTER_AGG</code> / <code>AI_AGG</code> require exactly two
args: <code>(prompt_literal, text)</code>.</li>
<li class=""><code>AI_CLASSIFY_AGG</code> requires <code>(text_column, labels[])</code>.</li>
<li class="">The prompt / labels arg must be a string literal — it gets
baked into the system prompt and isn't allowed to vary per row.
Per-row prompts would defeat de-duplication and produce
nonsensical audit logs.</li>
</ul>
<p>If you try <code>AI_FILTER(predicate_col, body)</code>, you get a clear
<strong>"AI_FILTER: first argument must be a literal prompt"</strong> error at
bind time, not a mysterious runtime miss-classification. Empty
literals are caught with a separate "non-empty string literal"
error.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-this-matters">Why this matters<a href="https://docs.gnok.io/blog/2026/05/05/ai-filter#why-this-matters" class="hash-link" aria-label="Direct link to Why this matters" title="Direct link to Why this matters" translate="no">​</a></h2>
<p>Most "natural language → SQL" stories put the LLM in the
<strong>parser</strong>: take a question, generate a query, execute it. That's
a different feature (<code>ASK</code> does it). The AI_FILTER family puts the
LLM in the <strong>execution path</strong> as a cell-by-cell function. You
write the SQL; the LLM evaluates predicates the engine can't
express.</p>
<p>Both have their place. AI_FILTER is the one you reach for when:</p>
<ul>
<li class="">Your data is already structured (you know the table, the
columns, the filter logic).</li>
<li class="">The thing the regex / <code>LIKE</code> / classifier can't catch is a
semantic predicate.</li>
<li class="">You want the result to compose with <code>JOIN</code>, <code>GROUP BY</code>,
<code>WINDOW</code>, materialized views — every other SQL feature.</li>
</ul>
<p>Drop one into a <code>WHERE</code> clause against a table you already know
and see what happens.</p>]]></content>
        <author>
            <name>Gnok Team</name>
        </author>
        <category label="AI/ML" term="AI/ML"/>
        <category label="Tutorial" term="Tutorial"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Real Iceberg Rollback in Pure SQL]]></title>
        <id>https://docs.gnok.io/blog/2026/05/05/iceberg-real-rollback</id>
        <link href="https://docs.gnok.io/blog/2026/05/05/iceberg-real-rollback"/>
        <updated>2026-05-05T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Most engines say they support time travel. Watch what happens when]]></summary>
        <content type="html"><![CDATA[<p>Most engines say they support time travel. Watch what happens when
you actually try to roll back. Plenty of catalogs have shipped a
"snapshot rollback" that quietly only edits the table's
<code>current-snapshot-id</code> property — subsequent reads still hit the
latest data. Until recently, gnok was one of them.</p>
<p>This post walks through how that path is now wired end-to-end:
<code>ALTER TABLE … SET SNAPSHOT</code> actually flips the read pointer, the
new <code>is_current</code> / <code>parent_snapshot_id</code> / <code>sequence_number</code>
columns surface state honestly, and the <code>$snapshots</code> system view
makes Iceberg metadata first-class SQL.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="a-demo-you-can-re-run">A demo you can re-run<a href="https://docs.gnok.io/blog/2026/05/05/iceberg-real-rollback#a-demo-you-can-re-run" class="hash-link" aria-label="Direct link to A demo you can re-run" title="Direct link to A demo you can re-run" translate="no">​</a></h2>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">-- 0. Clean slate</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DROP</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TABLE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">IF</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">EXISTS</span><span class="token plain"> analytics</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">hr</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">staff</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TABLE</span><span class="token plain"> analytics</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">hr</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">staff </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  id     </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  name   </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">VARCHAR</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  salary </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INT</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">COMMENT</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'snapshot demo'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">-- 1. INSERT  → snapshot S1 (seq=1, parent=NULL)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INSERT</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INTO</span><span class="token plain"> analytics</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">hr</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">staff </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">VALUES</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'Alice'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">100</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">2</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'Bob'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain">    </span><span class="token number">80</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">3</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'Carol'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain">  </span><span class="token number">90</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">-- 2. UPDATE → snapshot S2 (seq=2, parent=S1)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">UPDATE</span><span class="token plain"> analytics</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">hr</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">staff </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SET</span><span class="token plain"> salary </span><span class="token operator">=</span><span class="token plain"> </span><span class="token number">150</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHERE</span><span class="token plain"> id </span><span class="token operator">=</span><span class="token plain"> </span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">-- 3. DELETE → snapshot S3 (seq=3, parent=S2)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DELETE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> analytics</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">hr</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">staff </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHERE</span><span class="token plain"> id </span><span class="token operator">=</span><span class="token plain"> </span><span class="token number">2</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> salary </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> analytics</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">hr</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">staff </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ORDER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">-- Alice=150, Carol=90  (no Bob)</span><br></span></code></pre></div></div>
<p>Three commits, three snapshots. <code>SHOW SNAPSHOTS</code> returns the
expected timeline:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SHOW</span><span class="token plain"> SNAPSHOTS </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> analytics</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">hr</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">staff</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">snapshot_id   </span><span class="token operator">|</span><span class="token plain"> is_current </span><span class="token operator">|</span><span class="token plain"> parent_snapshot_id </span><span class="token operator">|</span><span class="token plain"> seq </span><span class="token operator">|</span><span class="token plain"> operation</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token number">1777740163004</span><span class="token plain"> </span><span class="token operator">|</span><span class="token plain"> </span><span class="token boolean">false</span><span class="token plain">      </span><span class="token operator">|</span><span class="token plain"> </span><span class="token boolean">NULL</span><span class="token plain">               </span><span class="token operator">|</span><span class="token plain"> </span><span class="token number">1</span><span class="token plain">   </span><span class="token operator">|</span><span class="token plain"> append</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token number">1777740187411</span><span class="token plain"> </span><span class="token operator">|</span><span class="token plain"> </span><span class="token boolean">false</span><span class="token plain">      </span><span class="token operator">|</span><span class="token plain"> </span><span class="token number">1777740163004</span><span class="token plain">      </span><span class="token operator">|</span><span class="token plain"> </span><span class="token number">2</span><span class="token plain">   </span><span class="token operator">|</span><span class="token plain"> overwrite</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token number">1777740201632</span><span class="token plain"> </span><span class="token operator">|</span><span class="token plain"> </span><span class="token boolean">true</span><span class="token plain">       </span><span class="token operator">|</span><span class="token plain"> </span><span class="token number">1777740187411</span><span class="token plain">      </span><span class="token operator">|</span><span class="token plain"> </span><span class="token number">3</span><span class="token plain">   </span><span class="token operator">|</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">delete</span><br></span></code></pre></div></div>
<p>Two columns there are new. <strong><code>is_current</code></strong> says which snapshot
read queries hit right now. <strong><code>parent_snapshot_id</code></strong> lets you walk
the lineage back without a second <code>SHOW TBLPROPERTIES</code> round-trip.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-rollback">The rollback<a href="https://docs.gnok.io/blog/2026/05/05/iceberg-real-rollback#the-rollback" class="hash-link" aria-label="Direct link to The rollback" title="Direct link to The rollback" translate="no">​</a></h2>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">-- Roll back to S1 — clean DDL form</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ALTER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TABLE</span><span class="token plain"> analytics</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">hr</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">staff </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SET</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SNAPSHOT</span><span class="token plain"> </span><span class="token number">1777740163004</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<p>Equivalent forms the parser accepts:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ALTER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TABLE</span><span class="token plain"> analytics</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">hr</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">staff </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SET</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CURRENT</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SNAPSHOT</span><span class="token plain"> </span><span class="token number">1777740163004</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ALTER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TABLE</span><span class="token plain"> analytics</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">hr</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">staff </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SET</span><span class="token plain"> VERSION </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">OF</span><span class="token plain"> </span><span class="token number">1777740163004</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ROLLBACK</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TABLE</span><span class="token plain"> analytics</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">hr</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">staff </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TO</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SNAPSHOT</span><span class="token plain"> </span><span class="token number">1777740163004</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ROLLBACK</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TABLE</span><span class="token plain"> analytics</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">hr</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">staff </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TO</span><span class="token plain"> PARENT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain">        </span><span class="token comment" style="color:rgb(98, 114, 164)">-- one snapshot back</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ALTER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TABLE</span><span class="token plain"> analytics</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">hr</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">staff </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SET</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TIMESTAMP</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">OF</span><span class="token plain"> </span><span class="token number">1777740164000</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<p>The rollback is <strong>declarative</strong> — it does not write a new commit.
It flips the table's main pointer back to S1. Newer snapshots stay
on disk so a roll-forward works:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> salary </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> analytics</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">hr</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">staff </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ORDER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">-- Alice=100, Bob=80, Carol=90  ← original 3 rows back</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">-- Roll forward to S3</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ROLLBACK</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TABLE</span><span class="token plain"> analytics</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">hr</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">staff </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TO</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SNAPSHOT</span><span class="token plain"> </span><span class="token number">1777740201632</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> salary </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> analytics</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">hr</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">staff </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ORDER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">-- Alice=150, Carol=90  ← back to current state</span><br></span></code></pre></div></div>
<p><code>is_current</code> flips correspondingly. No new snapshots get created
during rollback — the history stays clean.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="snapshots--system-metadata-as-queryable-sql">$snapshots — system metadata as queryable SQL<a href="https://docs.gnok.io/blog/2026/05/05/iceberg-real-rollback#snapshots--system-metadata-as-queryable-sql" class="hash-link" aria-label="Direct link to $snapshots — system metadata as queryable SQL" title="Direct link to $snapshots — system metadata as queryable SQL" translate="no">​</a></h2>
<p>Inspecting Iceberg metadata used to require shelling out to
<code>pyiceberg</code> or running <code>SHOW TBLPROPERTIES</code> and parsing the result.
The engine now exposes per-table system views you can <code>SELECT</code>
from directly. The full set:</p>
<table><thead><tr><th>View</th><th>Contents</th></tr></thead><tbody><tr><td><code>$snapshots</code></td><td>Snapshot metadata + summary stats</td></tr><tr><td><code>$history</code></td><td>Snapshot history with ancestry tracking</td></tr><tr><td><code>$files</code></td><td>Data files with partition info + column stats</td></tr><tr><td><code>$manifests</code></td><td>Manifest files with file/row counts</td></tr><tr><td><code>$partitions</code></td><td>Partition statistics aggregated from data files</td></tr><tr><td><code>$refs</code></td><td>Branch + tag references</td></tr><tr><td><code>$entries</code></td><td>All manifest entries (incl. deleted)</td></tr><tr><td><code>$all_data_files</code>, <code>$all_delete_files</code>, <code>$all_manifests</code></td><td>Cross-snapshot views</td></tr></tbody></table>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">-- Every snapshot of a single table</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> snapshot_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> is_current</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> sequence_number</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> operation</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain">   analytics</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">hr</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">staff</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">$snapshots</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ORDER</span><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> sequence_number </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DESC</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">LIMIT</span><span class="token plain">  </span><span class="token number">10</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">-- The active head only</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> snapshot_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> sequence_number</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> operation</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain">   analytics</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">hr</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">staff</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">$snapshots</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHERE</span><span class="token plain">  is_current </span><span class="token operator">=</span><span class="token plain"> </span><span class="token boolean">TRUE</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">-- Oldest non-current snapshot (next candidate for EXPIRE)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> snapshot_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> sequence_number</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain">   analytics</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">hr</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">staff</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">$snapshots</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHERE</span><span class="token plain">  </span><span class="token operator">NOT</span><span class="token plain"> is_current</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ORDER</span><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> sequence_number </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ASC</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">LIMIT</span><span class="token plain">  </span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<p>The views are scoped per-table (<code>&lt;catalog&gt;.&lt;schema&gt;.&lt;table&gt;.$snapshots</code>),
not per-schema. For cross-table inventories use
<code>information_schema.tables</code> joined to whichever metadata view
applies.</p>
<p>Previously, <code>$snapshots</code> parsed but silently ignored any
<code>WHERE</code> / <code>ORDER BY</code> / <code>LIMIT</code> you wrote — the system-table path
short-circuited the planner. Now they go through the SQL planner
properly: filters push down, orderings hold, projections work.</p>
<p>The metadata views inherit the same RBAC surface as regular
tables, so audits work without a separate access path.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="snapshots-tab-in-studio">Snapshots tab in studio<a href="https://docs.gnok.io/blog/2026/05/05/iceberg-real-rollback#snapshots-tab-in-studio" class="hash-link" aria-label="Direct link to Snapshots tab in studio" title="Direct link to Snapshots tab in studio" translate="no">​</a></h2>
<p>The studio Catalog Browser's Snapshots tab consumes the new
columns directly — the <code>current</code> badge is sourced from
<code>is_current</code>, parent-child lineage is shown alongside each row,
and the rollback button calls <code>ALTER TABLE … SET SNAPSHOT</code>
under the hood.</p>
<p>If you have a tab open against a rolled-back table, the result
panel shows an amber <strong>"rolled back"</strong> chip below the row count —
because reading from a non-newest snapshot is the kind of bug
people lose hours to, and silently doing it is worse than the
warning.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-it-matters">Why it matters<a href="https://docs.gnok.io/blog/2026/05/05/iceberg-real-rollback#why-it-matters" class="hash-link" aria-label="Direct link to Why it matters" title="Direct link to Why it matters" translate="no">​</a></h2>
<p>Time travel is one of Iceberg's headline features. Half-shipped
time travel is a worse experience than no time travel at all —
users learn not to trust it. The new path is honest about what it
does and exposes the metadata you'd want to verify it.</p>
<p>Try the demo above against your own catalog and watch
<code>is_current</code> flip.</p>]]></content>
        <author>
            <name>Gnok Team</name>
        </author>
        <category label="Iceberg" term="Iceberg"/>
        <category label="Engineering" term="Engineering"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Learned Optimization: Inspecting What AutoML Has Picked Up]]></title>
        <id>https://docs.gnok.io/blog/2026/05/05/learned-advisors</id>
        <link href="https://docs.gnok.io/blog/2026/05/05/learned-advisors"/>
        <updated>2026-05-05T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[After watching ten thousand queries, Gnok has opinions]]></summary>
        <content type="html"><![CDATA[<p>After watching ten thousand queries, Gnok has opinions
about your workload. <strong>Learned scorers</strong> now feed the planner's
partition and index advisors, so those opinions reflect the
cost savings actually observed across your queries — not just
the original heuristics.</p>
<p>The advisors run continuously as part of the service. This post walks through the surface that lets you see
what they've learned: three <code>SHOW … RECOMMENDATIONS</code> commands
that expose the live state of the join-order, materialized-view,
and overall AutoML caches.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="show-recommendations--is-automl-running">SHOW RECOMMENDATIONS — is AutoML running?<a href="https://docs.gnok.io/blog/2026/05/05/learned-advisors#show-recommendations--is-automl-running" class="hash-link" aria-label="Direct link to SHOW RECOMMENDATIONS — is AutoML running?" title="Direct link to SHOW RECOMMENDATIONS — is AutoML running?" translate="no">​</a></h2>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SHOW</span><span class="token plain"> RECOMMENDATIONS</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">enabled | total_queries_recorded | tables_tracked</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">true    | 12,847                 | 84</span><br></span></code></pre></div></div>
<p>That's the AutoML status summary. <code>enabled</code> shows whether
AutoML advisors are on; <code>total_queries_recorded</code> counts the
plans AutoML has observed; <code>tables_tracked</code> is the number of
distinct base tables in the workload profile.</p>
<p>If <code>enabled = false</code> and you expected otherwise, check with an
administrator: Studio shows the same status under
<strong>Operations → AutoML Control</strong>. See
<a class="" href="https://docs.gnok.io/docs/ai-ml/automl-advisors">AutoML Advisors</a> for what is
available to your account.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="show-join-order-recommendations">SHOW JOIN ORDER RECOMMENDATIONS<a href="https://docs.gnok.io/blog/2026/05/05/learned-advisors#show-join-order-recommendations" class="hash-link" aria-label="Direct link to SHOW JOIN ORDER RECOMMENDATIONS" title="Direct link to SHOW JOIN ORDER RECOMMENDATIONS" translate="no">​</a></h2>
<p>The join-order advisor watches the planner's choices and
remembers which join orders consistently produced the cheapest
plans for each table-set:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SHOW</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">JOIN</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ORDER</span><span class="token plain"> RECOMMENDATIONS</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">tables_hash | recommended_order                      | preferred_root | confidence | obs_count | sql_hint</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">8a2f1b…     | analytics.sales.orders, …customers, … | orders         | 0.92       | 184       | /*+ JOIN_ORDER(orders, customers, payments) */</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">b41c…       | analytics.events.clicks, …users        | users          | 0.71       | 36        | /*+ JOIN_ORDER(users, clicks) */</span><br></span></code></pre></div></div>
<p>Each row represents one join-shape the advisor has observed
enough times to score. <code>confidence</code> is bounded in [0, 1]; the
<code>sql_hint</code> is a paste-ready optimizer hint you can drop into a
query that the planner is mis-ordering. <code>obs_count</code> tells you
how much weight to put on the recommendation — a 0.92-confidence
row backed by 184 observations is a different story than a
0.92 backed by 4.</p>
<p>The advisor surfaces these as <strong>passive recommendations</strong> —
the planner already uses the underlying cache when it can; the
hint is for the cases where the user wants to pin the choice.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="show-mv-recommendations">SHOW MV RECOMMENDATIONS<a href="https://docs.gnok.io/blog/2026/05/05/learned-advisors#show-mv-recommendations" class="hash-link" aria-label="Direct link to SHOW MV RECOMMENDATIONS" title="Direct link to SHOW MV RECOMMENDATIONS" translate="no">​</a></h2>
<p>Repeated query patterns the advisor scores as worth
materializing:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SHOW</span><span class="token plain"> MV RECOMMENDATIONS</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">fingerprint | tables                          | filter_columns | repeated_count | savings_score | last_seen           | sql_hint</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">4a91b3      | analytics.sales.orders          | created_at     | 47             | 22.1          | 2026-05-04T14:22:01 | CREATE MATERIALIZED VIEW mv_4a91b3 AS …</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">e7203c      | analytics.events.clicks         | event_date,    | 28             | 18.7          | 2026-05-05T10:11:33 | CREATE MATERIALIZED VIEW mv_e7203c AS …</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            |                                 | user_id        |                |               |                     |</span><br></span></code></pre></div></div>
<p><code>savings_score</code> is the planner's estimate of "total cost saved
across observed queries if this MV existed" — a function of
estimated MV scan cost, base-table scan cost, and how often the
fingerprint shows up. <code>repeated_count</code> is the raw count;
<code>last_seen</code> lets you spot stale recommendations (a workload
shift means yesterday's top candidate may not be today's).</p>
<p>The <code>sql_hint</code> column is a paste-ready <code>CREATE MATERIALIZED VIEW</code> statement.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="whats-actually-learned">What's actually learned<a href="https://docs.gnok.io/blog/2026/05/05/learned-advisors#whats-actually-learned" class="hash-link" aria-label="Direct link to What's actually learned" title="Direct link to What's actually learned" translate="no">​</a></h2>
<p>The partition and index advisors score candidate strategies
for each base table the workload touches. Learned scorers
(gradient-boosted models trained on query history) augment the
original heuristics:</p>
<ul>
<li class=""><strong>The partition advisor</strong> scores <code>(column, strategy)</code> pairs across
the candidate set. Strategies include identity, hash, bucket,
truncate, day, hour-of. Features: column statistics, workload
filter/join/group-by frequencies, predicate distribution,
estimated build cost.</li>
<li class=""><strong>The index advisor</strong> scores <code>(column, kind)</code> pairs — kinds are
<code>btree</code>, <code>bloom</code>, <code>bitmap</code>, <code>hnsw</code>, <code>bm25</code>. Same feature
base, different fit checks.</li>
</ul>
<p>Gnok <strong>applies recommendations passively</strong> (e.g. the
planner consults the join-order cache during its own search,
even when no <code>SHOW</code> is called). The <code>SHOW … RECOMMENDATIONS</code>
commands surface what has accumulated for human inspection.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="whats-not-in-scope-yet">What's not in scope (yet)<a href="https://docs.gnok.io/blog/2026/05/05/learned-advisors#whats-not-in-scope-yet" class="hash-link" aria-label="Direct link to What's not in scope (yet)" title="Direct link to What's not in scope (yet)" translate="no">​</a></h2>
<ul>
<li class=""><strong>No per-table SQL command.</strong> There's no <code>SUGGEST PARTITION FOR &lt;table&gt;</code> form today. The advisor runs over the full
workload-profile cache; surfacing per-table is on the roadmap
alongside an <code>EXPLAIN ADVISOR</code> introspection command.</li>
<li class=""><strong>Auto-apply.</strong> AutoML doesn't apply schema-
changing recommendations on its own. Materialized-view
creation and index builds remain explicit actions you take
(with the <code>sql_hint</code> as a paste-ready starting point).</li>
<li class=""><strong>Cross-tenant generalization.</strong> The learned scorers start
from benchmark-trained baselines; tuning for your workload
happens as your queries accumulate.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="try-it">Try it<a href="https://docs.gnok.io/blog/2026/05/05/learned-advisors#try-it" class="hash-link" aria-label="Direct link to Try it" title="Direct link to Try it" translate="no">​</a></h2>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SHOW</span><span class="token plain"> RECOMMENDATIONS</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SHOW</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">JOIN</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ORDER</span><span class="token plain"> RECOMMENDATIONS</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SHOW</span><span class="token plain"> MV RECOMMENDATIONS</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<p>Run those after your account has been serving real
workloads for a few hours. If the second and third return zero
rows, either the workload is too uniform to recommend anything
useful, or AutoML is disabled (check the first command's
<code>enabled</code> column).</p>
<p>The recommendation cache is rebuilt continuously — re-running
the <code>SHOW</code>s after a few hundred more queries should show the
scores stabilize as the advisor accumulates evidence.</p>]]></content>
        <author>
            <name>Gnok Team</name>
        </author>
        <category label="AI/ML" term="AI/ML"/>
        <category label="Engineering" term="Engineering"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[AI/ML in Gnok Goes Production-Grade: Filtered ANN, Point-in-Time Features, and Schema-Aware Autocomplete]]></title>
        <id>https://docs.gnok.io/blog/2026/04/28/ai-ml-production-grade</id>
        <link href="https://docs.gnok.io/blog/2026/04/28/ai-ml-production-grade"/>
        <updated>2026-04-28T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[The AI/ML stack in Gnok has been usable for a while — register an]]></summary>
        <content type="html"><![CDATA[<p>The AI/ML stack in Gnok has been usable for a while — register an
ONNX model, build an HNSW index, materialize a feature group, run
inference inline. What's new this month is that <strong>every track now
holds up at production scale</strong>: vector search with <code>WHERE</code> clauses
no longer falls back to brute-force, point-in-time training joins
read durably from Iceberg time-travel, and the SQL editor knows
which models you've actually registered.</p>
<p>This post walks through the three biggest changes — filtered ANN,
offline-backed feature lookups, and the schema-aware ML
autocomplete — with the SQL you can run today.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-filtered-ann-vector-search-with-where-clauses-fast">1. Filtered ANN: vector search with WHERE clauses, fast<a href="https://docs.gnok.io/blog/2026/04/28/ai-ml-production-grade#1-filtered-ann-vector-search-with-where-clauses-fast" class="hash-link" aria-label="Direct link to 1. Filtered ANN: vector search with WHERE clauses, fast" title="Direct link to 1. Filtered ANN: vector search with WHERE clauses, fast" translate="no">​</a></h2>
<p>Production vector queries are almost never "find me 10 things
similar to X". They're "find me 10 things similar to X <strong>that are
in stock and in my region</strong>". Until this release, that <code>WHERE</code>
clause forced the planner to brute-force the scan over the entire
table — defeating the purpose of having an HNSW index.</p>
<p>Now the planner picks among three strategies based on the
predicate's selectivity:</p>
<table><thead><tr><th>Strategy</th><th>When</th><th>What it does</th></tr></thead><tbody><tr><td><strong>IndexOnly</strong></td><td>No predicate above the scan</td><td>Pure ANN: probe HNSW for top-k row ids → materialise from source</td></tr><tr><td><strong>PostFilter</strong></td><td>Non-selective predicate (~10%+)</td><td>Probe with overfetch (k × factor / sel), apply filter, take top-k</td></tr><tr><td><strong>PreFilter</strong></td><td>Very selective predicate (&lt;1%)</td><td>Apply filter first → SIMD brute-force ANN over the candidate set</td></tr></tbody></table>
<p>Same query — the planner chooses transparently:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">-- IndexOnly: no predicate. Pure ANN.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> title</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> documents</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ORDER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> COSINE_DISTANCE</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">embedding</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> EMBED</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'refund policy'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">LIMIT</span><span class="token plain"> </span><span class="token number">10</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">-- PostFilter: predicate is ~30% selective. The planner emits</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">-- VectorIndexTopK with k=80 (overfetch=8x), Filter, then</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">-- LIMIT 10 — bounded at 32x to keep memory predictable.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> title</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> documents</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHERE</span><span class="token plain"> region </span><span class="token operator">IN</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'us-east'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'us-west'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ORDER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> COSINE_DISTANCE</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">embedding</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> EMBED</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'refund policy'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">LIMIT</span><span class="token plain"> </span><span class="token number">10</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<p>You can inspect the planner's choice with <code>EXPLAIN</code>:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">EXPLAIN</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> title </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> documents</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHERE</span><span class="token plain"> filed_at </span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'2026-01-01'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ORDER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> COSINE_DISTANCE</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">embedding</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> EMBED</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'contract dispute'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">LIMIT</span><span class="token plain"> </span><span class="token number">50</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<p>A <code>VectorIndexTopK { k = 800, ... }</code> under a <code>Filter → Limit(50)</code>
tells you PostFilter fired with 16× overfetch — the query is going
to be ~10× faster than the brute-force fallback while still
returning the right top-50.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="industries">Industries<a href="https://docs.gnok.io/blog/2026/04/28/ai-ml-production-grade#industries" class="hash-link" aria-label="Direct link to Industries" title="Direct link to Industries" translate="no">​</a></h3>
<ul>
<li class=""><strong>E-commerce.</strong> "Headphones similar to this, in stock and under
$200." The price + stock predicate is moderately selective →
PostFilter wins.</li>
<li class=""><strong>Legal e-discovery.</strong> "Documents semantically similar to this
contract, filed in this 90-day window, by these custodians."
Custodian + time predicates are very selective → PreFilter
gives the best latency.</li>
<li class=""><strong>Streaming personalization.</strong> "Shows like Severance, available
in my region." Region is non-selective in most catalogues →
PostFilter with modest overfetch.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-offline-backed-feature-store-point-in-time-training-without-leaks">2. Offline-Backed Feature Store: point-in-time training without leaks<a href="https://docs.gnok.io/blog/2026/04/28/ai-ml-production-grade#2-offline-backed-feature-store-point-in-time-training-without-leaks" class="hash-link" aria-label="Direct link to 2. Offline-Backed Feature Store: point-in-time training without leaks" title="Direct link to 2. Offline-Backed Feature Store: point-in-time training without leaks" translate="no">​</a></h2>
<p>Online features (the current snapshot, low latency) and offline
features (historical, for training) used to be two separate
systems with two separate APIs and two opportunities to leak.
Gnok's feature store now serves both from a single SQL surface,
backed by Iceberg time-travel underneath.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-create-side">The CREATE side<a href="https://docs.gnok.io/blog/2026/04/28/ai-ml-production-grade#the-create-side" class="hash-link" aria-label="Direct link to The CREATE side" title="Direct link to The CREATE side" translate="no">​</a></h3>
<p>Add an <code>OFFLINE TABLE</code> clause to a feature group and every
materialization runs an <code>INSERT OVERWRITE</code> into the Iceberg table.
The latest snapshot stays in memory for online lookups; older
snapshots are reachable through Iceberg's snapshot history.</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CREATE</span><span class="token plain"> FEATURE </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">GROUP</span><span class="token plain"> user_transaction_features </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    user_id </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BIGINT</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">PRIMARY</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">KEY</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">MATERIALIZED </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> user_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">           </span><span class="token function" style="color:rgb(80, 250, 123)">COUNT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token operator">*</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> tx_count</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">           </span><span class="token function" style="color:rgb(80, 250, 123)">AVG</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">amount</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain">     </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> avg_amount</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">           </span><span class="token function" style="color:rgb(80, 250, 123)">MAX</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">created_at</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> last_tx_time</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">transactions</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">GROUP</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> user_id</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">REFRESH EVERY </span><span class="token number">300</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">OFFLINE </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TABLE</span><span class="token plain"> warehouse</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">feature_store</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">user_transaction_features</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-lookup-side">The lookup side<a href="https://docs.gnok.io/blog/2026/04/28/ai-ml-production-grade#the-lookup-side" class="hash-link" aria-label="Direct link to The lookup side" title="Direct link to The lookup side" translate="no">​</a></h3>
<p>Two UDFs — same SQL surface, different semantics:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">-- Online: live snapshot, O(1), microseconds</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> t</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">transaction_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">       FEATURE_LOOKUP</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'user_transaction_features'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                      t</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">user_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                      </span><span class="token string" style="color:rgb(255, 121, 198)">'avg_amount'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> avg_amt</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">transactions</span><span class="token plain"> t</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHERE</span><span class="token plain"> t</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">created_at </span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">now</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INTERVAL</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'5 minutes'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">-- Point-in-time: rebuilds the snapshot in effect at as_of_ms.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">-- First checks the in-memory snapshot ring; if the timestamp is</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">-- older than the ring retains, falls through to the offline</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">-- Iceberg table via FOR SYSTEM_TIME AS OF time-travel.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> lbl</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">user_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> lbl</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">churned</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">       FEATURE_LOOKUP_AS_OF</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">           </span><span class="token string" style="color:rgb(255, 121, 198)">'user_transaction_features'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">           lbl</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">user_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">           </span><span class="token string" style="color:rgb(255, 121, 198)">'avg_amount'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">           CAST</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">EXTRACT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">EPOCH </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> lbl</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">observed_at</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">*</span><span class="token plain"> </span><span class="token number">1000</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BIGINT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">       </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> avg_at_label_time</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> training_labels lbl</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<p>For training joins that pull many features per entity, use a SQL
JOIN against the offline table directly with explicit time-travel
— the planner pushes down better than per-feature UDF calls:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">-- Multi-feature join: every column reflects the snapshot in effect on Jan 1</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> lbl</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">user_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> lbl</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">churned</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">       fg</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">avg_amount</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> fg</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">tx_count</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> fg</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">last_tx_time</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> training_labels lbl</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">LEFT</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">JOIN</span><span class="token plain"> warehouse</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">feature_store</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">user_transaction_features</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FOR</span><span class="token plain"> SYSTEM_TIME </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">OF</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'2026-01-01T00:00:00Z'</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> fg</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ON</span><span class="token plain"> lbl</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">user_id </span><span class="token operator">=</span><span class="token plain"> fg</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">user_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-this-matters">Why this matters<a href="https://docs.gnok.io/blog/2026/04/28/ai-ml-production-grade#why-this-matters" class="hash-link" aria-label="Direct link to Why this matters" title="Direct link to Why this matters" translate="no">​</a></h3>
<p>The classic mistake in ML training is <strong>temporal leakage</strong>: a
training row with label "user churned on Feb 1" enriched with
features computed today (Apr 28) — the features know things they
shouldn't. The new <code>FEATURE_LOOKUP_AS_OF</code> surface makes the
correct semantic the easy one: pass the label's observation
timestamp, get the features as they existed at that point, no
manual snapshot management.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="industries-1">Industries<a href="https://docs.gnok.io/blog/2026/04/28/ai-ml-production-grade#industries-1" class="hash-link" aria-label="Direct link to Industries" title="Direct link to Industries" translate="no">​</a></h3>
<ul>
<li class=""><strong>Fintech / underwriting.</strong> A claim model joining 20 features
for one applicant, all at the application's submission
timestamp. No leakage; reproducible.</li>
<li class=""><strong>Subscription churn modelling.</strong> Train on "what did this user
look like two weeks before they churned" without time machine
hacks.</li>
<li class=""><strong>Insurance.</strong> Auditable point-in-time joins are a regulatory
requirement, not a nice-to-have.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-schema-aware-ml-autocomplete">3. Schema-Aware ML Autocomplete<a href="https://docs.gnok.io/blog/2026/04/28/ai-ml-production-grade#3-schema-aware-ml-autocomplete" class="hash-link" aria-label="Direct link to 3. Schema-Aware ML Autocomplete" title="Direct link to 3. Schema-Aware ML Autocomplete" translate="no">​</a></h2>
<p>The SQL editor used to suggest <code>ML_PREDICT</code> and <code>FEATURE_LOOKUP</code> as
static keywords. Now, when your cursor sits inside the function's
string-literal arg, it suggests the <strong>actual model and feature-group
names</strong> registered in your catalog:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">ML_PREDICT('fr|</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            ↑ cursor here</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">              suggestions:</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                fraud_detector            model · onnx · v3</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                fraud_classifier          model · pytorch · v1</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                fr_credit_scoring         model · sklearn · v2</span><br></span></code></pre></div></div>
<p>Studio looks up the models, vector indexes, and feature groups
you can see, matches them by case-insensitive substring, and shows
each suggestion's kind, name, and a one-line detail
("model · onnx · v3", "feature group · keys: user_id · 12 features").</p>
<p>The same autocomplete fires inside <code>FEATURE_LOOKUP('|</code>,
<code>FEATURE_LOOKUP_AS_OF('|</code>, <code>EXPLAIN MODEL '|</code>, and <code>ALTER MODEL '|</code> —
Studio detects which kind of object you're naming and only
shows that kind.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-this-matters-1">Why this matters<a href="https://docs.gnok.io/blog/2026/04/28/ai-ml-production-grade#why-this-matters-1" class="hash-link" aria-label="Direct link to Why this matters" title="Direct link to Why this matters" translate="no">​</a></h3>
<p>In a tenant with hundreds of models and feature groups, you stop
tab-switching between the editor and the catalog browser to
remember names. For onboarding new analysts, the editor itself
becomes a discoverability surface.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="whats-next">What's next<a href="https://docs.gnok.io/blog/2026/04/28/ai-ml-production-grade#whats-next" class="hash-link" aria-label="Direct link to What's next" title="Direct link to What's next" translate="no">​</a></h2>
<p>Roadmap items that build on this foundation:</p>
<ul>
<li class=""><strong>In-traversal attribute filtering</strong> so HNSW prunes during the
graph walk</li>
<li class=""><strong>Multi-group point-in-time joins</strong> that fan out across feature
groups in parallel</li>
<li class=""><strong>Per-feature regression detection</strong> — catch models that go bad
on a high-value slice while looking flat in aggregate</li>
<li class=""><strong>Drag-and-drop feature group composer</strong> in Studio for citizen
data scientists</li>
</ul>
<p>If any of the industry scenarios above match yours, let us know —
sequencing follows real demand.</p>]]></content>
        <author>
            <name>Gnok Team</name>
        </author>
        <category label="AI/ML" term="AI/ML"/>
        <category label="Vector Search" term="Vector Search"/>
        <category label="Feature Store" term="Feature Store"/>
        <category label="Autocomplete" term="Autocomplete"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Building a Real-Time Fraud Detection Pipeline with Gnok]]></title>
        <id>https://docs.gnok.io/blog/2026/03/18/fraud-detection-pipeline</id>
        <link href="https://docs.gnok.io/blog/2026/03/18/fraud-detection-pipeline"/>
        <updated>2026-03-18T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[This tutorial builds a complete fraud detection system inside Gnok -- from data ingestion to ML scoring to alerting -- without any external services. We'll use COPY INTO for bulk loading, vector similarity for merchant profiling, statistical anomaly detection for flagging outliers, and natural language queries for ad-hoc investigation.]]></summary>
        <content type="html"><![CDATA[<p>This tutorial builds a complete fraud detection system inside Gnok -- from data ingestion to ML scoring to alerting -- without any external services. We'll use COPY INTO for bulk loading, vector similarity for merchant profiling, statistical anomaly detection for flagging outliers, and natural language queries for ad-hoc investigation.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="architecture">Architecture<a href="https://docs.gnok.io/blog/2026/03/18/fraud-detection-pipeline#architecture" class="hash-link" aria-label="Direct link to Architecture" title="Direct link to Architecture" translate="no">​</a></h2>
<!-- -->
<p>Everything runs inside Gnok. No Kafka, no external model servers, no ETL pipelines.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-1-set-up-the-schema">Step 1: Set Up the Schema<a href="https://docs.gnok.io/blog/2026/03/18/fraud-detection-pipeline#step-1-set-up-the-schema" class="hash-link" aria-label="Direct link to Step 1: Set Up the Schema" title="Direct link to Step 1: Set Up the Schema" translate="no">​</a></h2>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SCHEMA</span><span class="token plain"> fraud_demo</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">USE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SCHEMA</span><span class="token plain"> fraud_demo</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">-- Transaction table</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TABLE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">transactions</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    tx_id           </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BIGINT</span><span class="token plain"> </span><span class="token operator">NOT</span><span class="token plain"> </span><span class="token boolean">NULL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    user_id         </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BIGINT</span><span class="token plain"> </span><span class="token operator">NOT</span><span class="token plain"> </span><span class="token boolean">NULL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    merchant_id     </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BIGINT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    amount          </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DECIMAL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">10</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">2</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    currency        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">VARCHAR</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    tx_type         </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">VARCHAR</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    channel         </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">VARCHAR</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    country         </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">VARCHAR</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    is_international </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BOOLEAN</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    hour_of_day     </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    day_of_week     </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    tx_timestamp    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TIMESTAMP</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">PARTITIONED </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">day</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">tx_timestamp</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">-- Merchant table with embeddings</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TABLE</span><span class="token plain"> merchants </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    merchant_id     </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BIGINT</span><span class="token plain"> </span><span class="token operator">NOT</span><span class="token plain"> </span><span class="token boolean">NULL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    name            </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">VARCHAR</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    category        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">VARCHAR</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    risk_tier       </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">VARCHAR</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    avg_ticket      </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DECIMAL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">10</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">2</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    embedding       VECTOR</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">128</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-2-bulk-load-historical-data">Step 2: Bulk Load Historical Data<a href="https://docs.gnok.io/blog/2026/03/18/fraud-detection-pipeline#step-2-bulk-load-historical-data" class="hash-link" aria-label="Direct link to Step 2: Bulk Load Historical Data" title="Direct link to Step 2: Bulk Load Historical Data" translate="no">​</a></h2>
<p>Upload transaction data to a stage, then load it:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">-- Load historical transactions from Parquet</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">COPY </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INTO</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">transactions</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'@stage/fraud_demo/transactions/'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">FILE_FORMAT </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TYPE</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> PARQUET</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">-- Load merchant data from CSV</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">COPY </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INTO</span><span class="token plain"> merchants</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'@stage/fraud_demo/merchants/'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">FILE_FORMAT </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TYPE</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> CSV</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DELIMITER</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">','</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    SKIP_ROWS </span><span class="token operator">=</span><span class="token plain"> </span><span class="token number">1</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<p>Gnok's COPY INTO automatically parallelizes across workers and routes rows to the correct partitions. For our transaction table partitioned by <code>day(tx_timestamp)</code>, each day's data ends up in optimally-sized files.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="verify-the-load">Verify the Load<a href="https://docs.gnok.io/blog/2026/03/18/fraud-detection-pipeline#verify-the-load" class="hash-link" aria-label="Direct link to Verify the Load" title="Direct link to Verify the Load" translate="no">​</a></h3>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">COUNT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token operator">*</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> total_txns</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">       </span><span class="token function" style="color:rgb(80, 250, 123)">MIN</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">tx_timestamp</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> earliest</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">       </span><span class="token function" style="color:rgb(80, 250, 123)">MAX</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">tx_timestamp</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> latest</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">transactions</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">COUNT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token operator">*</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> merchant_count</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">       </span><span class="token function" style="color:rgb(80, 250, 123)">COUNT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DISTINCT</span><span class="token plain"> category</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> categories</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> merchants</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-3-build-user-behavior-profiles">Step 3: Build User Behavior Profiles<a href="https://docs.gnok.io/blog/2026/03/18/fraud-detection-pipeline#step-3-build-user-behavior-profiles" class="hash-link" aria-label="Direct link to Step 3: Build User Behavior Profiles" title="Direct link to Step 3: Build User Behavior Profiles" translate="no">​</a></h2>
<p>Fraud detection works best when you can compare a transaction against the user's normal behavior. Let's compute per-user statistics:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TABLE</span><span class="token plain"> user_profiles </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    user_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token function" style="color:rgb(80, 250, 123)">COUNT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token operator">*</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain">                          </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> tx_count_total</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token function" style="color:rgb(80, 250, 123)">AVG</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">amount</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain">                       </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> avg_amount</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    STDDEV</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">amount</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain">                    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> stddev_amount</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    PERCENTILE_CONT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">0.95</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WITHIN</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">GROUP</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ORDER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> amount</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> p95_amount</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token function" style="color:rgb(80, 250, 123)">COUNT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DISTINCT</span><span class="token plain"> merchant_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain">       </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> unique_merchants</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token function" style="color:rgb(80, 250, 123)">COUNT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DISTINCT</span><span class="token plain"> country</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain">           </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> unique_countries</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token function" style="color:rgb(80, 250, 123)">SUM</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CASE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHEN</span><span class="token plain"> is_international </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">THEN</span><span class="token plain"> </span><span class="token number">1</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ELSE</span><span class="token plain"> </span><span class="token number">0</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">END</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> intl_tx_count</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">MODE</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">channel</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain">                     </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> preferred_channel</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token function" style="color:rgb(80, 250, 123)">MAX</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">tx_timestamp</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain">                 </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> last_tx_time</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">transactions</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">GROUP</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> user_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<p>Check the profile for a specific user:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> </span><span class="token operator">*</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> user_profiles </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHERE</span><span class="token plain"> user_id </span><span class="token operator">=</span><span class="token plain"> </span><span class="token number">12345</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-4-statistical-anomaly-scoring">Step 4: Statistical Anomaly Scoring<a href="https://docs.gnok.io/blog/2026/03/18/fraud-detection-pipeline#step-4-statistical-anomaly-scoring" class="hash-link" aria-label="Direct link to Step 4: Statistical Anomaly Scoring" title="Direct link to Step 4: Statistical Anomaly Scoring" translate="no">​</a></h2>
<p>Now let's build a scoring query that flags anomalies using multiple signals.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="amount-anomaly-z-score">Amount Anomaly (Z-Score)<a href="https://docs.gnok.io/blog/2026/03/18/fraud-detection-pipeline#amount-anomaly-z-score" class="hash-link" aria-label="Direct link to Amount Anomaly (Z-Score)" title="Direct link to Amount Anomaly (Z-Score)" translate="no">​</a></h3>
<p>Compare each transaction's amount against the user's historical distribution:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    t</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">tx_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    t</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">user_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    t</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">amount</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    u</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">avg_amount</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    u</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">stddev_amount</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token comment" style="color:rgb(98, 114, 164)">-- Z-score: how many standard deviations from the user's mean</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CASE</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHEN</span><span class="token plain"> u</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">stddev_amount </span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token number">0</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">THEN</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">t</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">amount </span><span class="token operator">-</span><span class="token plain"> u</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">avg_amount</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">/</span><span class="token plain"> u</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">stddev_amount</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ELSE</span><span class="token plain"> </span><span class="token number">0</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">END</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> amount_zscore</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token comment" style="color:rgb(98, 114, 164)">-- Flag if more than 2.5 standard deviations</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CASE</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHEN</span><span class="token plain"> u</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">stddev_amount </span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token number">0</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">             </span><span class="token operator">AND</span><span class="token plain"> ABS</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">t</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">amount </span><span class="token operator">-</span><span class="token plain"> u</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">avg_amount</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">/</span><span class="token plain"> u</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">stddev_amount</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token number">2.5</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">THEN</span><span class="token plain"> </span><span class="token boolean">true</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ELSE</span><span class="token plain"> </span><span class="token boolean">false</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">END</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> amount_anomaly</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">transactions</span><span class="token plain"> t</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">JOIN</span><span class="token plain"> user_profiles u </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ON</span><span class="token plain"> t</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">user_id </span><span class="token operator">=</span><span class="token plain"> u</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">user_id</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHERE</span><span class="token plain"> t</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">tx_timestamp </span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CURRENT_TIMESTAMP</span><span class="token plain"> </span><span class="token operator">-</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INTERVAL</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'1'</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">HOUR</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="velocity-anomaly">Velocity Anomaly<a href="https://docs.gnok.io/blog/2026/03/18/fraud-detection-pipeline#velocity-anomaly" class="hash-link" aria-label="Direct link to Velocity Anomaly" title="Direct link to Velocity Anomaly" translate="no">​</a></h3>
<p>Flag users with unusual transaction frequency:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WITH</span><span class="token plain"> recent_velocity </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        user_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token function" style="color:rgb(80, 250, 123)">COUNT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token operator">*</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> tx_last_hour</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token function" style="color:rgb(80, 250, 123)">SUM</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">amount</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> total_last_hour</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">transactions</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHERE</span><span class="token plain"> tx_timestamp </span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CURRENT_TIMESTAMP</span><span class="token plain"> </span><span class="token operator">-</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INTERVAL</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'1'</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">HOUR</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">GROUP</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> user_id</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    v</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">user_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    v</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">tx_last_hour</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    v</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">total_last_hour</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token comment" style="color:rgb(98, 114, 164)">-- Compare against historical daily average</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    u</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">tx_count_total </span><span class="token operator">/</span><span class="token plain"> </span><span class="token number">30.0</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> avg_daily_rate</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token comment" style="color:rgb(98, 114, 164)">-- Velocity ratio: how many times above normal</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    v</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">tx_last_hour </span><span class="token operator">/</span><span class="token plain"> GREATEST</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">u</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">tx_count_total </span><span class="token operator">/</span><span class="token plain"> </span><span class="token number">720.0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">0.1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> velocity_ratio</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> recent_velocity v</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">JOIN</span><span class="token plain"> user_profiles u </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ON</span><span class="token plain"> v</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">user_id </span><span class="token operator">=</span><span class="token plain"> u</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">user_id</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHERE</span><span class="token plain"> v</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">tx_last_hour </span><span class="token operator">/</span><span class="token plain"> GREATEST</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">u</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">tx_count_total </span><span class="token operator">/</span><span class="token plain"> </span><span class="token number">720.0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">0.1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token number">5.0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="geographic-anomaly">Geographic Anomaly<a href="https://docs.gnok.io/blog/2026/03/18/fraud-detection-pipeline#geographic-anomaly" class="hash-link" aria-label="Direct link to Geographic Anomaly" title="Direct link to Geographic Anomaly" translate="no">​</a></h3>
<p>Flag transactions from countries the user has never used before:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WITH</span><span class="token plain"> user_countries </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> user_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> country</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">transactions</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">GROUP</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> user_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> country</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    t</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">tx_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    t</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">user_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    t</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">country </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> tx_country</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token boolean">true</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> new_country_flag</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">transactions</span><span class="token plain"> t</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">LEFT</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">JOIN</span><span class="token plain"> user_countries uc</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ON</span><span class="token plain"> t</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">user_id </span><span class="token operator">=</span><span class="token plain"> uc</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">user_id </span><span class="token operator">AND</span><span class="token plain"> t</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">country </span><span class="token operator">=</span><span class="token plain"> uc</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">country</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHERE</span><span class="token plain"> t</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">tx_timestamp </span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CURRENT_TIMESTAMP</span><span class="token plain"> </span><span class="token operator">-</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INTERVAL</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'1'</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">HOUR</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token operator">AND</span><span class="token plain"> uc</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">country </span><span class="token operator">IS</span><span class="token plain"> </span><span class="token boolean">NULL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-5-merchant-similarity-analysis">Step 5: Merchant Similarity Analysis<a href="https://docs.gnok.io/blog/2026/03/18/fraud-detection-pipeline#step-5-merchant-similarity-analysis" class="hash-link" aria-label="Direct link to Step 5: Merchant Similarity Analysis" title="Direct link to Step 5: Merchant Similarity Analysis" translate="no">​</a></h2>
<p>Use vector distance to find merchants similar to known high-risk merchants:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">-- Find merchants whose embeddings are closest to a known fraudulent merchant</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WITH</span><span class="token plain"> fraud_merchant </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> embedding</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> merchants</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHERE</span><span class="token plain"> merchant_id </span><span class="token operator">=</span><span class="token plain"> </span><span class="token number">9999</span><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)">-- known fraud merchant</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    m</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">merchant_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    m</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    m</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">category</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    COSINE_DISTANCE</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">m</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">embedding</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> fm</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">embedding</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> similarity_distance</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> merchants m</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CROSS</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">JOIN</span><span class="token plain"> fraud_merchant fm</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHERE</span><span class="token plain"> m</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">merchant_id </span><span class="token operator">&lt;&gt;</span><span class="token plain"> </span><span class="token number">9999</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ORDER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> similarity_distance </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ASC</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">LIMIT</span><span class="token plain"> </span><span class="token number">20</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<p>Compute average embedding per merchant category for cluster analysis:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    category</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token function" style="color:rgb(80, 250, 123)">COUNT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token operator">*</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> merchant_count</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    VECTOR_AVG</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">embedding</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> category_centroid</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> merchants</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">GROUP</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> category</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-6-combined-risk-score">Step 6: Combined Risk Score<a href="https://docs.gnok.io/blog/2026/03/18/fraud-detection-pipeline#step-6-combined-risk-score" class="hash-link" aria-label="Direct link to Step 6: Combined Risk Score" title="Direct link to Step 6: Combined Risk Score" translate="no">​</a></h2>
<p>Pull all the signals together into a single scoring query:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">VIEW</span><span class="token plain"> fraud_scores </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WITH</span><span class="token plain"> amount_scores </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        t</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">tx_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        t</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">user_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        t</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">merchant_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        t</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">amount</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        t</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">country</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        t</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">tx_timestamp</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        t</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">is_international</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CASE</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHEN</span><span class="token plain"> u</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">stddev_amount </span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token number">0</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">THEN</span><span class="token plain"> ABS</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">t</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">amount </span><span class="token operator">-</span><span class="token plain"> u</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">avg_amount</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">/</span><span class="token plain"> u</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">stddev_amount</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ELSE</span><span class="token plain"> </span><span class="token number">0</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">END</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> amount_zscore</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        t</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">amount </span><span class="token operator">&gt;</span><span class="token plain"> u</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">p95_amount </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> exceeds_p95</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">transactions</span><span class="token plain"> t</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">JOIN</span><span class="token plain"> user_profiles u </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ON</span><span class="token plain"> t</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">user_id </span><span class="token operator">=</span><span class="token plain"> u</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">user_id</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHERE</span><span class="token plain"> t</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">tx_timestamp </span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CURRENT_TIMESTAMP</span><span class="token plain"> </span><span class="token operator">-</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INTERVAL</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'1'</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">HOUR</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">velocity_scores </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        user_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token function" style="color:rgb(80, 250, 123)">COUNT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token operator">*</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> tx_last_hour</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">transactions</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHERE</span><span class="token plain"> tx_timestamp </span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CURRENT_TIMESTAMP</span><span class="token plain"> </span><span class="token operator">-</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INTERVAL</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'1'</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">HOUR</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">GROUP</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> user_id</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">geo_scores </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DISTINCT</span><span class="token plain"> t</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">tx_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token boolean">true</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> new_country</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">transactions</span><span class="token plain"> t</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">LEFT</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">JOIN</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> user_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> country </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">transactions</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">GROUP</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> user_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> country</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> hist </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ON</span><span class="token plain"> t</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">user_id </span><span class="token operator">=</span><span class="token plain"> hist</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">user_id </span><span class="token operator">AND</span><span class="token plain"> t</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">country </span><span class="token operator">=</span><span class="token plain"> hist</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">country</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHERE</span><span class="token plain"> t</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">tx_timestamp </span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CURRENT_TIMESTAMP</span><span class="token plain"> </span><span class="token operator">-</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INTERVAL</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'1'</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">HOUR</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token operator">AND</span><span class="token plain"> hist</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">country </span><span class="token operator">IS</span><span class="token plain"> </span><span class="token boolean">NULL</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    a</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">tx_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    a</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">user_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    a</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">amount</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    a</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">country</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    a</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">tx_timestamp</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    m</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">category </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> merchant_category</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    m</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">risk_tier </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> merchant_risk_tier</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token comment" style="color:rgb(98, 114, 164)">-- Individual signals</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    a</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">amount_zscore</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    a</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">exceeds_p95</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">COALESCE</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">v</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">tx_last_hour</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> tx_last_hour</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">COALESCE</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">g</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">new_country</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token boolean">false</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> new_country</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token comment" style="color:rgb(98, 114, 164)">-- Combined risk score (weighted sum, 0-100)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    LEAST</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">100</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">ROUND</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CASE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHEN</span><span class="token plain"> a</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">amount_zscore </span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token number">3.0</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">THEN</span><span class="token plain"> </span><span class="token number">30</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHEN</span><span class="token plain"> a</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">amount_zscore </span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token number">2.0</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">THEN</span><span class="token plain"> </span><span class="token number">15</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ELSE</span><span class="token plain"> </span><span class="token number">0</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">END</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token operator">+</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CASE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHEN</span><span class="token plain"> a</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">exceeds_p95 </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">THEN</span><span class="token plain"> </span><span class="token number">15</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ELSE</span><span class="token plain"> </span><span class="token number">0</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">END</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token operator">+</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CASE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHEN</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">COALESCE</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">v</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">tx_last_hour</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token number">10</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">THEN</span><span class="token plain"> </span><span class="token number">25</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHEN</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">COALESCE</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">v</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">tx_last_hour</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token number">5</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">THEN</span><span class="token plain"> </span><span class="token number">10</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ELSE</span><span class="token plain"> </span><span class="token number">0</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">END</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token operator">+</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CASE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHEN</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">COALESCE</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">g</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">new_country</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token boolean">false</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">THEN</span><span class="token plain"> </span><span class="token number">20</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ELSE</span><span class="token plain"> </span><span class="token number">0</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">END</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token operator">+</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CASE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHEN</span><span class="token plain"> m</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">risk_tier </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'high'</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">THEN</span><span class="token plain"> </span><span class="token number">15</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHEN</span><span class="token plain"> m</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">risk_tier </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'medium'</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">THEN</span><span class="token plain"> </span><span class="token number">5</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ELSE</span><span class="token plain"> </span><span class="token number">0</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">END</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token operator">+</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CASE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHEN</span><span class="token plain"> a</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">is_international </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">THEN</span><span class="token plain"> </span><span class="token number">5</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ELSE</span><span class="token plain"> </span><span class="token number">0</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">END</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> risk_score</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token comment" style="color:rgb(98, 114, 164)">-- Risk level classification</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CASE</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHEN</span><span class="token plain"> LEAST</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">100</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">ROUND</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CASE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHEN</span><span class="token plain"> a</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">amount_zscore </span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token number">3.0</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">THEN</span><span class="token plain"> </span><span class="token number">30</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHEN</span><span class="token plain"> a</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">amount_zscore </span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token number">2.0</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">THEN</span><span class="token plain"> </span><span class="token number">15</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ELSE</span><span class="token plain"> </span><span class="token number">0</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">END</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token operator">+</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CASE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHEN</span><span class="token plain"> a</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">exceeds_p95 </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">THEN</span><span class="token plain"> </span><span class="token number">15</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ELSE</span><span class="token plain"> </span><span class="token number">0</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">END</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token operator">+</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CASE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHEN</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">COALESCE</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">v</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">tx_last_hour</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token number">10</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">THEN</span><span class="token plain"> </span><span class="token number">25</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHEN</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">COALESCE</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">v</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">tx_last_hour</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token number">5</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">THEN</span><span class="token plain"> </span><span class="token number">10</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ELSE</span><span class="token plain"> </span><span class="token number">0</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">END</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token operator">+</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CASE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHEN</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">COALESCE</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">g</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">new_country</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token boolean">false</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">THEN</span><span class="token plain"> </span><span class="token number">20</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ELSE</span><span class="token plain"> </span><span class="token number">0</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">END</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token operator">+</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CASE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHEN</span><span class="token plain"> m</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">risk_tier </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'high'</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">THEN</span><span class="token plain"> </span><span class="token number">15</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHEN</span><span class="token plain"> m</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">risk_tier </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'medium'</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">THEN</span><span class="token plain"> </span><span class="token number">5</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ELSE</span><span class="token plain"> </span><span class="token number">0</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">END</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token operator">+</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CASE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHEN</span><span class="token plain"> a</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">is_international </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">THEN</span><span class="token plain"> </span><span class="token number">5</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ELSE</span><span class="token plain"> </span><span class="token number">0</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">END</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">&gt;=</span><span class="token plain"> </span><span class="token number">60</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">THEN</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'CRITICAL'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHEN</span><span class="token plain"> LEAST</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">100</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">ROUND</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CASE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHEN</span><span class="token plain"> a</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">amount_zscore </span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token number">3.0</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">THEN</span><span class="token plain"> </span><span class="token number">30</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHEN</span><span class="token plain"> a</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">amount_zscore </span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token number">2.0</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">THEN</span><span class="token plain"> </span><span class="token number">15</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ELSE</span><span class="token plain"> </span><span class="token number">0</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">END</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token operator">+</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CASE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHEN</span><span class="token plain"> a</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">exceeds_p95 </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">THEN</span><span class="token plain"> </span><span class="token number">15</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ELSE</span><span class="token plain"> </span><span class="token number">0</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">END</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token operator">+</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CASE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHEN</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">COALESCE</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">v</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">tx_last_hour</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token number">10</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">THEN</span><span class="token plain"> </span><span class="token number">25</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHEN</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">COALESCE</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">v</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">tx_last_hour</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token number">5</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">THEN</span><span class="token plain"> </span><span class="token number">10</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ELSE</span><span class="token plain"> </span><span class="token number">0</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">END</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token operator">+</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CASE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHEN</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">COALESCE</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">g</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">new_country</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token boolean">false</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">THEN</span><span class="token plain"> </span><span class="token number">20</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ELSE</span><span class="token plain"> </span><span class="token number">0</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">END</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token operator">+</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CASE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHEN</span><span class="token plain"> m</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">risk_tier </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'high'</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">THEN</span><span class="token plain"> </span><span class="token number">15</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHEN</span><span class="token plain"> m</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">risk_tier </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'medium'</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">THEN</span><span class="token plain"> </span><span class="token number">5</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ELSE</span><span class="token plain"> </span><span class="token number">0</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">END</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token operator">+</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CASE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHEN</span><span class="token plain"> a</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">is_international </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">THEN</span><span class="token plain"> </span><span class="token number">5</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ELSE</span><span class="token plain"> </span><span class="token number">0</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">END</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">&gt;=</span><span class="token plain"> </span><span class="token number">40</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">THEN</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'HIGH'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHEN</span><span class="token plain"> LEAST</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">100</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">ROUND</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CASE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHEN</span><span class="token plain"> a</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">amount_zscore </span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token number">3.0</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">THEN</span><span class="token plain"> </span><span class="token number">30</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHEN</span><span class="token plain"> a</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">amount_zscore </span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token number">2.0</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">THEN</span><span class="token plain"> </span><span class="token number">15</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ELSE</span><span class="token plain"> </span><span class="token number">0</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">END</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token operator">+</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CASE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHEN</span><span class="token plain"> a</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">exceeds_p95 </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">THEN</span><span class="token plain"> </span><span class="token number">15</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ELSE</span><span class="token plain"> </span><span class="token number">0</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">END</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token operator">+</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CASE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHEN</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">COALESCE</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">v</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">tx_last_hour</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token number">10</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">THEN</span><span class="token plain"> </span><span class="token number">25</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHEN</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">COALESCE</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">v</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">tx_last_hour</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token number">5</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">THEN</span><span class="token plain"> </span><span class="token number">10</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ELSE</span><span class="token plain"> </span><span class="token number">0</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">END</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token operator">+</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CASE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHEN</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">COALESCE</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">g</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">new_country</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token boolean">false</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">THEN</span><span class="token plain"> </span><span class="token number">20</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ELSE</span><span class="token plain"> </span><span class="token number">0</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">END</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token operator">+</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CASE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHEN</span><span class="token plain"> m</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">risk_tier </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'high'</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">THEN</span><span class="token plain"> </span><span class="token number">15</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHEN</span><span class="token plain"> m</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">risk_tier </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'medium'</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">THEN</span><span class="token plain"> </span><span class="token number">5</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ELSE</span><span class="token plain"> </span><span class="token number">0</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">END</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token operator">+</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CASE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHEN</span><span class="token plain"> a</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">is_international </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">THEN</span><span class="token plain"> </span><span class="token number">5</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ELSE</span><span class="token plain"> </span><span class="token number">0</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">END</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">&gt;=</span><span class="token plain"> </span><span class="token number">20</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">THEN</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'MEDIUM'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ELSE</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'LOW'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">END</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> risk_level</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> amount_scores a</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">LEFT</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">JOIN</span><span class="token plain"> velocity_scores v </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ON</span><span class="token plain"> a</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">user_id </span><span class="token operator">=</span><span class="token plain"> v</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">user_id</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">LEFT</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">JOIN</span><span class="token plain"> geo_scores g </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ON</span><span class="token plain"> a</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">tx_id </span><span class="token operator">=</span><span class="token plain"> g</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">tx_id</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">LEFT</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">JOIN</span><span class="token plain"> merchants m </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ON</span><span class="token plain"> a</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">merchant_id </span><span class="token operator">=</span><span class="token plain"> m</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">merchant_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="query-the-scores">Query the Scores<a href="https://docs.gnok.io/blog/2026/03/18/fraud-detection-pipeline#query-the-scores" class="hash-link" aria-label="Direct link to Query the Scores" title="Direct link to Query the Scores" translate="no">​</a></h3>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">-- Top 20 highest-risk transactions in the last hour</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> </span><span class="token operator">*</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> fraud_scores</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ORDER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> risk_score </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DESC</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">LIMIT</span><span class="token plain"> </span><span class="token number">20</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">-- Summary by risk level</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    risk_level</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token function" style="color:rgb(80, 250, 123)">COUNT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token operator">*</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> tx_count</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token function" style="color:rgb(80, 250, 123)">SUM</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">amount</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> total_amount</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token function" style="color:rgb(80, 250, 123)">AVG</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">risk_score</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> avg_score</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> fraud_scores</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">GROUP</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> risk_level</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ORDER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> avg_score </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DESC</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">-- Critical alerts with full context</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    tx_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> user_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> amount</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> country</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    merchant_category</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> merchant_risk_tier</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    risk_score</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    amount_zscore</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    tx_last_hour</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    new_country</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> fraud_scores</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHERE</span><span class="token plain"> risk_level </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'CRITICAL'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ORDER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> risk_score </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DESC</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-7-connect-a-dashboard">Step 7: Connect a Dashboard<a href="https://docs.gnok.io/blog/2026/03/18/fraud-detection-pipeline#step-7-connect-a-dashboard" class="hash-link" aria-label="Direct link to Step 7: Connect a Dashboard" title="Direct link to Step 7: Connect a Dashboard" translate="no">​</a></h2>
<p>Connect your BI tool to your account's assigned PostgreSQL-compatible endpoint (see <a class="" href="https://docs.gnok.io/docs/connectivity/overview">Connect an Application</a> and <a class="" href="https://docs.gnok.io/docs/connectivity/postgres-wire">PostgreSQL wire protocol</a>), or build a Studio dashboard, on the <code>fraud_scores</code> view:</p>
<p><strong>Dashboard panels:</strong></p>
<ul>
<li class=""><strong>Risk distribution</strong> — pie chart of risk_level counts</li>
<li class=""><strong>Top flagged transactions</strong> — table sorted by risk_score DESC</li>
<li class=""><strong>Risk by merchant category</strong> — bar chart of avg risk_score per category</li>
<li class=""><strong>Hourly trend</strong> — time series of CRITICAL + HIGH transaction counts</li>
<li class=""><strong>Geographic heat map</strong> — risk scores by country</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-8-investigate-with-natural-language">Step 8: Investigate with Natural Language<a href="https://docs.gnok.io/blog/2026/03/18/fraud-detection-pipeline#step-8-investigate-with-natural-language" class="hash-link" aria-label="Direct link to Step 8: Investigate with Natural Language" title="Direct link to Step 8: Investigate with Natural Language" translate="no">​</a></h2>
<p>When an alert fires, use <code>ASK</code> for quick investigation. <code>ASK</code> uses Gnok's AI provider, so an organization administrator must first enable AI Governance for your organization (see <a class="" href="https://docs.gnok.io/docs/ai-ml/natural-language">Natural Language Queries</a>):</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">-- Set context</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">USE</span><span class="token plain"> CATALOG my_catalog</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">USE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SCHEMA</span><span class="token plain"> fraud_demo</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">-- Investigate</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">ASK </span><span class="token string" style="color:rgb(255, 121, 198)">'Show me all transactions for user 12345 in the last 24 hours, sorted by time'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">ASK </span><span class="token string" style="color:rgb(255, 121, 198)">'What merchant categories have the most critical-risk transactions today?'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">ASK </span><span class="token string" style="color:rgb(255, 121, 198)">'How does today''s high-risk transaction count compare to the last 7 days?'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">ASK </span><span class="token string" style="color:rgb(255, 121, 198)">'Find users with more than 10 transactions in the last hour'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-9-time-travel-for-forensics">Step 9: Time Travel for Forensics<a href="https://docs.gnok.io/blog/2026/03/18/fraud-detection-pipeline#step-9-time-travel-for-forensics" class="hash-link" aria-label="Direct link to Step 9: Time Travel for Forensics" title="Direct link to Step 9: Time Travel for Forensics" translate="no">​</a></h2>
<p>When investigating a confirmed fraud case, use time travel to reconstruct what happened:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">-- What did the user's profile look like before the fraud?</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> </span><span class="token operator">*</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> user_profiles</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">AT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TIMESTAMP</span><span class="token plain"> </span><span class="token operator">=</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'2026-03-15 08:00:00'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHERE</span><span class="token plain"> user_id </span><span class="token operator">=</span><span class="token plain"> </span><span class="token number">12345</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">-- Compare with current profile</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> </span><span class="token operator">*</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> user_profiles</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHERE</span><span class="token plain"> user_id </span><span class="token operator">=</span><span class="token plain"> </span><span class="token number">12345</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">-- Track how the user's transaction pattern changed</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    DATE_TRUNC</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'day'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> tx_timestamp</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">day</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token function" style="color:rgb(80, 250, 123)">COUNT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token operator">*</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> tx_count</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token function" style="color:rgb(80, 250, 123)">SUM</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">amount</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> total_amount</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token function" style="color:rgb(80, 250, 123)">AVG</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">amount</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> avg_amount</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token function" style="color:rgb(80, 250, 123)">COUNT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DISTINCT</span><span class="token plain"> country</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> countries</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">transactions</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHERE</span><span class="token plain"> user_id </span><span class="token operator">=</span><span class="token plain"> </span><span class="token number">12345</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token operator">AND</span><span class="token plain"> tx_timestamp </span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CURRENT_TIMESTAMP</span><span class="token plain"> </span><span class="token operator">-</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INTERVAL</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'30'</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DAY</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">GROUP</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> DATE_TRUNC</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'day'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> tx_timestamp</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ORDER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">day</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-10-ongoing-maintenance">Step 10: Ongoing Maintenance<a href="https://docs.gnok.io/blog/2026/03/18/fraud-detection-pipeline#step-10-ongoing-maintenance" class="hash-link" aria-label="Direct link to Step 10: Ongoing Maintenance" title="Direct link to Step 10: Ongoing Maintenance" translate="no">​</a></h2>
<p>Keep the pipeline running smoothly:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">-- Refresh user profiles (run hourly or on a schedule)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TABLE</span><span class="token plain"> user_profiles_new </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">/* same query as Step 3 */</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DROP</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TABLE</span><span class="token plain"> user_profiles</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ALTER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TABLE</span><span class="token plain"> user_profiles_new </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">RENAME</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TO</span><span class="token plain"> user_profiles</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">-- Compact transaction data (run daily)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">OPTIMIZE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TABLE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">transactions</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">-- Clean up old snapshots (run weekly)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ALTER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TABLE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">transactions</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">EXECUTE</span><span class="token plain"> EXPIRE_SNAPSHOTS</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SET</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'older-than'</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'2026-03-01T00:00:00Z'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">VACUUM </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TABLE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">transactions</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="summary">Summary<a href="https://docs.gnok.io/blog/2026/03/18/fraud-detection-pipeline#summary" class="hash-link" aria-label="Direct link to Summary" title="Direct link to Summary" translate="no">​</a></h2>
<p>What we built:</p>
<table><thead><tr><th>Component</th><th>Gnok Feature</th><th>Purpose</th></tr></thead><tbody><tr><td>Data ingestion</td><td>COPY INTO</td><td>Bulk load historical transactions</td></tr><tr><td>User profiling</td><td>CTAS + aggregates</td><td>Compute behavioral baselines</td></tr><tr><td>Amount anomaly</td><td>Z-score calculation</td><td>Flag unusual transaction amounts</td></tr><tr><td>Velocity check</td><td>Windowed counts</td><td>Detect burst transaction patterns</td></tr><tr><td>Geographic check</td><td>LEFT JOIN anti-pattern</td><td>Flag new-country transactions</td></tr><tr><td>Merchant risk</td><td>COSINE_DISTANCE + VECTOR_AVG</td><td>Profile merchant similarity</td></tr><tr><td>Combined scoring</td><td>Weighted CASE expression</td><td>Unified 0-100 risk score</td></tr><tr><td>Dashboard</td><td>PostgreSQL-compatible endpoint</td><td>BI visualization</td></tr><tr><td>Investigation</td><td>ASK (NLI)</td><td>Natural language forensics</td></tr><tr><td>Forensics</td><td>Time travel</td><td>Historical state reconstruction</td></tr><tr><td>Maintenance</td><td>OPTIMIZE + VACUUM</td><td>Keep performance optimal</td></tr></tbody></table>
<p>Apart from the <code>ASK</code> investigation step, which calls Gnok's AI provider, all of this runs inside Gnok. The scoring view executes in-engine with partition pruning, SIMD-accelerated aggregation, and distributed MPP execution.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="further-reading">Further Reading<a href="https://docs.gnok.io/blog/2026/03/18/fraud-detection-pipeline#further-reading" class="hash-link" aria-label="Direct link to Further Reading" title="Direct link to Further Reading" translate="no">​</a></h2>
<ul>
<li class=""><a class="" href="https://docs.gnok.io/docs/ai-ml/tutorials">AI &amp; ML Tutorials</a> -- model training, ONNX inference, feature groups, streaming scoring</li>
<li class=""><a class="" href="https://docs.gnok.io/docs/ai-ml/vector-operations">Vector Operations</a> -- distance functions reference</li>
<li class=""><a class="" href="https://docs.gnok.io/docs/sql-reference/select#time-travel">Time Travel</a> -- snapshot query syntax</li>
<li class=""><a class="" href="https://docs.gnok.io/docs/sql-reference/dml#copy-into">COPY INTO</a> -- bulk loading reference</li>
<li class=""><a class="" href="https://docs.gnok.io/docs/operations/table-maintenance">Table Maintenance</a> -- OPTIMIZE, VACUUM schedules</li>
<li class=""><a class="" href="https://docs.gnok.io/docs/connectivity/overview">Connect an Application</a> -- SQL clients and BI tools</li>
</ul>]]></content>
        <author>
            <name>Gnok Team</name>
        </author>
        <category label="Tutorial" term="Tutorial"/>
        <category label="AI/ML" term="AI/ML"/>
        <category label="Engineering" term="Engineering"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Iceberg v3 in Gnok: Deletion Vectors, VARIANT, Spatial Types, and Row Lineage]]></title>
        <id>https://docs.gnok.io/blog/2026/03/15/iceberg-v3</id>
        <link href="https://docs.gnok.io/blog/2026/03/15/iceberg-v3"/>
        <updated>2026-03-15T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Apache Iceberg format version 3 brings four major capabilities: deletion vectors for more efficient row-level mutations, the VARIANT type for semi-structured data, GEOMETRY/GEOGRAPHY types for spatial analytics, and row lineage for stable row identity across compaction. Gnok now supports all of them.]]></summary>
        <content type="html"><![CDATA[<p>Apache Iceberg format version 3 brings four major capabilities: deletion vectors for more efficient row-level mutations, the VARIANT type for semi-structured data, GEOMETRY/GEOGRAPHY types for spatial analytics, and row lineage for stable row identity across compaction. Gnok now supports all of them.</p>
<p>This post explains what each feature does, when to use it, and how to get started.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="format-versions-at-a-glance">Format Versions at a Glance<a href="https://docs.gnok.io/blog/2026/03/15/iceberg-v3#format-versions-at-a-glance" class="hash-link" aria-label="Direct link to Format Versions at a Glance" title="Direct link to Format Versions at a Glance" translate="no">​</a></h2>
<p>Iceberg defines three format versions, each building on the previous:</p>
<table><thead><tr><th>Version</th><th>Key Additions</th></tr></thead><tbody><tr><td><strong>v1</strong></td><td>Immutable data files, schema evolution, partition specs, sort orders, column statistics</td></tr><tr><td><strong>v2</strong></td><td>Position and equality deletes, sequence numbers, partition evolution, snapshot references</td></tr><tr><td><strong>v3</strong></td><td>Deletion vectors, VARIANT type, GEOMETRY/GEOGRAPHY, row lineage, default column values</td></tr></tbody></table>
<p>Gnok defaults to <strong>v2</strong> when creating tables. To use v3 features, specify the format version explicitly:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TABLE</span><span class="token plain"> events </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    id </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BIGINT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    payload VARIANT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    location </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">GEOMETRY</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WITH</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'format-version'</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'3'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<p>You can also upgrade an existing table:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ALTER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TABLE</span><span class="token plain"> events </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SET</span><span class="token plain"> TBLPROPERTIES </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'format-version'</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'3'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<div class="theme-admonition theme-admonition-caution admonition_xJq3 alert alert--warning"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>caution</div><div class="admonitionContent_BuS1"><p>Format version upgrades are one-way -- downgrading is not supported. Make sure all consumers of the table support v3 before upgrading.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="deletion-vectors">Deletion Vectors<a href="https://docs.gnok.io/blog/2026/03/15/iceberg-v3#deletion-vectors" class="hash-link" aria-label="Direct link to Deletion Vectors" title="Direct link to Deletion Vectors" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-problem-with-position-deletes">The Problem with Position Deletes<a href="https://docs.gnok.io/blog/2026/03/15/iceberg-v3#the-problem-with-position-deletes" class="hash-link" aria-label="Direct link to The Problem with Position Deletes" title="Direct link to The Problem with Position Deletes" translate="no">​</a></h3>
<p>In v2, every <code>UPDATE</code> or <code>DELETE</code> writes a <strong>position delete file</strong> -- a Parquet file containing <code>(file_path, pos)</code> pairs that mark which rows are deleted. This works, but delete files accumulate over time. A table with frequent updates can end up with hundreds of delete files, each of which must be read and merged during every scan.</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">Data file (1M rows)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  ├── Position delete file #1 (50 rows)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  ├── Position delete file #2 (30 rows)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  ├── Position delete file #3 (120 rows)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  └── ... (N more delete files)</span><br></span></code></pre></div></div>
<p>Each scan must load all N delete files, build an index, and filter every row against it. At scale this becomes the dominant cost of reads.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-deletion-vectors-work">How Deletion Vectors Work<a href="https://docs.gnok.io/blog/2026/03/15/iceberg-v3#how-deletion-vectors-work" class="hash-link" aria-label="Direct link to How Deletion Vectors Work" title="Direct link to How Deletion Vectors Work" translate="no">​</a></h3>
<p>Deletion vectors replace individual delete files with a single, compact <strong>Roaring bitmap</strong> stored in a Puffin file. Instead of N separate Parquet files with <code>(file_path, pos)</code> pairs, each data file has at most one deletion vector containing the set of deleted row positions.</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">Data file (1M rows)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  └── Deletion vector (Roaring bitmap, ~2KB) → {pos: 12, 47, 193, ...}</span><br></span></code></pre></div></div>
<p>The binary format is compact:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">Combined Length (4 bytes)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Magic: 0xD1D33964 (4 bytes)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">64-bit Roaring Bitmap (variable)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">CRC-32C checksum (4 bytes)</span><br></span></code></pre></div></div>
<p>Roaring bitmaps are extremely space-efficient for sparse sets (a few thousand deletes in a million-row file) and support O(1) membership tests. The bitmap is stored in a Puffin blob with LZ4 or Zstd compression.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="current-support">Current Support<a href="https://docs.gnok.io/blog/2026/03/15/iceberg-v3#current-support" class="hash-link" aria-label="Direct link to Current Support" title="Direct link to Current Support" translate="no">​</a></h3>
<p>Gnok fully supports <strong>reading</strong> deletion vectors. During scan planning, DVs are matched to data files by <code>referenced_data_file</code> path, preloaded, and merged with any existing position deletes. The write path currently produces v2-style position delete files; DV write support is planned.</p>
<table><thead><tr><th>Operation</th><th>Status</th></tr></thead><tbody><tr><td>Read DVs during scan</td><td>Supported</td></tr><tr><td>Merge DVs with position deletes</td><td>Supported</td></tr><tr><td>Write DVs on UPDATE/DELETE</td><td>Planned</td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-this-means-for-you">What This Means for You<a href="https://docs.gnok.io/blog/2026/03/15/iceberg-v3#what-this-means-for-you" class="hash-link" aria-label="Direct link to What This Means for You" title="Direct link to What This Means for You" translate="no">​</a></h3>
<p>If you produce data with a writer that creates deletion vectors (e.g., Spark 4.0), Gnok can read those tables efficiently today. For Gnok-originated writes, position deletes are used until DV write support ships. Run <code>OPTIMIZE PURGE DELETES</code> periodically to consolidate accumulated delete files.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="variant-type">VARIANT Type<a href="https://docs.gnok.io/blog/2026/03/15/iceberg-v3#variant-type" class="hash-link" aria-label="Direct link to VARIANT Type" title="Direct link to VARIANT Type" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="semi-structured-data-natively">Semi-Structured Data, Natively<a href="https://docs.gnok.io/blog/2026/03/15/iceberg-v3#semi-structured-data-natively" class="hash-link" aria-label="Direct link to Semi-Structured Data, Natively" title="Direct link to Semi-Structured Data, Natively" translate="no">​</a></h3>
<p>JSON columns stored as <code>VARCHAR</code> work for simple cases, but they have drawbacks: no type safety, no columnar pruning, and every access requires parsing the entire string. The VARIANT type solves this by storing semi-structured data in Iceberg's native binary variant format with optional <strong>shredding</strong> -- extracting frequently accessed fields into separate columns for efficient columnar reads.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="creating-and-querying-variant-data">Creating and Querying VARIANT Data<a href="https://docs.gnok.io/blog/2026/03/15/iceberg-v3#creating-and-querying-variant-data" class="hash-link" aria-label="Direct link to Creating and Querying VARIANT Data" title="Direct link to Creating and Querying VARIANT Data" translate="no">​</a></h3>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">-- Create a table with a VARIANT column</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TABLE</span><span class="token plain"> events </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    event_id </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BIGINT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    event_type </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">VARCHAR</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    payload VARIANT</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WITH</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'format-version'</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'3'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">-- Insert JSON data as VARIANT</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INSERT</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INTO</span><span class="token plain"> events </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">VALUES</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'click'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> PARSE_JSON</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'{"page": "/home", "duration_ms": 1200, "user": "alice"}'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">2</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'purchase'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> PARSE_JSON</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'{"item": "widget", "price": 29.99, "quantity": 3}'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">3</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'click'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> PARSE_JSON</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'{"page": "/products", "duration_ms": 800}'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="accessing-nested-values">Accessing Nested Values<a href="https://docs.gnok.io/blog/2026/03/15/iceberg-v3#accessing-nested-values" class="hash-link" aria-label="Direct link to Accessing Nested Values" title="Direct link to Accessing Nested Values" translate="no">​</a></h3>
<p>Use <code>VARIANT_GET</code> to extract typed values at a dot-path:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    event_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    event_type</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    VARIANT_GET</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">payload</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'page'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'VARCHAR'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> page</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    VARIANT_GET</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">payload</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'duration_ms'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'INT'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> duration_ms</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    VARIANT_GET</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">payload</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'price'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'DOUBLE'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> price</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> events</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<p>Result:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">event_id | event_type | page      | duration_ms | price</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">---------+------------+-----------+-------------+------</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">       1 | click      | /home     |        1200 | NULL</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">       2 | purchase   | NULL      |        NULL | 29.99</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">       3 | click      | /products |         800 | NULL</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="function-reference">Function Reference<a href="https://docs.gnok.io/blog/2026/03/15/iceberg-v3#function-reference" class="hash-link" aria-label="Direct link to Function Reference" title="Direct link to Function Reference" translate="no">​</a></h3>
<table><thead><tr><th>Function</th><th>Description</th></tr></thead><tbody><tr><td><code>PARSE_JSON(string)</code></td><td>Parse JSON into VARIANT</td></tr><tr><td><code>TO_VARIANT(expr)</code></td><td>Convert a scalar to VARIANT</td></tr><tr><td><code>VARIANT_GET(v, path, type)</code></td><td>Extract a typed value at a dot-path</td></tr><tr><td><code>VARIANT_EXTRACT_STRING(v, path)</code></td><td>Extract any value as a string</td></tr><tr><td><code>VARIANT_TYPE(v)</code></td><td>Return the runtime type name</td></tr><tr><td><code>VARIANT_AS_BOOL(v)</code></td><td>Cast to BOOLEAN</td></tr><tr><td><code>VARIANT_AS_INT(v)</code></td><td>Cast to BIGINT</td></tr><tr><td><code>VARIANT_AS_DOUBLE(v)</code></td><td>Cast to DOUBLE</td></tr><tr><td><code>VARIANT_AS_STRING(v)</code></td><td>Cast to VARCHAR</td></tr><tr><td><code>IS_VARIANT_NULL(v)</code></td><td>True if the VARIANT value is null</td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="when-to-use-variant-vs-json">When to Use VARIANT vs JSON<a href="https://docs.gnok.io/blog/2026/03/15/iceberg-v3#when-to-use-variant-vs-json" class="hash-link" aria-label="Direct link to When to Use VARIANT vs JSON" title="Direct link to When to Use VARIANT vs JSON" translate="no">​</a></h3>
<table><thead><tr><th>Scenario</th><th>Use</th></tr></thead><tbody><tr><td>Schema is unknown or evolving rapidly</td><td>VARIANT</td></tr><tr><td>You need columnar pruning on nested fields</td><td>VARIANT (with shredding)</td></tr><tr><td>Existing tables with JSON string columns</td><td>Keep as-is; use <code>json_extract()</code></td></tr><tr><td>Small payloads, infrequent access</td><td>Either works</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="geometry-and-geography-types">GEOMETRY and GEOGRAPHY Types<a href="https://docs.gnok.io/blog/2026/03/15/iceberg-v3#geometry-and-geography-types" class="hash-link" aria-label="Direct link to GEOMETRY and GEOGRAPHY Types" title="Direct link to GEOMETRY and GEOGRAPHY Types" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="spatial-analytics-in-sql">Spatial Analytics in SQL<a href="https://docs.gnok.io/blog/2026/03/15/iceberg-v3#spatial-analytics-in-sql" class="hash-link" aria-label="Direct link to Spatial Analytics in SQL" title="Direct link to Spatial Analytics in SQL" translate="no">​</a></h3>
<p>v3 adds native spatial types following PostGIS naming conventions. GEOMETRY operates in Cartesian coordinates (planar); GEOGRAPHY uses geodesic calculations (great-circle distances on the WGS84 ellipsoid).</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="example-store-locator">Example: Store Locator<a href="https://docs.gnok.io/blog/2026/03/15/iceberg-v3#example-store-locator" class="hash-link" aria-label="Direct link to Example: Store Locator" title="Direct link to Example: Store Locator" translate="no">​</a></h3>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">-- Create a table with spatial columns</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TABLE</span><span class="token plain"> stores </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    store_id </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BIGINT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    name </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">VARCHAR</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    location </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">GEOMETRY</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WITH</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'format-version'</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'3'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">-- Insert store locations</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INSERT</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INTO</span><span class="token plain"> stores </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">VALUES</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'Downtown'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> ST_POINT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token operator">-</span><span class="token number">73.9857</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">40.7484</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">2</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'Midtown'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> ST_POINT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token operator">-</span><span class="token number">73.9787</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">40.7614</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">3</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'Brooklyn'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> ST_POINT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token operator">-</span><span class="token number">73.9442</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">40.6782</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">-- Find stores within 2km of a location</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    ST_DISTANCE</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">location</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> ST_POINT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token operator">-</span><span class="token number">73.98</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">40.75</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> distance_m</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> stores</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHERE</span><span class="token plain"> ST_DWITHIN</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">location</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> ST_POINT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token operator">-</span><span class="token number">73.98</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">40.75</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">2000</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ORDER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> distance_m</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="spatial-pruning">Spatial Pruning<a href="https://docs.gnok.io/blog/2026/03/15/iceberg-v3#spatial-pruning" class="hash-link" aria-label="Direct link to Spatial Pruning" title="Direct link to Spatial Pruning" translate="no">​</a></h3>
<p>Gnok applies spatial pruning at the scan level using bounding box statistics stored in file metadata. Queries with <code>ST_WITHIN</code>, <code>ST_CONTAINS</code>, <code>ST_INTERSECTS</code>, or <code>ST_DWITHIN</code> predicates can skip entire data files whose bounding boxes don't overlap the query region.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="crs-support">CRS Support<a href="https://docs.gnok.io/blog/2026/03/15/iceberg-v3#crs-support" class="hash-link" aria-label="Direct link to CRS Support" title="Direct link to CRS Support" translate="no">​</a></h3>
<table><thead><tr><th>CRS</th><th>EPSG Code</th><th>Use Case</th></tr></thead><tbody><tr><td>WGS84</td><td>4326</td><td>GPS coordinates (lat/lon)</td></tr><tr><td>Web Mercator</td><td>3857</td><td>Web maps (Leaflet, Mapbox)</td></tr><tr><td>UTM zones</td><td>326xx/327xx</td><td>Regional metric projections</td></tr></tbody></table>
<p>Transform between projections with <code>ST_TRANSFORM</code>:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"> ST_TRANSFORM</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">location</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">3857</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> web_mercator_point</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> stores</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="full-function-list">Full Function List<a href="https://docs.gnok.io/blog/2026/03/15/iceberg-v3#full-function-list" class="hash-link" aria-label="Direct link to Full Function List" title="Direct link to Full Function List" translate="no">​</a></h3>
<p><code>ST_POINT</code>, <code>ST_DISTANCE</code>, <code>ST_WITHIN</code>, <code>ST_CONTAINS</code>, <code>ST_INTERSECTS</code>, <code>ST_AREA</code>, <code>ST_LENGTH</code>, <code>ST_ASTEXT</code>, <code>ST_GEOMFROMTEXT</code>, <code>ST_GEOGFROMTEXT</code>, <code>ST_X</code>, <code>ST_Y</code>, <code>ST_SRID</code>, <code>ST_TRANSFORM</code>, <code>ST_BUFFER</code>, <code>ST_DWITHIN</code>, <code>ST_ENVELOPE</code></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="row-lineage">Row Lineage<a href="https://docs.gnok.io/blog/2026/03/15/iceberg-v3#row-lineage" class="hash-link" aria-label="Direct link to Row Lineage" title="Direct link to Row Lineage" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="stable-row-identity">Stable Row Identity<a href="https://docs.gnok.io/blog/2026/03/15/iceberg-v3#stable-row-identity" class="hash-link" aria-label="Direct link to Stable Row Identity" title="Direct link to Stable Row Identity" translate="no">​</a></h3>
<p>Row lineage provides globally unique, stable row identifiers that survive compaction, schema evolution, and table maintenance. Each row gets a <code>_row_id</code> that doesn't change when the underlying data files are rewritten.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-it-works">How It Works<a href="https://docs.gnok.io/blog/2026/03/15/iceberg-v3#how-it-works" class="hash-link" aria-label="Direct link to How It Works" title="Direct link to How It Works" translate="no">​</a></h3>
<ol>
<li class="">Each table tracks a monotonically increasing <code>next_row_id</code> counter in its metadata</li>
<li class="">When data files are committed, each file is assigned a <code>first_row_id</code> from this counter</li>
<li class="">A row's <code>_row_id</code> is computed as <code>first_row_id + position_within_file</code></li>
<li class="">The counter advances by the total number of rows written</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="querying-row-lineage">Querying Row Lineage<a href="https://docs.gnok.io/blog/2026/03/15/iceberg-v3#querying-row-lineage" class="hash-link" aria-label="Direct link to Querying Row Lineage" title="Direct link to Querying Row Lineage" translate="no">​</a></h3>
<p>Two metadata columns are available on v3 tables:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    _row_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    _last_updated_sequence_number</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    order_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    amount</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> orders</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">LIMIT</span><span class="token plain"> </span><span class="token number">5</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">_row_id | _last_updated_sequence_number | order_id | amount</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">--------+-------------------------------+----------+-------</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      0 |                             1 |     1001 | 99.99</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      1 |                             1 |     1002 | 49.50</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      2 |                             1 |     1003 | 199.00</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      3 |                             2 |     1004 | 75.25</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      4 |                             2 |     1005 | 320.00</span><br></span></code></pre></div></div>
<table><thead><tr><th>Column</th><th>Description</th></tr></thead><tbody><tr><td><code>_row_id</code></td><td>Globally unique, stable row identifier</td></tr><tr><td><code>_last_updated_sequence_number</code></td><td>The commit sequence number that last modified this row</td></tr></tbody></table>
<p>These are metadata columns -- they're excluded from <code>SELECT *</code> but can be requested explicitly.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="use-cases">Use Cases<a href="https://docs.gnok.io/blog/2026/03/15/iceberg-v3#use-cases" class="hash-link" aria-label="Direct link to Use Cases" title="Direct link to Use Cases" translate="no">​</a></h3>
<ul>
<li class=""><strong>CDC pipelines</strong>: Track which rows changed between commits using <code>_last_updated_sequence_number</code></li>
<li class=""><strong>Deduplication</strong>: Use <code>_row_id</code> as a stable key for exactly-once processing</li>
<li class=""><strong>Audit trails</strong>: Record <code>_row_id</code> in downstream systems for lineage tracking</li>
<li class=""><strong>Incremental ETL</strong>: Process only rows where <code>_last_updated_sequence_number &gt; last_processed_sequence</code></li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="default-column-values">Default Column Values<a href="https://docs.gnok.io/blog/2026/03/15/iceberg-v3#default-column-values" class="hash-link" aria-label="Direct link to Default Column Values" title="Direct link to Default Column Values" translate="no">​</a></h2>
<p>v3 adds support for default values that apply both to new rows (when the column is omitted on INSERT) and to existing rows (when reading data written before the column was added):</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">-- Add a column with defaults</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ALTER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TABLE</span><span class="token plain"> orders </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ADD</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">COLUMN</span><span class="token plain"> priority </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INT</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DEFAULT</span><span class="token plain"> </span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ALTER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TABLE</span><span class="token plain"> orders </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ADD</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">COLUMN</span><span class="token plain"> created_at </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TIMESTAMP</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DEFAULT</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CURRENT_TIMESTAMP</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<table><thead><tr><th>Default Type</th><th>When Applied</th></tr></thead><tbody><tr><td><code>write_default</code></td><td>New rows where the column value is omitted</td></tr><tr><td><code>initial_default</code></td><td>Existing rows written before the column was added</td></tr></tbody></table>
<p>This makes schema evolution seamless -- old data files don't need to be rewritten when a new column is added.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="migration-guide">Migration Guide<a href="https://docs.gnok.io/blog/2026/03/15/iceberg-v3#migration-guide" class="hash-link" aria-label="Direct link to Migration Guide" title="Direct link to Migration Guide" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="upgrading-to-v3">Upgrading to v3<a href="https://docs.gnok.io/blog/2026/03/15/iceberg-v3#upgrading-to-v3" class="hash-link" aria-label="Direct link to Upgrading to v3" title="Direct link to Upgrading to v3" translate="no">​</a></h3>
<ol>
<li class=""><strong>Assess compatibility</strong>: Ensure all consumers (Spark, Trino, Flink, etc.) support Iceberg v3</li>
<li class=""><strong>Upgrade the table</strong>:<!-- -->
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ALTER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TABLE</span><span class="token plain"> my_table </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SET</span><span class="token plain"> TBLPROPERTIES </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'format-version'</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'3'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
</li>
<li class=""><strong>Start using v3 features</strong>: Add VARIANT/GEOMETRY columns, query <code>_row_id</code>, etc.</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="creating-new-v3-tables">Creating New v3 Tables<a href="https://docs.gnok.io/blog/2026/03/15/iceberg-v3#creating-new-v3-tables" class="hash-link" aria-label="Direct link to Creating New v3 Tables" title="Direct link to Creating New v3 Tables" translate="no">​</a></h3>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TABLE</span><span class="token plain"> sensor_data </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    sensor_id </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BIGINT</span><span class="token plain"> </span><span class="token operator">NOT</span><span class="token plain"> </span><span class="token boolean">NULL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    reading </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DOUBLE</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    metadata VARIANT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    location </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">GEOMETRY</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    ts </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TIMESTAMP</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">PARTITIONED </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">day</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">ts</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WITH</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'format-version'</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'3'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-doesnt-change">What Doesn't Change<a href="https://docs.gnok.io/blog/2026/03/15/iceberg-v3#what-doesnt-change" class="hash-link" aria-label="Direct link to What Doesn't Change" title="Direct link to What Doesn't Change" translate="no">​</a></h3>
<ul>
<li class="">All v1 and v2 features continue to work unchanged</li>
<li class="">Existing queries don't need modification</li>
<li class="">Position deletes are still written (until DV write support ships)</li>
<li class="">Schema evolution, partition pruning, and time travel work identically</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="further-reading">Further Reading<a href="https://docs.gnok.io/blog/2026/03/15/iceberg-v3#further-reading" class="hash-link" aria-label="Direct link to Further Reading" title="Direct link to Further Reading" translate="no">​</a></h2>
<ul>
<li class=""><a class="" href="https://docs.gnok.io/docs/getting-started/iceberg-compatibility">Iceberg Compatibility Reference</a> -- full v1/v2/v3 feature matrix</li>
<li class=""><a class="" href="https://docs.gnok.io/docs/sql-reference/data-types">Data Types Reference</a> -- VARIANT, GEOMETRY, GEOGRAPHY type details</li>
<li class=""><a class="" href="https://docs.gnok.io/docs/sql-reference/functions#spatial-functions-v3">Spatial Functions Reference</a> -- all ST_* functions</li>
<li class=""><a class="" href="https://docs.gnok.io/docs/sql-reference/functions#variant-functions-v3">VARIANT Functions Reference</a> -- all VARIANT_* functions</li>
</ul>]]></content>
        <author>
            <name>Gnok Team</name>
        </author>
        <category label="Engineering" term="Engineering"/>
        <category label="Iceberg" term="Iceberg"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Building Custom WASM UDFs for Gnok: A Step-by-Step Guide]]></title>
        <id>https://docs.gnok.io/blog/2026/02/23/wasm-udf-tutorial</id>
        <link href="https://docs.gnok.io/blog/2026/02/23/wasm-udf-tutorial"/>
        <updated>2026-02-23T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Gnok's WebAssembly UDF system lets you extend SQL with custom functions written in Rust, Go, C, or any language that compiles to WASM. Your functions run inside the query engine with near-native performance, sandboxed execution, and automatic distribution across cluster workers.]]></summary>
        <content type="html"><![CDATA[<p>Gnok's WebAssembly UDF system lets you extend SQL with custom functions written in Rust, Go, C, or any language that compiles to WASM. Your functions run inside the query engine with near-native performance, sandboxed execution, and automatic distribution across cluster workers.</p>
<p>This tutorial walks through building a WASM UDF from scratch, registering it, and using it in queries.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-youll-build">What You'll Build<a href="https://docs.gnok.io/blog/2026/02/23/wasm-udf-tutorial#what-youll-build" class="hash-link" aria-label="Direct link to What You'll Build" title="Direct link to What You'll Build" translate="no">​</a></h2>
<p>We'll create three UDFs of increasing complexity:</p>
<ol>
<li class=""><strong><code>mask_pii</code></strong> -- redact personally identifiable information from text</li>
<li class=""><strong><code>haversine_km</code></strong> -- calculate the distance between two GPS coordinates</li>
<li class=""><strong><code>json_extract_field</code></strong> -- extract a field from a JSON string</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="prerequisites">Prerequisites<a href="https://docs.gnok.io/blog/2026/02/23/wasm-udf-tutorial#prerequisites" class="hash-link" aria-label="Direct link to Prerequisites" title="Direct link to Prerequisites" translate="no">​</a></h2>
<ul>
<li class="">A Gnok account with permission to create functions (you can run the SQL in a Gnok Studio worksheet)</li>
<li class=""><a href="https://rustup.rs/" target="_blank" rel="noopener noreferrer" class="">Rust toolchain</a> with the WASM target:</li>
</ul>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">rustup target </span><span class="token function" style="color:rgb(80, 250, 123)">add</span><span class="token plain"> wasm32-wasip2</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">cargo</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">install</span><span class="token plain"> cargo-component</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-wasm-udfs-work">How WASM UDFs Work<a href="https://docs.gnok.io/blog/2026/02/23/wasm-udf-tutorial#how-wasm-udfs-work" class="hash-link" aria-label="Direct link to How WASM UDFs Work" title="Direct link to How WASM UDFs Work" translate="no">​</a></h2>
<p>When you call a WASM UDF in SQL, Gnok:</p>
<ol>
<li class="">Receives an Arrow RecordBatch (one column per function argument)</li>
<li class="">Serializes it as Arrow IPC bytes</li>
<li class="">Calls your WASM function with the IPC bytes</li>
<li class="">Deserializes the output IPC bytes back into an Arrow column</li>
<li class="">Returns the result as part of the query</li>
</ol>
<p>Your function signature is always the same: receive <code>Vec&lt;u8&gt;</code> (Arrow IPC input), return <code>Result&lt;Vec&lt;u8&gt;, String&gt;</code> (Arrow IPC output).</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="project-setup">Project Setup<a href="https://docs.gnok.io/blog/2026/02/23/wasm-udf-tutorial#project-setup" class="hash-link" aria-label="Direct link to Project Setup" title="Direct link to Project Setup" translate="no">​</a></h2>
<p>Create a new Rust library project:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">cargo</span><span class="token plain"> init </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--lib</span><span class="token plain"> gnok-udf-examples</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">cd</span><span class="token plain"> gnok-udf-examples</span><br></span></code></pre></div></div>
<p>Add the guest library and Arrow dependencies to <code>Cargo.toml</code>:</p>
<div class="language-toml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-toml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token table class-name">package</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key property">name</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"gnok-udf-examples"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key property">version</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"0.1.0"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key property">edition</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"2021"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token table class-name">lib</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key property">crate-type</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"cdylib"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token table class-name">dependencies</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key property">gnok-udf-guest</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token key property">version</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"0.1"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key property">arrow</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token key property">version</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"54"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token key property">default-features</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token boolean">false</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token key property">features</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"ipc"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="udf-1-pii-masking">UDF 1: PII Masking<a href="https://docs.gnok.io/blog/2026/02/23/wasm-udf-tutorial#udf-1-pii-masking" class="hash-link" aria-label="Direct link to UDF 1: PII Masking" title="Direct link to UDF 1: PII Masking" translate="no">​</a></h2>
<p>Let's start with a simple string transformation that redacts email addresses and phone numbers from text.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="write-the-function">Write the Function<a href="https://docs.gnok.io/blog/2026/02/23/wasm-udf-tutorial#write-the-function" class="hash-link" aria-label="Direct link to Write the Function" title="Direct link to Write the Function" translate="no">​</a></h3>
<p>Create <code>src/lib.rs</code>:</p>
<div class="language-rust codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-rust codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">use</span><span class="token plain"> </span><span class="token namespace">gnok_udf_guest</span><span class="token namespace punctuation" style="color:rgb(248, 248, 242)">::</span><span class="token namespace">ipc</span><span class="token namespace punctuation" style="color:rgb(248, 248, 242)">::</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">decode_input_batch</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> encode_output_column</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">use</span><span class="token plain"> </span><span class="token namespace">arrow</span><span class="token namespace punctuation" style="color:rgb(248, 248, 242)">::</span><span class="token namespace">array</span><span class="token namespace punctuation" style="color:rgb(248, 248, 242)">::</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token class-name">ArrayRef</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token class-name">StringArray</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">/// Masks email addresses and phone numbers in text.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">/// Input: a single TEXT column</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">/// Output: TEXT column with PII replaced by [REDACTED]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">pub</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">fn</span><span class="token plain"> </span><span class="token function-definition function" style="color:rgb(80, 250, 123)">mask_pii</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">input_ipc</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token class-name">Vec</span><span class="token operator">&lt;</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">u8</span><span class="token operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-&gt;</span><span class="token plain"> </span><span class="token class-name">Result</span><span class="token operator">&lt;</span><span class="token class-name">Vec</span><span class="token operator">&lt;</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">u8</span><span class="token operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token class-name">String</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">let</span><span class="token plain"> args </span><span class="token operator">=</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">decode_input_batch</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token operator">&amp;</span><span class="token plain">input_ipc</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token operator">?</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">let</span><span class="token plain"> texts </span><span class="token operator">=</span><span class="token plain"> args</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">as_any</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">downcast_ref</span><span class="token punctuation" style="color:rgb(248, 248, 242)">::</span><span class="token operator">&lt;</span><span class="token class-name">StringArray</span><span class="token operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">ok_or</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"expected string array"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token operator">?</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">let</span><span class="token plain"> result</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token class-name">StringArray</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> texts</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">iter</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">map</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token closure-params closure-punctuation punctuation" style="color:rgb(248, 248, 242)">|</span><span class="token closure-params">opt_val</span><span class="token closure-params closure-punctuation punctuation" style="color:rgb(248, 248, 242)">|</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            opt_val</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">map</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token closure-params closure-punctuation punctuation" style="color:rgb(248, 248, 242)">|</span><span class="token closure-params">text</span><span class="token closure-params closure-punctuation punctuation" style="color:rgb(248, 248, 242)">|</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">let</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">mut</span><span class="token plain"> masked </span><span class="token operator">=</span><span class="token plain"> </span><span class="token class-name">String</span><span class="token punctuation" style="color:rgb(248, 248, 242)">::</span><span class="token function" style="color:rgb(80, 250, 123)">with_capacity</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">len</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">let</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">mut</span><span class="token plain"> i </span><span class="token operator">=</span><span class="token plain"> </span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">let</span><span class="token plain"> chars</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token class-name">Vec</span><span class="token operator">&lt;</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">char</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">chars</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">collect</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">while</span><span class="token plain"> i </span><span class="token operator">&lt;</span><span class="token plain"> chars</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">len</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                    </span><span class="token comment" style="color:rgb(98, 114, 164)">// Simple email detection: look for @ sign</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> chars</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">i</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">==</span><span class="token plain"> </span><span class="token char" style="color:rgb(255, 121, 198)">'@'</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                        </span><span class="token comment" style="color:rgb(98, 114, 164)">// Walk back to find start of email</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">let</span><span class="token plain"> start </span><span class="token operator">=</span><span class="token plain"> masked</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">rfind</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token closure-params closure-punctuation punctuation" style="color:rgb(248, 248, 242)">|</span><span class="token closure-params">c</span><span class="token closure-params punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token closure-params"> </span><span class="token closure-params keyword" style="color:rgb(189, 147, 249);font-style:italic">char</span><span class="token closure-params closure-punctuation punctuation" style="color:rgb(248, 248, 242)">|</span><span class="token plain"> c</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">is_whitespace</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                            </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">map</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token closure-params closure-punctuation punctuation" style="color:rgb(248, 248, 242)">|</span><span class="token closure-params">pos</span><span class="token closure-params closure-punctuation punctuation" style="color:rgb(248, 248, 242)">|</span><span class="token plain"> pos </span><span class="token operator">+</span><span class="token plain"> </span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                            </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">unwrap_or</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                        masked</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">truncate</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">start</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                        masked</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">push_str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"[REDACTED]"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                        </span><span class="token comment" style="color:rgb(98, 114, 164)">// Skip to end of email</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">while</span><span class="token plain"> i </span><span class="token operator">&lt;</span><span class="token plain"> chars</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">len</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">&amp;&amp;</span><span class="token plain"> </span><span class="token operator">!</span><span class="token plain">chars</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">i</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">is_whitespace</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                            i </span><span class="token operator">+=</span><span class="token plain"> </span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">continue</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                    </span><span class="token comment" style="color:rgb(98, 114, 164)">// Simple phone detection: sequences of 10+ digits</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> chars</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">i</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">is_ascii_digit</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">let</span><span class="token plain"> digit_start </span><span class="token operator">=</span><span class="token plain"> i</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">let</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">mut</span><span class="token plain"> digit_count </span><span class="token operator">=</span><span class="token plain"> </span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">let</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">mut</span><span class="token plain"> j </span><span class="token operator">=</span><span class="token plain"> i</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">while</span><span class="token plain"> j </span><span class="token operator">&lt;</span><span class="token plain"> chars</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">len</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                            </span><span class="token operator">&amp;&amp;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">chars</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">j</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">is_ascii_digit</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                                </span><span class="token operator">||</span><span class="token plain"> chars</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">j</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">==</span><span class="token plain"> </span><span class="token char" style="color:rgb(255, 121, 198)">'-'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                                </span><span class="token operator">||</span><span class="token plain"> chars</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">j</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">==</span><span class="token plain"> </span><span class="token char" style="color:rgb(255, 121, 198)">' '</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                                </span><span class="token operator">||</span><span class="token plain"> chars</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">j</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">==</span><span class="token plain"> </span><span class="token char" style="color:rgb(255, 121, 198)">'('</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                                </span><span class="token operator">||</span><span class="token plain"> chars</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">j</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">==</span><span class="token plain"> </span><span class="token char" style="color:rgb(255, 121, 198)">')'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                            </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> chars</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">j</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">is_ascii_digit</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                                digit_count </span><span class="token operator">+=</span><span class="token plain"> </span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                            </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                            j </span><span class="token operator">+=</span><span class="token plain"> </span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> digit_count </span><span class="token operator">&gt;=</span><span class="token plain"> </span><span class="token number">10</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                            masked</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">push_str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"[REDACTED]"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                            i </span><span class="token operator">=</span><span class="token plain"> j</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                            </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">continue</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                    masked</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">push</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">chars</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">i</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                    i </span><span class="token operator">+=</span><span class="token plain"> </span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                masked</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">collect</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token function" style="color:rgb(80, 250, 123)">encode_output_column</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token operator">&amp;</span><span class="token plain">result</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="build">Build<a href="https://docs.gnok.io/blog/2026/02/23/wasm-udf-tutorial#build" class="hash-link" aria-label="Direct link to Build" title="Direct link to Build" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">cargo</span><span class="token plain"> component build </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--target</span><span class="token plain"> wasm32-wasip2 </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--release</span><br></span></code></pre></div></div>
<p>This produces <code>target/wasm32-wasip2/release/gnok_udf_examples.wasm</code>.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="register">Register<a href="https://docs.gnok.io/blog/2026/02/23/wasm-udf-tutorial#register" class="hash-link" aria-label="Direct link to Register" title="Direct link to Register" translate="no">​</a></h3>
<p>Base64-encode the WASM binary and register it:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># Encode the WASM module</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">WASM_B64</span><span class="token operator">=</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$(</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">base64 </span><span class="token variable parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-i</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"> target/wasm32-wasip2/release/gnok_udf_examples.wasm</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">)</span><br></span></code></pre></div></div>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FUNCTION</span><span class="token plain"> mask_pii</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">RETURNS</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">LANGUAGE</span><span class="token plain"> wasm</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">VOLATILITY immutable</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'&lt;paste-base64-here&gt;'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="use-it">Use It<a href="https://docs.gnok.io/blog/2026/02/23/wasm-udf-tutorial#use-it" class="hash-link" aria-label="Direct link to Use It" title="Direct link to Use It" translate="no">​</a></h3>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">-- Mask PII in customer support messages</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    ticket_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    mask_pii</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">message</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> safe_message</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> support_tickets</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">LIMIT</span><span class="token plain"> </span><span class="token number">5</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<p>Result:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">ticket_id | safe_message</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">----------+---------------------------------------------</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">1001      | Please contact me at [REDACTED] about my order</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">1002      | My phone number is [REDACTED], call me back</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">1003      | Hi, I'm John from accounting</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="udf-2-haversine-distance">UDF 2: Haversine Distance<a href="https://docs.gnok.io/blog/2026/02/23/wasm-udf-tutorial#udf-2-haversine-distance" class="hash-link" aria-label="Direct link to UDF 2: Haversine Distance" title="Direct link to UDF 2: Haversine Distance" translate="no">​</a></h2>
<p>A numeric UDF that calculates the great-circle distance between two GPS coordinates.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="write-the-function-1">Write the Function<a href="https://docs.gnok.io/blog/2026/02/23/wasm-udf-tutorial#write-the-function-1" class="hash-link" aria-label="Direct link to Write the Function" title="Direct link to Write the Function" translate="no">​</a></h3>
<div class="language-rust codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-rust codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">use</span><span class="token plain"> </span><span class="token namespace">gnok_udf_guest</span><span class="token namespace punctuation" style="color:rgb(248, 248, 242)">::</span><span class="token namespace">ipc</span><span class="token namespace punctuation" style="color:rgb(248, 248, 242)">::</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">decode_input_batch</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> encode_output_column</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">use</span><span class="token plain"> </span><span class="token namespace">arrow</span><span class="token namespace punctuation" style="color:rgb(248, 248, 242)">::</span><span class="token namespace">array</span><span class="token namespace punctuation" style="color:rgb(248, 248, 242)">::</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token class-name">Float64Array</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">/// Computes the Haversine distance in kilometers between two</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">/// lat/lon coordinate pairs.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">///</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">/// Arguments: lat1 FLOAT64, lon1 FLOAT64, lat2 FLOAT64, lon2 FLOAT64</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">/// Returns:   FLOAT64 (distance in km)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">pub</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">fn</span><span class="token plain"> </span><span class="token function-definition function" style="color:rgb(80, 250, 123)">haversine_km</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">input_ipc</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token class-name">Vec</span><span class="token operator">&lt;</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">u8</span><span class="token operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-&gt;</span><span class="token plain"> </span><span class="token class-name">Result</span><span class="token operator">&lt;</span><span class="token class-name">Vec</span><span class="token operator">&lt;</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">u8</span><span class="token operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token class-name">String</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">let</span><span class="token plain"> args </span><span class="token operator">=</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">decode_input_batch</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token operator">&amp;</span><span class="token plain">input_ipc</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token operator">?</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">let</span><span class="token plain"> lat1 </span><span class="token operator">=</span><span class="token plain"> args</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">as_any</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">downcast_ref</span><span class="token punctuation" style="color:rgb(248, 248, 242)">::</span><span class="token operator">&lt;</span><span class="token class-name">Float64Array</span><span class="token operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">ok_or</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"expected float64 for lat1"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token operator">?</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">let</span><span class="token plain"> lon1 </span><span class="token operator">=</span><span class="token plain"> args</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">as_any</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">downcast_ref</span><span class="token punctuation" style="color:rgb(248, 248, 242)">::</span><span class="token operator">&lt;</span><span class="token class-name">Float64Array</span><span class="token operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">ok_or</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"expected float64 for lon1"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token operator">?</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">let</span><span class="token plain"> lat2 </span><span class="token operator">=</span><span class="token plain"> args</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token number">2</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">as_any</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">downcast_ref</span><span class="token punctuation" style="color:rgb(248, 248, 242)">::</span><span class="token operator">&lt;</span><span class="token class-name">Float64Array</span><span class="token operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">ok_or</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"expected float64 for lat2"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token operator">?</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">let</span><span class="token plain"> lon2 </span><span class="token operator">=</span><span class="token plain"> args</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token number">3</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">as_any</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">downcast_ref</span><span class="token punctuation" style="color:rgb(248, 248, 242)">::</span><span class="token operator">&lt;</span><span class="token class-name">Float64Array</span><span class="token operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">ok_or</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"expected float64 for lon2"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token operator">?</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token constant" style="color:rgb(189, 147, 249)">EARTH_RADIUS_KM</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">f64</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token number">6371.0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">let</span><span class="token plain"> result</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token class-name">Float64Array</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">..</span><span class="token plain">lat1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">len</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">map</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token closure-params closure-punctuation punctuation" style="color:rgb(248, 248, 242)">|</span><span class="token closure-params">i</span><span class="token closure-params closure-punctuation punctuation" style="color:rgb(248, 248, 242)">|</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> lat1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">is_null</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">i</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">||</span><span class="token plain"> lon1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">is_null</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">i</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token operator">||</span><span class="token plain"> lat2</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">is_null</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">i</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">||</span><span class="token plain"> lon2</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">is_null</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">i</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token class-name">None</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">let</span><span class="token plain"> lat1_rad </span><span class="token operator">=</span><span class="token plain"> lat1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">value</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">i</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">to_radians</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">let</span><span class="token plain"> lat2_rad </span><span class="token operator">=</span><span class="token plain"> lat2</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">value</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">i</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">to_radians</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">let</span><span class="token plain"> dlat </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">lat2</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">value</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">i</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token plain"> lat1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">value</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">i</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">to_radians</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">let</span><span class="token plain"> dlon </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">lon2</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">value</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">i</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token plain"> lon1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">value</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">i</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">to_radians</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">let</span><span class="token plain"> a </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">dlat </span><span class="token operator">/</span><span class="token plain"> </span><span class="token number">2.0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">sin</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">powi</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">2</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token operator">+</span><span class="token plain"> lat1_rad</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">cos</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">*</span><span class="token plain"> lat2_rad</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">cos</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">*</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">dlon </span><span class="token operator">/</span><span class="token plain"> </span><span class="token number">2.0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">sin</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">powi</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">2</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">let</span><span class="token plain"> c </span><span class="token operator">=</span><span class="token plain"> </span><span class="token number">2.0</span><span class="token plain"> </span><span class="token operator">*</span><span class="token plain"> a</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">sqrt</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">asin</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token class-name">Some</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token constant" style="color:rgb(189, 147, 249)">EARTH_RADIUS_KM</span><span class="token plain"> </span><span class="token operator">*</span><span class="token plain"> c</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">collect</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token function" style="color:rgb(80, 250, 123)">encode_output_column</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token operator">&amp;</span><span class="token plain">result</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="register-and-use">Register and Use<a href="https://docs.gnok.io/blog/2026/02/23/wasm-udf-tutorial#register-and-use" class="hash-link" aria-label="Direct link to Register and Use" title="Direct link to Register and Use" translate="no">​</a></h3>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FUNCTION</span><span class="token plain"> haversine_km</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">FLOAT64</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> FLOAT64</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> FLOAT64</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> FLOAT64</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">RETURNS</span><span class="token plain"> FLOAT64</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">LANGUAGE</span><span class="token plain"> wasm</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">VOLATILITY immutable</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'&lt;base64-encoded-wasm&gt;'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">-- Find the 10 nearest warehouses to each order's delivery address</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    o</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">order_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    w</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">warehouse_name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    haversine_km</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">o</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">delivery_lat</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> o</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">delivery_lon</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> w</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">lat</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> w</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">lon</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> distance_km</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> orders o</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CROSS</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">JOIN</span><span class="token plain"> warehouses w</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ORDER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> o</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">order_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> distance_km</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">LIMIT</span><span class="token plain"> </span><span class="token number">10</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">-- Filter deliveries within a 50km radius</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    order_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    customer_name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    haversine_km</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">delivery_lat</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> delivery_lon</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">37.7749</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token operator">-</span><span class="token number">122.4194</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> distance_to_sf</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> orders</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHERE</span><span class="token plain"> haversine_km</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">delivery_lat</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> delivery_lon</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">37.7749</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token operator">-</span><span class="token number">122.4194</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">&lt;</span><span class="token plain"> </span><span class="token number">50.0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<p>Because we declared <code>VOLATILITY immutable</code>, the optimizer knows this function is deterministic. If you reference <code>haversine_km</code> multiple times with the same arguments, Gnok evaluates it once and reuses the result.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="udf-3-json-field-extraction">UDF 3: JSON Field Extraction<a href="https://docs.gnok.io/blog/2026/02/23/wasm-udf-tutorial#udf-3-json-field-extraction" class="hash-link" aria-label="Direct link to UDF 3: JSON Field Extraction" title="Direct link to UDF 3: JSON Field Extraction" translate="no">​</a></h2>
<p>A practical UDF for extracting fields from JSON strings stored in text columns.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="write-the-function-2">Write the Function<a href="https://docs.gnok.io/blog/2026/02/23/wasm-udf-tutorial#write-the-function-2" class="hash-link" aria-label="Direct link to Write the Function" title="Direct link to Write the Function" translate="no">​</a></h3>
<p>Add <code>serde_json</code> to your dependencies:</p>
<div class="language-toml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-toml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token table class-name">dependencies</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key property">serde_json</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"1"</span><br></span></code></pre></div></div>
<div class="language-rust codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-rust codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">use</span><span class="token plain"> </span><span class="token namespace">gnok_udf_guest</span><span class="token namespace punctuation" style="color:rgb(248, 248, 242)">::</span><span class="token namespace">ipc</span><span class="token namespace punctuation" style="color:rgb(248, 248, 242)">::</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">decode_input_batch</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> encode_output_column</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">use</span><span class="token plain"> </span><span class="token namespace">arrow</span><span class="token namespace punctuation" style="color:rgb(248, 248, 242)">::</span><span class="token namespace">array</span><span class="token namespace punctuation" style="color:rgb(248, 248, 242)">::</span><span class="token class-name">StringArray</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">/// Extracts a top-level field from a JSON string.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">///</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">/// Arguments: json_text TEXT, field_name TEXT</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">/// Returns:   TEXT (the field value as a string, or NULL if missing)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">pub</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">fn</span><span class="token plain"> </span><span class="token function-definition function" style="color:rgb(80, 250, 123)">json_extract_field</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">input_ipc</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token class-name">Vec</span><span class="token operator">&lt;</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">u8</span><span class="token operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-&gt;</span><span class="token plain"> </span><span class="token class-name">Result</span><span class="token operator">&lt;</span><span class="token class-name">Vec</span><span class="token operator">&lt;</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">u8</span><span class="token operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token class-name">String</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">let</span><span class="token plain"> args </span><span class="token operator">=</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">decode_input_batch</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token operator">&amp;</span><span class="token plain">input_ipc</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token operator">?</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">let</span><span class="token plain"> json_col </span><span class="token operator">=</span><span class="token plain"> args</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">as_any</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">downcast_ref</span><span class="token punctuation" style="color:rgb(248, 248, 242)">::</span><span class="token operator">&lt;</span><span class="token class-name">StringArray</span><span class="token operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">ok_or</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"expected string for json_text"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token operator">?</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">let</span><span class="token plain"> field_col </span><span class="token operator">=</span><span class="token plain"> args</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">as_any</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">downcast_ref</span><span class="token punctuation" style="color:rgb(248, 248, 242)">::</span><span class="token operator">&lt;</span><span class="token class-name">StringArray</span><span class="token operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">ok_or</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"expected string for field_name"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token operator">?</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">let</span><span class="token plain"> result</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token class-name">StringArray</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">..</span><span class="token plain">json_col</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">len</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">map</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token closure-params closure-punctuation punctuation" style="color:rgb(248, 248, 242)">|</span><span class="token closure-params">i</span><span class="token closure-params closure-punctuation punctuation" style="color:rgb(248, 248, 242)">|</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> json_col</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">is_null</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">i</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">||</span><span class="token plain"> field_col</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">is_null</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">i</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token class-name">None</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">let</span><span class="token plain"> json_str </span><span class="token operator">=</span><span class="token plain"> json_col</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">value</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">i</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">let</span><span class="token plain"> field </span><span class="token operator">=</span><span class="token plain"> field_col</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">value</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">i</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">let</span><span class="token plain"> parsed</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token namespace">serde_json</span><span class="token namespace punctuation" style="color:rgb(248, 248, 242)">::</span><span class="token class-name">Value</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">match</span><span class="token plain"> </span><span class="token namespace">serde_json</span><span class="token namespace punctuation" style="color:rgb(248, 248, 242)">::</span><span class="token function" style="color:rgb(80, 250, 123)">from_str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">json_str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token class-name">Ok</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">v</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">=&gt;</span><span class="token plain"> v</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token class-name">Err</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">_</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">=&gt;</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token class-name">None</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            parsed</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">get</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">field</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">map</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token closure-params closure-punctuation punctuation" style="color:rgb(248, 248, 242)">|</span><span class="token closure-params">v</span><span class="token closure-params closure-punctuation punctuation" style="color:rgb(248, 248, 242)">|</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">match</span><span class="token plain"> v </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token namespace">serde_json</span><span class="token namespace punctuation" style="color:rgb(248, 248, 242)">::</span><span class="token class-name">Value</span><span class="token punctuation" style="color:rgb(248, 248, 242)">::</span><span class="token class-name">String</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">s</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">=&gt;</span><span class="token plain"> s</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">clone</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                other </span><span class="token operator">=&gt;</span><span class="token plain"> other</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">to_string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">collect</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token function" style="color:rgb(80, 250, 123)">encode_output_column</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token operator">&amp;</span><span class="token plain">result</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="register-and-use-1">Register and Use<a href="https://docs.gnok.io/blog/2026/02/23/wasm-udf-tutorial#register-and-use-1" class="hash-link" aria-label="Direct link to Register and Use" title="Direct link to Register and Use" translate="no">​</a></h3>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FUNCTION</span><span class="token plain"> json_extract_field</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">RETURNS</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">LANGUAGE</span><span class="token plain"> wasm</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">VOLATILITY immutable</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'&lt;base64-encoded-wasm&gt;'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">-- Extract user agent from JSON event payloads</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SELECT</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    event_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    json_extract_field</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">payload</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'user_agent'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> user_agent</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    json_extract_field</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">payload</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'action'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">action</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FROM</span><span class="token plain"> events</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHERE</span><span class="token plain"> json_extract_field</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">payload</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'action'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'purchase'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="volatility-matters">Volatility Matters<a href="https://docs.gnok.io/blog/2026/02/23/wasm-udf-tutorial#volatility-matters" class="hash-link" aria-label="Direct link to Volatility Matters" title="Direct link to Volatility Matters" translate="no">​</a></h2>
<p>The <code>VOLATILITY</code> clause tells the optimizer how aggressively it can optimize calls to your function:</p>
<table><thead><tr><th>Volatility</th><th>When to Use</th><th>Optimizer Behavior</th></tr></thead><tbody><tr><td><code>immutable</code></td><td>Pure functions -- same inputs always produce the same output</td><td>Can be constant-folded, pushed through joins</td></tr><tr><td><code>stable</code></td><td>Depends on session state but is consistent within a query</td><td>Cached per-query, can be pushed into scans</td></tr><tr><td><code>volatile</code></td><td>Depends on external state or randomness (default)</td><td>Re-evaluated every call, never optimized</td></tr></tbody></table>
<p>Use <code>immutable</code> whenever possible -- it gives the optimizer the most freedom. All three UDFs in this tutorial are <code>immutable</code> because they are pure functions.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="sandboxing-and-limits">Sandboxing and Limits<a href="https://docs.gnok.io/blog/2026/02/23/wasm-udf-tutorial#sandboxing-and-limits" class="hash-link" aria-label="Direct link to Sandboxing and Limits" title="Direct link to Sandboxing and Limits" translate="no">​</a></h2>
<p>WASM UDFs run in a Wasmtime sandbox with strict resource limits:</p>
<table><thead><tr><th>Limit</th><th>Default</th><th>Description</th></tr></thead><tbody><tr><td>Memory per instance</td><td>64 MB</td><td>Maximum memory a single UDF invocation can use</td></tr><tr><td>Execution timeout</td><td>30 seconds</td><td>Epoch-based timeout enforcement</td></tr><tr><td>Max module size</td><td>50 MB</td><td>Maximum <code>.wasm</code> binary size</td></tr></tbody></table>
<p>If your UDF exceeds the memory limit, the WASM runtime traps and Gnok returns an error for that row batch. The timeout prevents runaway computation from blocking the query pipeline.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="distributed-execution">Distributed Execution<a href="https://docs.gnok.io/blog/2026/02/23/wasm-udf-tutorial#distributed-execution" class="hash-link" aria-label="Direct link to Distributed Execution" title="Direct link to Distributed Execution" translate="no">​</a></h2>
<p>When a query runs across multiple workers, Gnok distributes your WASM UDF automatically. Each worker compiles the module on first use and caches it, so later queries reuse the compiled module. You register the function once with SQL; there is nothing to deploy.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="managing-udfs">Managing UDFs<a href="https://docs.gnok.io/blog/2026/02/23/wasm-udf-tutorial#managing-udfs" class="hash-link" aria-label="Direct link to Managing UDFs" title="Direct link to Managing UDFs" translate="no">​</a></h2>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">-- List all registered functions</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SHOW</span><span class="token plain"> FUNCTIONS</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">-- Replace an existing function with a new version</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CREATE</span><span class="token plain"> </span><span class="token operator">OR</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">REPLACE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FUNCTION</span><span class="token plain"> mask_pii</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">RETURNS</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">LANGUAGE</span><span class="token plain"> wasm</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">VOLATILITY immutable</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AS</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'&lt;updated-base64&gt;'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">-- Remove a function</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DROP</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FUNCTION</span><span class="token plain"> mask_pii</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DROP</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">FUNCTION</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">IF</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">EXISTS</span><span class="token plain"> haversine_km</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="tips-and-best-practices">Tips and Best Practices<a href="https://docs.gnok.io/blog/2026/02/23/wasm-udf-tutorial#tips-and-best-practices" class="hash-link" aria-label="Direct link to Tips and Best Practices" title="Direct link to Tips and Best Practices" translate="no">​</a></h2>
<ol>
<li class="">
<p><strong>Keep modules small.</strong> Smaller WASM binaries compile faster and distribute quicker across workers. Use <code>wasm-opt</code> to shrink your output:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">wasm-opt </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-Oz</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-o</span><span class="token plain"> optimized.wasm target/wasm32-wasip2/release/gnok_udf_examples.wasm</span><br></span></code></pre></div></div>
</li>
<li class="">
<p><strong>Handle nulls.</strong> Arrow arrays can contain null values. Always check <code>is_null(i)</code> before accessing values, and return <code>None</code> for null inputs.</p>
</li>
<li class="">
<p><strong>Prefer batch operations.</strong> Your function receives an entire column batch (up to 8192 rows). Process the whole batch in a loop rather than returning early -- this maximizes throughput.</p>
</li>
<li class="">
<p><strong>Use <code>immutable</code> volatility</strong> for pure functions. This is the single biggest performance lever because it enables constant folding and predicate pushdown.</p>
</li>
<li class="">
<p><strong>Test locally first.</strong> You can unit-test your UDF logic as regular Rust code before compiling to WASM:</p>
<div class="language-rust codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-rust codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token attribute attr-name" style="color:rgb(241, 250, 140)">#[cfg(test)]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">mod</span><span class="token plain"> </span><span class="token module-declaration namespace">tests</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">use</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">super</span><span class="token punctuation" style="color:rgb(248, 248, 242)">::</span><span class="token operator">*</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">use</span><span class="token plain"> </span><span class="token namespace">arrow</span><span class="token namespace punctuation" style="color:rgb(248, 248, 242)">::</span><span class="token namespace">array</span><span class="token namespace punctuation" style="color:rgb(248, 248, 242)">::</span><span class="token class-name">StringArray</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token attribute attr-name" style="color:rgb(241, 250, 140)">#[test]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">fn</span><span class="token plain"> </span><span class="token function-definition function" style="color:rgb(80, 250, 123)">test_mask_pii_emails</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">let</span><span class="token plain"> input </span><span class="token operator">=</span><span class="token plain"> </span><span class="token class-name">StringArray</span><span class="token punctuation" style="color:rgb(248, 248, 242)">::</span><span class="token function" style="color:rgb(80, 250, 123)">from</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token macro property">vec!</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token class-name">Some</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"Contact alice@example.com for details"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token class-name">Some</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"No PII here"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token class-name">None</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token comment" style="color:rgb(98, 114, 164)">// ... test your masking logic</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="further-reading">Further Reading<a href="https://docs.gnok.io/blog/2026/02/23/wasm-udf-tutorial#further-reading" class="hash-link" aria-label="Direct link to Further Reading" title="Direct link to Further Reading" translate="no">​</a></h2>
<ul>
<li class=""><a class="" href="https://docs.gnok.io/docs/advanced/udf-wasm">UDF Reference</a> -- full SQL syntax and type mappings</li>
<li class=""><a class="" href="https://docs.gnok.io/docs/ai-ml/onnx-inference">ONNX Model Inference</a> -- register ML models as SQL functions</li>
<li class=""><a class="" href="https://docs.gnok.io/docs/getting-started/architecture">Service architecture</a> -- how hosted Gnok components fit together</li>
</ul>]]></content>
        <author>
            <name>Gnok Team</name>
        </author>
        <category label="Tutorial" term="Tutorial"/>
        <category label="Engineering" term="Engineering"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Welcome to the Gnok Blog]]></title>
        <id>https://docs.gnok.io/blog/2026/02/22/welcome</id>
        <link href="https://docs.gnok.io/blog/2026/02/22/welcome"/>
        <updated>2026-02-22T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Welcome to the Gnok blog. Gnok is a hosted cloud data platform: you sign in to Gnok Studio and work with your data in SQL, notebooks, and dashboards, while Gnok runs the service for you.]]></summary>
        <content type="html"><![CDATA[<p>Welcome to the Gnok blog. Gnok is a hosted cloud data platform: you sign in to <a href="https://studio.gnok.io/" target="_blank" rel="noopener noreferrer" class="">Gnok Studio</a> and work with your data in SQL, notebooks, and dashboards, while Gnok runs the service for you.</p>
<p>Here we post:</p>
<ul>
<li class=""><strong>Product updates:</strong> what's new in Gnok and Gnok Studio, and how to use it.</li>
<li class=""><strong>How-to walkthroughs:</strong> step-by-step guides you can follow in your own account.</li>
<li class=""><strong>Practical examples:</strong> end-to-end scenarios such as loading data, building change feeds, and scoring data with models.</li>
</ul>
<p>New to Gnok? Start with the <a class="" href="https://docs.gnok.io/docs/getting-started/quickstart">Quickstart</a>, then try the <a class="" href="https://docs.gnok.io/docs/ai-ml/tutorials">AI &amp; ML tutorials</a>. You can run each tutorial from Studio.</p>]]></content>
        <author>
            <name>Gnok Team</name>
        </author>
        <category label="Community" term="Community"/>
    </entry>
</feed>