Comparison

SQE vs Trino

SQE is a drop-in replacement for Trino in Iceberg-only environments — roughly 96% SQL compatibility, the Trino HTTP wire protocol so existing clients and dbt models connect unchanged, and per-query identity instead of a shared service account. The matrix below maps every Trino function and feature to its SQE equivalent.

Current as of 19 October 2026 · synced from the SQE source

✅ equivalent ⚠️ partial / different ❌ missing 🔧 SQE-specific

Iceberg matrix · vs DuckDB · SQL features

Trino SQL Compatibility Matrix

SQE aims to be a drop-in replacement for Trino in Iceberg-only environments. This document maps every Trino SQL function and feature to its SQE equivalent, noting semantic differences and gaps.

Summary

CategoryTotal✅⚠️❌Coverage
Scalar: String272700100%
Scalar: Math292900100%
Scalar: Date/Time383800100%
Scalar: JSON121200100%
Scalar: URL8800100%
Scalar: Regex6600100%
Scalar: Conditional8710100%
Scalar: Conversion1090190%
Aggregate35330294.3%
Window14130192.9%
DDL/DML37 + 1🔧322391.9%
Type System27220581.5%
Iceberg-Specific19160384.2%

Overall Coverage

~96% Trino SQL compatibility for Iceberg-only workloads. The remaining gaps are:

  • Trino-specific sketch types (HyperLogLog, TDigest, SetDigest). Not used in typical Iceberg analytics.
  • approx_most_frequent(n, x, cap): Trino’s Count-Min Sketch UDAF, one of two ❌ remaining in the Aggregate category. The other is merge(digest) (HyperLogLog/TDigest sketch types — not planned). All four Map-producing UDAFs (histogram, map_agg, multimap_agg, map_union) shipped.
  • CREATE MATERIALIZED VIEW. Not in Iceberg spec; use CTAS + scheduled refresh.
  • Lambda in window functions. DataFusion engine limitation.
  • ORC format. Strategic choice: Parquet only.
  • TIME WITH TIME ZONE. No Arrow equivalent. Use TIMESTAMP WITH TIME ZONE instead. SQE rejects with a clear NotImplemented at CREATE TABLE.
  • Sort order enforcement on write. Iceberg metadata is written but files are not physically sorted.
  • Write distribution mode. Distributed write path lands in Phase 3+.

How to Read This Document

Each section lists Trino functions with their SQE status:

Trino FunctionSQE EquivalentStatusNotes
concat(s1, s2, ...)concat(s1, s2, ...)✅Native DataFusion
approx_most_frequent(n, x, cap)—❌Count-Min Sketch UDAF; not planned
year(date)year(date)✅Trino compat UDF in sqe-coordinator

Scalar Functions: String

Trino FunctionSQE EquivalentStatusNotes
chr(n)chr(n)✅Native DataFusion
codepoint(s)codepoint(s)✅Trino compat UDF; full Unicode code point via proper UTF-8 decode. Errors on multi-character input per Trino spec
concat(s1, s2, ...)concat(s1, s2, ...)✅Native DataFusion
concat_ws(sep, s1, s2, ...)concat_ws(sep, s1, s2, ...)✅Native DataFusion
format(fmt, ...)format(fmt, ...)✅Trino compat UDF (%s, %d, %f, zero-pad, precision)
hamming_distance(s1, s2)hamming_distance(s1, s2)✅Trino compat UDF
length(s)length(s) / char_length(s)✅Native DataFusion
levenshtein_distance(s1, s2)levenshtein(s1, s2)✅Native DataFusion
lower(s)lower(s)✅Native DataFusion
lpad(s, size, pad)lpad(s, size, pad)✅Native DataFusion
ltrim(s)ltrim(s)✅Native DataFusion
normalize(s, form)normalize(s, form)✅Trino compat UDF (NFC/NFD/NFKC/NFKD)
position(sub IN s)position(sub IN s) / strpos(s, sub)✅Both syntaxes work
replace(s, from, to)replace(s, from, to)✅Native DataFusion
reverse(s)reverse(s)✅Native DataFusion
rpad(s, size, pad)rpad(s, size, pad)✅Native DataFusion
rtrim(s)rtrim(s)✅Native DataFusion
soundex(s)soundex(s)✅Trino compat UDF
split(s, delim)split(s, delim)✅Trino-aliased on string_to_array(s, delim); returns ARRAY(VARCHAR)
split_part(s, delim, idx)split_part(s, delim, idx)✅Native DataFusion
strpos(s, sub)strpos(s, sub)✅Trino compat UDF
substr(s, start, len)substr(s, start, len)✅Native DataFusion
translate(s, from, to)translate(s, from, to)✅Native DataFusion
trim(s)trim(s)✅Native DataFusion
upper(s)upper(s)✅Native DataFusion
word_stem(s)word_stem(s)✅Trino compat UDF (English default)
word_stem(s, lang)word_stem(s, lang)✅Single UDF accepts both word_stem(s) (English default) and word_stem(s, lang); word_stem_lang(s, lang) kept as a registered alias for backward compat. 17 languages

