| <!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("column_name")</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("col")) |
| |
| // Literal predicates: <, <=, >, >=, ==, !=, STARTS WITH, NOT STARTS WITH |
| lit := iceberg.NewLiteral(int64(42)) |
| pred := iceberg.LiteralPredicate(iceberg.OpEQ, iceberg.Reference("col"), lit) |
| |
| // Set predicates: IN / NOT IN |
| lits := []iceberg.Literal{iceberg.NewLiteral("a"), iceberg.NewLiteral("b")} |
| pred := iceberg.SetPredicate(iceberg.OpIn, iceberg.Reference("col"), 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() // -> 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("status"), "active") |
| 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> |