blob: b79f50e994f892aa4645870b1cc063040469eb8e [file]
<!DOCTYPE HTML>
<html lang="en" class="light" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Expression DSL - Iceberg Golang</title>
<!-- Custom HTML head -->
<!--
~ Licensed to the Apache Software Foundation (ASF) under one
~ or more contributor license agreements. See the NOTICE file
~ distributed with this work for additional information
~ regarding copyright ownership. The ASF licenses this file
~ to you under the Apache License, Version 2.0 (the
~ "License"); you may not use this file except in compliance
~ with the License. You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing,
~ software distributed under the License is distributed on an
~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
~ KIND, either express or implied. See the License for the
~ specific language governing permissions and limitations
~ under the License.
-->
<!-- Matomo -->
<script>
var _paq = window._paq = window._paq || [];
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
_paq.push(["setDoNotTrack", true]);
_paq.push(["disableCookies"]);
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function() {
var u="https://analytics.apache.org/";
_paq.push(['setTrackerUrl', u+'matomo.php']);
_paq.push(['setSiteId', '82']);
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
})();
</script>
<!-- End Matomo Code -->
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff">
<link rel="icon" href="favicon.svg">
<link rel="shortcut icon" href="favicon.png">
<link rel="stylesheet" href="css/variables.css">
<link rel="stylesheet" href="css/general.css">
<link rel="stylesheet" href="css/chrome.css">
<link rel="stylesheet" href="css/print.css" media="print">
<!-- Fonts -->
<link rel="stylesheet" href="FontAwesome/css/font-awesome.css">
<link rel="stylesheet" href="fonts/fonts.css">
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" href="highlight.css">
<link rel="stylesheet" href="tomorrow-night.css">
<link rel="stylesheet" href="ayu-highlight.css">
<!-- Custom theme stylesheets -->
</head>
<body class="sidebar-visible no-js">
<div id="body-container">
<!-- Provide site root to javascript -->
<script>
var path_to_root = "";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
</script>
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script>
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script>
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
var html = document.querySelector('html');
html.classList.remove('light')
html.classList.add(theme);
var body = document.querySelector('body');
body.classList.remove('no-js')
body.classList.add('js');
</script>
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
<!-- Hide / unhide sidebar before it is displayed -->
<script>
var body = document.querySelector('body');
var sidebar = null;
var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
} else {
sidebar = 'hidden';
}
sidebar_toggle.checked = sidebar === 'visible';
body.classList.remove('sidebar-visible');
body.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<div class="sidebar-scrollbox">
<ol class="chapter"><li class="chapter-item expanded "><a href="introduction.html">Introduction</a></li><li class="chapter-item expanded affix "><li class="part-title">User Guide</li><li class="chapter-item expanded "><a href="install.html">Install</a></li><li class="chapter-item expanded "><a href="getting-started.html">Getting Started</a></li><li class="chapter-item expanded "><a href="configuration.html">Configuration</a></li><li class="chapter-item expanded "><a href="cli.html">CLI</a></li><li class="chapter-item expanded "><a href="api.html">API</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="variant.html">Variant Type</a></li><li class="chapter-item expanded "><a href="row-filter-syntax.html">Row Filter Syntax</a></li><li class="chapter-item expanded "><a href="expression-dsl.html" class="active">Expression DSL</a></li><li class="chapter-item expanded "><a href="concurrent-writes.html">Concurrent Writes</a></li><li class="chapter-item expanded "><a href="metrics-reporting.html">Metrics Reporting</a></li></ol></li><li class="chapter-item expanded "><a href="feature-status.html">Feature Status</a></li><li class="chapter-item expanded affix "><li class="part-title">Developer Guide</li><li class="chapter-item expanded "><a href="contributing.html">Contributing</a></li><li class="chapter-item expanded "><a href="community.html">Community</a></li><li class="chapter-item expanded "><a href="glossary.html">Glossary</a></li><li class="chapter-item expanded affix "><li class="part-title">Releases</li><li class="chapter-item expanded "><a href="releases.html">Releases</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="verify-release.html">Verify a release</a></li><li class="chapter-item expanded "><a href="how-to-release.html">How to release</a></li></ol></li><li class="chapter-item expanded "><li class="part-title">Ecosystem</li><li class="chapter-item expanded "><a href="ecosystem.html">Other Iceberg implementations</a></li></ol>
</div>
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
<div class="sidebar-resize-indicator"></div>
</div>
</nav>
<!-- Track and set sidebar scroll position -->
<script>
var sidebarScrollbox = document.querySelector('#sidebar .sidebar-scrollbox');
sidebarScrollbox.addEventListener('click', function(e) {
if (e.target.tagName === 'A') {
sessionStorage.setItem('sidebar-scroll', sidebarScrollbox.scrollTop);
}
}, { passive: true });
var sidebarScrollTop = sessionStorage.getItem('sidebar-scroll');
sessionStorage.removeItem('sidebar-scroll');
if (sidebarScrollTop) {
// preserve sidebar scroll position when navigating via links within sidebar
sidebarScrollbox.scrollTop = sidebarScrollTop;
} else {
// scroll sidebar to current active section when navigating via "next/previous chapter" buttons
var activeSection = document.querySelector('#sidebar .active');
if (activeSection) {
activeSection.scrollIntoView({ block: 'center' });
}
}
</script>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar-hover-placeholder"></div>
<div id="menu-bar" class="menu-bar sticky">
<div class="left-buttons">
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</label>
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
</div>
<h1 class="menu-title">Iceberg Golang</h1>
<div class="right-buttons">
<a href="print.html" title="Print this book" aria-label="Print this book">
<i id="print-button" class="fa fa-print"></i>
</a>
<a href="https://github.com/apache/iceberg-go" title="Git repository" aria-label="Git repository">
<i id="git-repository-button" class="fa fa-github"></i>
</a>
<a href="https://github.com/apache/iceberg-go/edit/main/website/src/expression-dsl.md" title="Suggest an edit" aria-label="Suggest an edit">
<i id="git-edit-button" class="fa fa-edit"></i>
</a>
</div>
</div>
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script>
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
<!--
~ Licensed to the Apache Software Foundation (ASF) under one
~ or more contributor license agreements. See the NOTICE file
~ distributed with this work for additional information
~ regarding copyright ownership. The ASF licenses this file
~ to you under the Apache License, Version 2.0 (the
~ "License"); you may not use this file except in compliance
~ with the License. You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing,
~ software distributed under the License is distributed on an
~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
~ KIND, either express or implied. See the License for the
~ specific language governing permissions and limitations
~ under the License.
-->
<h1 id="expression-dsl"><a class="header" href="#expression-dsl">Expression DSL</a></h1>
<p>This page covers the building blocks behind the row-filter shortcuts in <a href="./row-filter-syntax.html">Row Filter Syntax</a>: boolean combinators, terms, and the lower-level predicate constructors.</p>
<p>The DSL lives entirely in the root <code>iceberg</code> package (see <code>exprs.go</code> and <code>predicates.go</code>).</p>
<h2 id="boolean-combinators"><a class="header" href="#boolean-combinators">Boolean combinators</a></h2>
<pre><code class="language-go">iceberg.NewAnd(a, b) // a AND b
iceberg.NewAnd(a, b, c, d) // a AND b AND c AND d (variadic)
iceberg.NewOr(a, b) // a OR b
iceberg.NewOr(a, b, c, d) // a OR b OR c OR d
iceberg.NewNot(a) // NOT a
</code></pre>
<p><code>NewAnd</code> and <code>NewOr</code> accept two required arguments plus a variadic tail (<code>exprs.go:226</code>, <code>exprs.go:287</code>). They simplify automatically:</p>
<ul>
<li><code>NewAnd(x, AlwaysTrue{})</code> reduces to <code>x</code>.</li>
<li><code>NewAnd(x, AlwaysFalse{})</code> reduces to <code>AlwaysFalse{}</code>.</li>
<li><code>NewOr(x, AlwaysFalse{})</code> reduces to <code>x</code>.</li>
<li><code>NewOr(x, AlwaysTrue{})</code> reduces to <code>AlwaysTrue{}</code>.</li>
<li><code>NewNot(NewNot(x))</code> reduces to <code>x</code>.</li>
</ul>
<h2 id="constants"><a class="header" href="#constants">Constants</a></h2>
<pre><code class="language-go">iceberg.AlwaysTrue{}
iceberg.AlwaysFalse{}
</code></pre>
<p>Both satisfy <code>BooleanExpression</code>. Use them as the identity element when composing filters dynamically.</p>
<h2 id="terms"><a class="header" href="#terms">Terms</a></h2>
<p>A <em>term</em> is the left-hand side of a predicate. Iceberg-go has two flavors:</p>
<ul>
<li><code>Reference(&quot;column_name&quot;)</code> - an unbound term that names a column (<code>exprs.go:373</code>). Typing happens at bind time, when the expression is matched against a schema. This is what you almost always want.</li>
<li><code>BoundReference</code> - the resolved form, produced by <code>Reference.Bind(schema, caseSensitive)</code> (<code>exprs.go:389</code>). You only encounter these when writing custom expression visitors.</li>
<li><code>Extract(ref, path, typ)</code> - a variant sub-path term that navigates a member-selector path (<code>$.a.b</code> or <code>$['a']['b']</code>; no array indexing or wildcards) into a <code>variant</code> column and casts the leaf to <code>typ</code> (<code>variant_extract.go</code>). Use it inside any predicate builder to filter on a field within a variant. See <a href="./row-filter-syntax.html#variant-extraction">Row Filter Syntax</a>.</li>
</ul>
<p>The interfaces are:</p>
<pre><code class="language-go">type Term interface { ... } // shared marker
type UnboundTerm interface { Term; ... } // pre-bind
type BoundTerm interface { Term; Ref() BoundReference; ... }
</code></pre>
<p>(<code>exprs.go:317-348</code>)</p>
<h2 id="predicates"><a class="header" href="#predicates">Predicates</a></h2>
<p>A <em>predicate</em> applies an <code>Operation</code> to one or more terms. <code>BooleanExpression</code> is the shared interface (<code>exprs.go:123</code>).</p>
<h3 id="convenience-builders-recommended"><a class="header" href="#convenience-builders-recommended">Convenience builders (recommended)</a></h3>
<p>For all the common shapes, the constructors in <code>predicates.go</code> are the right tool. See <a href="./row-filter-syntax.html">Row Filter Syntax</a> for the full list (<code>EqualTo</code>, <code>LessThan</code>, <code>IsIn</code>, <code>IsNull</code>, <code>StartsWith</code>, etc.).</p>
<h3 id="lower-level-escape-hatches"><a class="header" href="#lower-level-escape-hatches">Lower-level escape hatches</a></h3>
<p>When the convenience builders are not enough (custom operators, dynamic operation selection, working with already-typed <code>Literal</code> values), use the predicate constructors directly:</p>
<pre><code class="language-go">// Unary predicates: IS NULL / NOT NULL / IS NaN / NOT NaN
pred := iceberg.UnaryPredicate(iceberg.OpIsNull, iceberg.Reference(&quot;col&quot;))
// Literal predicates: &lt;, &lt;=, &gt;, &gt;=, ==, !=, STARTS WITH, NOT STARTS WITH
lit := iceberg.NewLiteral(int64(42))
pred := iceberg.LiteralPredicate(iceberg.OpEQ, iceberg.Reference(&quot;col&quot;), lit)
// Set predicates: IN / NOT IN
lits := []iceberg.Literal{iceberg.NewLiteral(&quot;a&quot;), iceberg.NewLiteral(&quot;b&quot;)}
pred := iceberg.SetPredicate(iceberg.OpIn, iceberg.Reference(&quot;col&quot;), lits)
</code></pre>
<p><code>UnaryPredicate</code> lives at <code>exprs.go:534</code>. <code>LiteralPredicate</code> and <code>SetPredicate</code> live in the same file.</p>
<h2 id="negation"><a class="header" href="#negation">Negation</a></h2>
<p>Every <code>BooleanExpression</code> and every <code>Operation</code> knows how to negate itself.</p>
<pre><code class="language-go">op := iceberg.OpEQ
op.Negate() // -&gt; OpNEQ
</code></pre>
<p>(<code>exprs.go:65-98</code>. <code>OpNot</code>, <code>OpAnd</code>, and <code>OpOr</code> panic on direct negation - negate the wrapping expression instead.)</p>
<pre><code class="language-go">expr := iceberg.EqualTo(iceberg.Reference(&quot;status&quot;), &quot;active&quot;)
inverted := expr.Negate() // equivalent to NotEqualTo(...)
</code></pre>
<h2 id="binding-and-evaluation"><a class="header" href="#binding-and-evaluation">Binding and evaluation</a></h2>
<p>Most user code stops at constructing the unbound expression - the scan pipeline handles binding, projection, and evaluation internally. If you are writing a custom visitor:</p>
<ul>
<li><code>(Reference).Bind(schema, caseSensitive)</code> returns a <code>BoundTerm</code> (<code>exprs.go:389</code>).</li>
<li><code>BoundExpression</code>s expose <code>Ref()</code>, <code>Type()</code>, and (for terms) the underlying <code>accessor</code> for evaluating against a <code>StructLike</code> row.</li>
</ul>
<p>For projection, evaluation, and visitor patterns, see <code>visitors.go</code> and the scan internals in <code>table/scanner.go</code>.</p>
<h2 id="when-to-reach-for-what"><a class="header" href="#when-to-reach-for-what">When to reach for what</a></h2>
<div class="table-wrapper"><table><thead><tr><th>Goal</th><th>Use</th></tr></thead><tbody>
<tr><td>Filter a scan or row-level delete</td><td>Convenience builders + <code>NewAnd</code>/<code>NewOr</code>/<code>NewNot</code></td></tr>
<tr><td>Combine many clauses dynamically</td><td>Start from <code>AlwaysTrue{}</code> (for AND) or <code>AlwaysFalse{}</code> (for OR), fold with <code>NewAnd</code>/<code>NewOr</code></td></tr>
<tr><td>Construct a predicate whose operator is chosen at runtime</td><td><code>UnaryPredicate(op, term)</code>, <code>LiteralPredicate(op, term, lit)</code>, <code>SetPredicate(op, term, lits)</code></td></tr>
<tr><td>Filter on a field inside a variant column</td><td><code>Extract(ref, path, typ)</code> inside a predicate builder</td></tr>
<tr><td>Walk an expression tree</td><td>A custom <code>BooleanExprVisitor</code> from <code>visitors.go</code></td></tr>
</tbody></table>
</div>
<p>For the per-operator cookbook, return to <a href="./row-filter-syntax.html">Row Filter Syntax</a>.</p>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="row-filter-syntax.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="concurrent-writes.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="row-filter-syntax.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="concurrent-writes.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</nav>
</div>
<script>
window.playground_copyable = true;
</script>
<script src="elasticlunr.min.js"></script>
<script src="mark.min.js"></script>
<script src="searcher.js"></script>
<script src="clipboard.min.js"></script>
<script src="highlight.js"></script>
<script src="book.js"></script>
<!-- Custom JS scripts -->
</div>
</body>
</html>