Scalar Functions: Math

Trino FunctionSQE EquivalentStatusNotes
abs(x)abs(x)✅
acos(x) / asin(x) / atan(x)Same✅
atan2(y, x)atan2(y, x)✅
cbrt(x)cbrt(x)✅
ceil(x) / ceiling(x)ceil(x)✅
cos(x) / sin(x) / tan(x)cos(x) / sin(x) / tan(x)✅
cosh(x) / sinh(x) / tanh(x)Same✅Native DataFusion (already built-in)
degrees(x)degrees(x)✅
e()e()✅Trino compat Nullary UDF returning std::f64::consts::E
exp(x)exp(x)✅
floor(x)floor(x)✅
from_base(s, radix)from_base(s, radix)✅Trino compat UDF
infinity()infinity()✅Trino compat UDF
ln(x)ln(x)✅
log(b, x)log(b, x)✅
log2(x)log2(x)✅
log10(x)log10(x)✅
mod(n, m)mod(n, m)✅Trino compat UDF; coerces numeric args to Float64. Errors on mod(_, 0) per IEEE 754
nan()nan()✅Trino compat UDF
pi()pi()✅
pow(x, p) / power(x, p)power(x, p)✅
radians(x)radians(x)✅
rand() / random()random()✅
round(x) / round(x, d)round(x, d)✅
sign(x)sign(x)✅Trino compat UDF; matches Trino spec including sign(0) = 0 (Rust’s f64::signum(0.0) returns 1.0, so the UDF overrides the zero case)
sqrt(x)sqrt(x)✅
to_base(n, radix)to_base(n, radix)✅Trino compat UDF
truncate(x[, n])truncate(x[, n])✅Trino compat UDF; truncates toward zero with optional decimal-precision argument
width_bucket(x, bound1, bound2, n)Same✅Native DataFusion (built-in in DF 52)
bitwise_and(x, y) / bitwise_or(x, y) / bitwise_xor(x, y)Same✅Trino compat UDF; widens integer args to bigint like Trino
bitwise_not(x)bitwise_not(x)✅Trino compat UDF
bitwise_left_shift(v, s) / bitwise_right_shift(v, s)Same✅Trino compat UDF; logical (zero-fill) shift on 64 bits. Trino’s narrower integer-width shift is not reachable because DataFusion types integer literals as bigint

Scalar Functions: Array

Trino FunctionSQE EquivalentStatusNotes
sequence(start, stop) / sequence(start, stop, step)sequence(...)✅Trino spelling of DataFusion’s inclusive generate_series; integer and date/timestamp + INTERVAL forms. The one gap is 2-arg descending (sequence(5, 1)), which needs an explicit negative step
slice(array, start, length)slice(array, start, length)✅Trino compat UDF; 1-based, negative start counts from the end, length clamps to the array end
element_at(array, n)element_at(array, n)✅Trino compat UDF; 1-based, negative from the end, out-of-bounds returns NULL. Also handles element_at(map, key) returning the scalar value
contains(array, x)contains(array, x)✅Trino compat UDF; three-valued (NULL when x is absent but the array holds a NULL). The string contains(haystack, needle) form is preserved
filter(array, x -> pred)filter(array, x -> pred)✅Higher-order; aliases DataFusion 54’s array_filter. Lambda syntax comes from the DuckDB parse dialect. 1-based binding, NULL/empty-array parity with Trino
transform(array, x -> expr)transform(array, x -> expr)✅Higher-order; aliases DataFusion 54’s array_transform. Lambda syntax from the DuckDB parse dialect
any_match(array, x -> pred)any_match(array, x -> pred)✅Higher-order; DataFusion alias of array_any_match
all_match(array, x -> pred)all_match(array, x -> pred)✅Higher-order SQE UDF (the sqe-trino-functions crate); Trino NULL/empty semantics
none_match(array, x -> pred)none_match(array, x -> pred)✅Higher-order SQE UDF; Trino NULL/empty semantics
reduce(array, init, (s,x) -> combine, s -> finish)Same✅Higher-order SQE UDF; sequential left fold, NULL array yields NULL

Scalar Functions: Date/Time

Trino FunctionSQE EquivalentStatusNotes
current_datecurrent_date✅SQL standard
current_timecurrent_time✅Native DataFusion (already built-in)
current_timestampcurrent_timestamp / now()✅
current_timezone()current_timezone()✅Trino compat UDF (returns “UTC”)
now()now()✅Trino compat UDF
localtimelocaltime()✅Trino compat UDF
localtimestamplocaltimestamp()✅Trino compat UDF
date(s)trino_date(s)✅Trino compat UDF
from_iso8601_date(s)from_iso8601_date(s)✅Trino compat UDF
from_iso8601_timestamp(s)from_iso8601_timestamp(s)✅Trino compat UDF
from_unixtime(n)from_unixtime(n)✅Trino compat UDF
to_unixtime(ts)to_unixtime(ts)✅Trino compat UDF
to_iso8601(ts)to_iso8601(ts)✅Trino compat UDF
date_add(unit, n, ts)date_add(unit, n, ts)✅Trino compat UDF in Trino’s argument order. The previous “different argument order” caveat was a stale doc claim; the implementation in the sqe-trino-functions crate has always taken (unit, amount, date_or_ts), matching Trino’s spec
date_diff(unit, ts1, ts2)date_diff(unit, ts1, ts2)✅Trino compat UDF
date_trunc(unit, ts)date_trunc(unit, ts)✅Native DataFusion
date_format(ts, fmt)date_format(ts, fmt)✅Trino compat UDF (MySQL format codes)
date_parse(s, fmt)date_parse(s, fmt)✅Trino compat UDF (MySQL format codes)
format_datetime(ts, fmt)format_datetime(ts, fmt)✅Trino compat UDF (Joda→chrono translation)
parse_datetime(s, fmt)parse_datetime(s, fmt)✅Trino compat UDF (Joda→chrono translation)
year(d)year(d)✅Trino compat UDF
quarter(d)quarter(d)✅Trino compat UDF
month(d)month(d)✅Trino compat UDF
week(d)week(d)✅Trino compat UDF
day(d) / day_of_month(d)day(d)✅Trino compat UDF
day_of_week(d) / dow(d)day_of_week(d)✅Trino compat UDF; ISO day-of-week Monday=1 .. Sunday=7 (was Sunday=0 before #361)
day_of_year(d) / doy(d)day_of_year(d)✅Trino compat UDF
hour(ts)hour(ts)✅Trino compat UDF
minute(ts)minute(ts)✅Trino compat UDF
second(ts)second(ts)✅Trino compat UDF
millisecond(ts)millisecond(ts)✅Trino compat UDF
timezone_hour(ts)timezone_hour(ts)✅Trino compat UDF (returns 0, UTC-only)
timezone_minute(ts)timezone_minute(ts)✅Trino compat UDF (returns 0, UTC-only)
with_timezone(ts, tz)with_timezone(ts, tz)✅Trino compat UDF (chrono-tz)
at_timezone(ts, tz)at_timezone(ts, tz)✅Trino compat UDF (chrono-tz)
INTERVAL 'n' UNITINTERVAL 'n' UNIT✅SQL standard
human_readable_seconds(n)human_readable_seconds(n)✅Trino compat UDF
last_day_of_month(d)last_day_of_month(d)✅Trino compat UDF

Scalar Functions: JSON

Trino FunctionSQE EquivalentStatusNotes
json_object(k1, v1, k2, v2, ...)json_object(k1, v1, ...)✅Trino compat UDF
json_format(json)json_format(json)✅Trino compat UDF
json_parse(s)json_parse(s)✅Trino compat UDF
json_extract(json, path)json_extract(json, path)✅Trino compat UDF (dot-path, not full JSONPath)
json_extract_scalar(json, path)json_extract_scalar(json, path)✅Trino compat UDF
json_size(json, path)json_size(json, path)✅Trino compat UDF
json_array_contains(json, val)json_array_contains(json, val)✅Trino compat UDF
json_array_get(json, idx)json_array_get(json, idx)✅Trino compat UDF (supports negative index)
json_array_length(json)json_array_length(json)✅Trino compat UDF
is_json_scalar(json)is_json_scalar(json)✅Trino compat UDF
CAST(v AS JSON)CAST(v AS JSON)✅sqe-sql AST rewriter intercepts CAST(... AS JSON) and rewrites to to_json(...) before DataFusion’s planner sees it (DataFusion does not recognize JSON as a target type for CAST). Skipped when the SQL does not contain as json
CAST(json AS type)CAST(json_col AS type)✅JSON reads back as JSON text (VARIANT on V3, Utf8 on V2); CAST rides DataFusion’s built-in coercion. For typed extraction from JSONPath, use json_get_int(j, '$'), json_get_str(j, '$'), etc.

Note: Core JSON extraction is now supported via datafusion-functions-json (registered at startup) plus Trino-aliased UDFs (json_extract, json_extract_scalar, json_array_length, json_parse). Full JSONPath syntax and JSON-typed columns remain unsupported — most Iceberg workloads use structured columns rather than JSON blobs.

Scalar Functions: URL

Trino FunctionSQE EquivalentStatusNotes
url_extract_host(url)url_extract_host(url)✅Trino compat UDF
url_extract_path(url)url_extract_path(url)✅Trino compat UDF
url_extract_port(url)url_extract_port(url)✅Trino compat UDF
url_extract_protocol(url)url_extract_protocol(url)✅Trino compat UDF
url_extract_query(url)url_extract_query(url)✅Trino compat UDF
url_extract_parameter(url, name)url_extract_parameter(url, name)✅Trino compat UDF
url_encode(s)url_encode(s)✅Trino compat UDF
url_decode(s)url_decode(s)✅Trino compat UDF

Scalar Functions: Regex

Trino FunctionSQE EquivalentStatusNotes
regexp_count(s, pattern)regexp_count(s, pattern)✅Native DataFusion
regexp_extract(s, pattern)regexp_extract(s, pattern)✅Trino compat UDF
regexp_extract_all(s, pattern)regexp_extract_all(s, pattern)✅Returns ARRAY(VARCHAR) (was previously a JSON-array string for legacy ARRAY-less callers; re-wired now that DataFusion’s ARRAY plumbing is solid). Errors on invalid regex per Trino spec
regexp_like(s, pattern)regexp_like(s, pattern)✅Native DataFusion
regexp_replace(s, pattern, repl)regexp_replace(s, pattern, repl)✅
regexp_split(s, pattern)regexp_split(s, pattern)✅Returns ARRAY(VARCHAR); same re-wiring as regexp_extract_all

Scalar Functions: Conditional

Trino FunctionSQE EquivalentStatusNotes
CASE WHEN ... THEN ... ENDSame✅SQL standard
COALESCE(v1, v2, ...)Same✅
NULLIF(v1, v2)Same✅
GREATEST(v1, v2, ...)Same✅Native DataFusion
LEAST(v1, v2, ...)Same✅Native DataFusion
IF(cond, true, false)trino_if(cond, true, false)✅Trino compat UDF
TRY(expr)try(expr)⚠️Passthrough UDF; does not catch runtime errors (DataFusion limitation), but query won’t fail with “unknown function”
TRY_CAST(v AS type)TRY_CAST(v AS type)✅Native DataFusion

Scalar Functions: Conversion / Type Cast

Trino FunctionSQE EquivalentStatusNotes
CAST(v AS type)Same✅
TRY_CAST(v AS type)Same✅
typeof(v)typeof(v)✅Trino compat UDF
format(fmt, ...)format(fmt, ...)✅Trino compat UDF (%s, %d, %f, zero-pad, precision)
from_utf8(binary)from_utf8(binary)✅Trino compat UDF
to_utf8(string)to_utf8(string)✅Trino compat UDF
from_base64(s)from_base64(s)✅Trino compat UDF
to_base64(binary)to_base64(binary)✅Trino compat UDF
from_hex(s)from_hex(s)✅Trino compat UDF
to_hex(binary)to_hex(binary)✅Trino compat UDF (named to_hex_binary to avoid conflict with DataFusion’s integer to_hex)

Aggregate Functions

Trino FunctionSQE EquivalentStatusNotes
count(*) / count(x)Same✅
count(DISTINCT x)Same✅
count_if(pred)count_if(pred)✅Trino compat UDAF; counts TRUE rows, ignores false and NULL
sum(x)Same✅
avg(x)Same✅
min(x) / max(x)Same✅
bool_and(x) / bool_or(x)bool_and(x) / bool_or(x)✅
every(x)every(x)✅Real aggregate alias on bool_and_udaf (replaced an earlier scalar stub that returned the input unchanged and was wrong in any GROUP BY)
array_agg(x)array_agg(x)✅
array_agg(x ORDER BY y)Same✅DataFusion supports ordered agg
string_agg(x, sep)string_agg(x, sep)✅
listagg(x, sep)listagg(x, sep)✅DataFusion’s string_agg UDAF re-registered with listagg alias. listagg(x, sep) WITHIN GROUP (ORDER BY ...) is rewritten to string_agg(x, sep ORDER BY ...); percentile_cont / percentile_disc keep native WITHIN GROUP
approx_distinct(x)approx_distinct(x)✅
approx_percentile(x, p)approx_percentile(x, p)✅DataFusion’s approx_percentile_cont UDAF re-registered with approx_percentile alias
stddev(x) / stddev_samp(x)Same✅
stddev_pop(x)Same✅
variance(x) / var_samp(x)Same✅DataFusion’s sample-variance UDAF (var) re-registered with variance alias
var_pop(x)Same✅
skewness(x)skewness(x)✅Real AggregateUDFImpl in the sqe-trino-functions crate. Online central moments; population skewness sqrt(n)*m3/m2^1.5 matching Trino (NULL for n<3)
kurtosis(x)kurtosis(x)✅Same accumulator as skewness. Sample excess kurtosis matching Trino’s formula (NULL for n<4)
covar_samp(y, x)covar_samp(y, x)✅
covar_pop(y, x)covar_pop(y, x)✅
corr(y, x)corr(y, x)✅
regr_slope(y, x)regr_slope(y, x)✅
bitwise_and_agg(x)bitwise_and_agg(x)✅DataFusion’s bit_and UDAF re-registered with bitwise_and_agg alias
bitwise_or_agg(x)bitwise_or_agg(x)✅DataFusion’s bit_or UDAF re-registered with bitwise_or_agg alias
bitwise_xor_agg(x)bitwise_xor_agg(x)✅DataFusion’s bit_xor UDAF re-registered with bitwise_xor_agg alias (DuckDB / Snowflake spelling)
arbitrary(x)arbitrary(x)✅Trino compat UDF (returns first non-null)
max_by(x, y) / min_by(x, y)max_by(x, y) / min_by(x, y)✅Real AggregateUDFImpl in the sqe-trino-functions crate. Type-flexible (x any type, y any orderable type). arg_max(x, y) / arg_min(x, y) registered as aliases (DuckDB / ClickHouse spelling)
histogram(x)histogram(x)✅Real AggregateUDFImpl in the sqe-trino-functions crate. Returns MAP<typeof(x), BIGINT> with the count per distinct value. Type-flexible key. Multi-phase aggregation supported via List<Struct{key, count}> state. NULLs skipped per Trino spec
multimap_agg(k, v)multimap_agg(k, v)✅Real AggregateUDFImpl in the sqe-trino-functions crate. Returns MAP<typeof(k), ARRAY<typeof(v)>>. NULL keys skipped; insertion order preserved within each value list
map_agg(k, v)map_agg(k, v)✅Real AggregateUDFImpl in the sqe-trino-functions crate. Returns MAP<typeof(k), typeof(v)>. Last-wins on duplicate keys (matches DuckDB / Snowflake)
map_union(map)map_union(m)✅Real AggregateUDFImpl in the sqe-trino-functions crate. Takes a MAP<K, V> column and merges every input map into one. Last-wins on duplicate keys
checksum(x)checksum(x)✅Trino compat UDF (hash-based)
approx_most_frequent(n, x, cap)—❌
merge(digest)—❌HyperLogLog/TDigest
GROUPING SETS / CUBE / ROLLUPSame✅Native DataFusion

Window Functions

Trino FunctionSQE EquivalentStatusNotes
row_number()Same✅
rank()Same✅
dense_rank()Same✅
ntile(n)Same✅
percent_rank()Same✅
cume_dist()Same✅
lead(x, offset, default)Same✅
lag(x, offset, default)Same✅
first_value(x)Same✅
last_value(x)Same✅
nth_value(x, n)Same✅
Frame specs: ROWS/RANGE/GROUPSAll three supported✅Native DataFusion (GROUPS added in DF 19, 2022)
QUALIFY clauseSame✅Native DataFusion + sqlparser 0.53
Lambda in window functions—❌No lambda support

DDL / DML Statements

Trino StatementSQE SupportStatusNotes
CREATE TABLE (cols) WITH (...)CREATE TABLE (cols) WITH (...)✅Trino’s WITH (foo = 'bar') syntax merges into table properties via merge_user_table_properties in write_handler.rs:589-590, alongside TBLPROPERTIES (...). Both spellings produce identical Iceberg metadata
CREATE TABLE AS SELECTSame✅
CREATE TABLE ... LIKECREATE TABLE new (LIKE src)✅Classifier rewrites the parenthesized form (unsupported by GenericDialect) to plain LIKE; handle_create_table_like copies the source schema. Unqualified source names resolve against the session schema (X-Trino-Schema)
DROP TABLESame✅
ALTER TABLE ... RENAME TOSame✅
ALTER TABLE ... ADD COLUMNSame✅
ALTER TABLE ... DROP COLUMNSame✅Top-level and nested struct-subfield drops. DROP COLUMN a.b is a pre-parse intercept (sqlparser cannot parse the dotted path) routed to drop_nested_column, which rebuilds the struct without the leaf via remove_struct_subfield and commits an Iceberg schema update. Missing leaf errors unless IF EXISTS. Schema surgery is unit-verified; the end-to-end Iceberg commit is best confirmed against a live stack
ALTER TABLE ... RENAME COLUMNSame✅
ALTER TABLE ... SET/DROP NOT NULLSame✅
ALTER TABLE ... SET PROPERTIESALTER TABLE ... SET TBLPROPERTIES✅Iceberg TableUpdate::SetProperties
CREATE VIEWSame✅Iceberg views
CREATE VIEW ... COMMENT '<text>'Same✅Stored as the view’s comment property
CREATE VIEW ... SECURITY DEFINER | INVOKERAccepted, recorded⚠️SQE always evaluates views as INVOKER (no service account to run as the definer). Recorded in sqe.view-security; stricter than DEFINER, so it fails closed
DROP VIEWSame✅
CREATE OR REPLACE VIEWSame✅Drop + recreate (non-atomic)
CREATE MATERIALIZED VIEW—❌Not in Iceberg spec; use CTAS + scheduled refresh
INSERT INTO ... VALUESSame✅
INSERT INTO ... SELECTSame✅
DELETE FROM ... WHERESame✅CoW rewrite_files
UPDATE ... SET ... WHERESame✅CoW rewrite_files
MERGE INTO ... USING ...Same✅CoW full-outer-join rewrite
TRUNCATE TABLETRUNCATE TABLE t✅Routes to DELETE FROM (no WHERE)
COMMENT ON TABLE/COLUMNSame✅Stored as Iceberg table property (comment / comment.<col>)
SHOW CATALOGSSame✅
SHOW SCHEMASSame✅
SHOW TABLESSame✅
SHOW COLUMNS FROMSHOW COLUMNS FROM✅New handle_show_columns handler translates Trino’s SHOW COLUMNS FROM ns.t into a query against information_schema.columns. Returns (column_name, data_type, is_nullable), the subset dbt and BI clients use for schema inspection
SHOW CREATE TABLESame✅Reconstructs DDL from information_schema
SHOW CREATE SCHEMASHOW CREATE SCHEMA <name>✅Classifier detects the form (sqlparser cannot parse it) and handle_show_create_schema reconstructs the namespace DDL as a single Create Schema column with optional WITH ( location = '...' ). Missing schema errors like Trino
SHOW FUNCTIONSSHOW FUNCTIONS✅handle_show_functions lists scalar/aggregate/window functions in Trino’s six-column shape (Function Name, Return Type, Argument Types, Function Type, Deterministic, Description). Row content differs from Trino by design (different function sets)
SHOW STATS FORSame✅Returns row_count, data_file_count, total_size from snapshot summary
SHOW ROLES / SHOW CURRENT ROLES / SHOW ROLE GRANTSReturns the caller’s roles✅SQE derives roles from the OIDC token (no global role registry), so all three return the caller’s own roles under a Role column. Lets JDBC clients that probe roles on connect proceed
SET SESSION iceberg.compression_codecApplied to writes✅The per-session codec now reaches the Parquet writer (session value wins over catalog.parquet_compression); previously echoed to the client but ignored
SET TIME ZONE '<tz>'Accepted no-op✅JDBC clients emit this on connect. Accepted as a no-op (FINISHED, no rows); SQE has no per-session zone, so the engine default is kept. Other unsupported SET forms still error
TABLE <name>SELECT * FROM <name>✅Parse-gated tokenizer rewrite (rewrite_bare_table) turns the leading TABLE keyword into SELECT * FROM, preserving trailing ORDER BY / LIMIT / OFFSET. No-op for CREATE/DROP/SHOW CREATE TABLE
ANALYZE <table>Accepted no-op✅Accepted as a statistics no-op so client tooling that issues ANALYZE on connect proceeds. SQE does not persist table stats
EXPLAINSame✅DataFusion explain
EXPLAIN ANALYZEEXPLAIN ANALYZE✅Routed through parse_and_classify -> Statement::Explain { analyze: true } -> explain_handler.analyze() since Phase 2; the previous “different keyword” caveat was a stale doc claim. EXPLAIN FULL is an SQE-specific extension on top
USE catalog.schemaSame✅Parsed and accepted (session-level, sets default catalog/schema)
PREPARE / EXECUTEPartial⚠️DataFusion has infrastructure, SQL integration incomplete
CALL procedure(...)Same (system.* only)✅Iceberg maintenance procedures are wired: CALL system.expire_snapshots(...), CALL system.remove_orphan_files(...), CALL system.rewrite_data_files(...), CALL system.rewrite_manifests(...). User-defined stored procedures return an informative NotImplemented (“SQE does not have stored procedures”) rather than a parse error
GRANT / REVOKEPlanned (Plan C)🔧SQE-specific grant system

Identifier case. Unquoted identifiers fold to lowercase, matching Trino: CREATE TABLE t (testInteger int) stores the column as testinteger, and SELECT testInteger resolves it. Double-quoted identifiers preserve case ("testInteger" stays testInteger and must be quoted to reference). This applies to CREATE TABLE columns, ALTER TABLE ADD/DROP/RENAME/ALTER COLUMN, and PARTITIONED BY refs. Caveat: a pre-existing mixed-case column (e.g. a table created by another engine) must be referenced with quotes, exactly as in Trino.

Type System

Trino TypeSQE/Arrow TypeStatusNotes
BOOLEANBoolean✅
TINYINTInt8✅
SMALLINTInt16✅
INTEGERInt32✅
BIGINTInt64✅
REALFloat32✅
DOUBLEFloat64✅
DECIMAL(p, s)Decimal128(p, s)✅Up to 38 digits
VARCHAR / VARCHAR(n)Utf8 / Utf8View✅Length limit not enforced
CHAR(n)Utf8✅Mapped to Utf8; treated as VARCHAR. No fixed-length space-padding (matches Postgres / Snowflake’s CHAR-as-VARCHAR behaviour). Trino itself recommends VARCHAR for new code
VARBINARYBinary✅
DATEDate32✅
TIME / TIME(p)Time64(Microsecond)✅Iceberg’s time primitive is microsecond-only; precisions 0..=6 collapse to Time64(Microsecond). localtime() returns Time64. hour() / minute() / second() work on TIME columns; year() / month() / day() raise a clear plan error per Trino spec
TIME WITH TIME ZONE—❌No Arrow equivalent. CREATE TABLE rejects with NotImplemented pointing at TIMESTAMP WITH TIME ZONE
TIMESTAMPTimestamp(Microsecond, None)✅
TIMESTAMP WITH TIME ZONETimestamp(Microsecond, Some(tz))✅
INTERVAL YEAR TO MONTHInterval(YearMonth)✅
INTERVAL DAY TO SECONDInterval(DayTime)✅
ARRAY(T)List(T)✅
MAP(K, V)Map(K, V)✅
ROW(fields...)Struct(fields...)✅Single-level and nested/parameterized ROW casts. CAST(row(...) AS row(a int, b row(x int))) is rewritten pre-parse to nested named_struct(...) (rewrite_nested_row_cast), which plans and serializes over the wire as a ROW. Full nested struct-value wire parity vs Trino’s [1,[10]] array shape is per the rewrite’s design, not stack-verified here
JSONVariant (V3) / Utf8 (V2)✅CREATE TABLE t(payload JSON) stores Iceberg VARIANT on a V3 table (the default) and Utf8 on a V2 table; both read back as JSON text. `CAST(json_col AS BIGINT
UUIDUtf8✅CREATE TABLE t(id UUID) aliases UUID to Utf8 in sql_type_to_arrow. Equality, regex, and CAST(... AS UUID) work via the string form. No native UUID logical type (Arrow has none); UUIDv4 generation needs a UDF if required
IPADDRESSVARCHAR⚠️Stored as VARCHAR, no IP-specific functions (subnet containment, etc.)
HyperLogLog—❌Trino-specific sketch type
TDigest—❌Trino-specific sketch type
SetDigest—❌Trino-specific sketch type

Type coercion: DataFusion handles implicit coercion for numeric types (INT → BIGINT → DOUBLE) and string types. Trino has additional coercion rules for JSON, TIME, and sketch types that are not applicable in SQE.

Iceberg-Specific SQL

FeatureSQE SupportTrino SupportStatusNotes
Partition pruning✅✅✅DataFusion optimizer pass
Hidden partitioning✅✅✅Via Iceberg transforms
Schema evolution✅✅✅ADD/DROP/RENAME COLUMN
Type widening✅✅✅INT→BIGINT, FLOAT→DOUBLE
Time travel: FOR VERSION AS OFFOR SYSTEM_TIME AS OF✅✅Pre-processes AST, resolves snapshot_id via metadata
Time travel: FOR TIMESTAMP AS OFSame mechanism✅✅Timestamp resolved to nearest snapshot
$snapshots metadata table"ns.t$snapshots" (Trino) or table_snapshots('ns', 't') (TVF)✅✅sqe-sql AST rewriter translates "ns.t$snapshots" to table_snapshots('ns', 't') before DataFusion sees it. Both spellings work; dbt-trino macros that hard-code $snapshots resolve transparently
$manifests metadata table"ns.t$manifests" or table_manifests('ns', 't')✅✅Same rewriter as $snapshots
$history metadata table"ns.t$history" or table_history('ns', 't')✅✅Same rewriter
$partitions metadata table"ns.t$partitions" or table_partitions('ns', 't')✅✅Same rewriter
$files metadata table"ns.t$files" or table_files('ns', 't')✅✅Same rewriter
$refs metadata table"ns.t$refs" or table_refs('ns', 't')✅✅Same rewriter
Partition evolution✅✅✅Via ALTER TABLE
Sort order—✅❌
Write distribution mode—✅❌
ORC file format—✅❌Parquet only
Copy-on-Write (CoW)✅✅✅DELETE/UPDATE/MERGE
Merge-on-Read (MoR) reads✅✅✅Position deletes, equality deletes, and V3 deletion vectors all readable (RW fork has full read support)
Merge-on-Read (MoR) writes✅ via write.delete.mode='merge-on-read'✅✅handle_delete_dispatch routes by table property: position deletes (V2) or one deletion vector per data file (V3) when no PK declared, equality deletes with PK. Position deletes commit via FastAppendAction; equality deletes and deletion vectors via RowDeltaAction. CoW remains the default

Operational Comparison

Run scripts/operational-comparison.sh to regenerate these numbers.

MetricSQETrinoNotes
LanguageRustJava 23
Build time (release)~3–5 min~10–15 mincargo build --release vs mvn package -DskipTests
Build dependencies~800 crates~2000+ Maven depsCargo.lock vs pom.xml tree
Coordinator binary~50 MBN/A (JVM)Single static binary vs JVM + JARs
Docker image~80 MB~700 MBAlpine + binary vs JVM + plugins
Cold start<1s10–30sFirst query latency from container start
Idle memory (RSS)~20 MB~300 MBAfter startup, no queries
Loaded memory~200–500 MB~1–4 GBDuring TPC-H SF1 full suite
Config surface~30 TOML knobs~200+ propertiessqe.toml vs config.properties + jvm.config + catalog files
DeploymentSingle binary + TOMLJVM + plugins + properties
Hot reload❌❌Neither supports hot config reload
PluginsCompile-time featuresRuntime JARsConnectors are Cargo features vs JAR plugins

Key advantages:

  • 10x smaller footprint — single binary, minimal memory
  • 10x faster cold start — no JVM warmup, no class loading
  • Simpler deployment — one binary, one TOML file
  • Fewer moving parts — no plugin system, no JVM tuning

Trino advantages:

  • Ecosystem — 100+ connectors, mature JDBC drivers
  • Runtime extensibility — add connectors without recompilation
  • Community — larger community, more Stack Overflow answers