Celigo SQL is the SQL dialect you use to query Celigo Storage files. It's a curated subset of standard SQL, PostgreSQL-compatible where syntax is otherwise ambiguous, with the exceptions documented in this article. This reference covers the supported statements, clauses, and functions, the behaviors that differ from PostgreSQL, and what Celigo rejects before a query runs.
For an introduction to querying, see About querying Celigo Storage files. To run a query end to end, see Query files in Celigo Storage.
Supported vs. unsupported SQL
Celigo SQL is a curated surface, not a passthrough to the underlying engine. Three tiers apply:
- Documented core — everything in this article. Supported and tested.
- Undocumented syntax — some SQL that isn't listed here executes anyway. It isn't supported or tested against Celigo's regression suite, and it may stop working after a platform upgrade. Don't build on it.
- Rejected — statements, functions, and references that fail validation before the query runs. See What Celigo rejects.
Statements
A request carries exactly one statement. A second statement is rejected with STORAGE_QUERY_MULTIPLE_STATEMENTS. A single trailing semicolon is fine.
| Statement | Description |
|---|---|
SELECT |
Retrieve and transform rows. Includes the VALUES and TABLE shorthands. |
WITH |
Define common table expressions, including WITH RECURSIVE. |
DESCRIBE <query> |
Return the column names and inferred types a query would produce, one row per column. Runs as a normal query job — there is no separate schema endpoint. |
SUMMARIZE <query> |
Return per-column statistics for the rows a query would produce: minimum, maximum, approximate distinct count, average, standard deviation, quartiles, count, and null percentage. Runs as a normal query job. |
No other statement type executes. Data definition (CREATE, ALTER, DROP), data manipulation (INSERT, UPDATE, DELETE), COPY in either direction, SET, PRAGMA, INSTALL, LOAD, ATTACH, EXPORT, CALL, EXPLAIN, SHOW forms that list engine state, and every other statement family are rejected with STORAGE_QUERY_NOT_SELECT. This is one of the two enforcement points that make the query API read-only; the other is the worker's read-only credentials.
Clauses and expressions
| Feature | Supported forms |
|---|---|
| Projection |
SELECT, SELECT DISTINCT, column aliases |
| Filtering |
WHERE with comparison operators, IN, BETWEEN, LIKE, IS NULL
|
| Grouping |
GROUP BY, HAVING
|
| Ordering |
ORDER BY with ASC and DESC, NULLS FIRST and NULLS LAST
|
| Paging |
LIMIT, OFFSET
|
| Joins |
INNER JOIN, LEFT JOIN, RIGHT JOIN, FULL OUTER JOIN, CROSS JOIN
|
| Set operations |
UNION, UNION ALL, INTERSECT, EXCEPT
|
| Subqueries | Scalar, IN, EXISTS, and derived tables |
| Common table expressions |
WITH, including RECURSIVE
|
| Window functions |
OVER (PARTITION BY … ORDER BY …), row_number, rank, lag, lead, running and moving aggregates |
| Conditional logic | CASE … WHEN … THEN … ELSE … END |
| Inline tables | FROM (VALUES (…), (…)) AS alias(col1, col2) |
| Aggregates |
COUNT, SUM, AVG, MIN, MAX, including COUNT(DISTINCT …) and the FILTER clause |
| Row generators |
generate_series and range with whole-number literal arguments |
Inline mapping tables
An inline VALUES table gives you a lookup table without a file:
SELECT o.region_code, m.region_name, SUM(o.total) AS revenue
FROM '/staging/orders/' AS o
JOIN (VALUES ('NA', 'North America'), ('EU', 'Europe')) AS m(code, region_name)
ON o.region_code = m.code
GROUP BY o.region_code, m.region_nameWindow functions
Window functions attach an aggregate computed over the whole dataset to every row, without collapsing rows:
SELECT order_id, customer_id, total,
SUM(total) OVER (PARTITION BY customer_id) AS customer_lifetime_total,
row_number() OVER (PARTITION BY customer_id ORDER BY order_date DESC) AS recency_rank
FROM '/staging/orders/'Row generators
generate_series and range produce rows without a file, for example to build a date spine. Three rules apply, each enforced before the query runs:
- Every argument must be a whole-number literal, so that Celigo can check the row count in advance. An expression, a column, or a parameter fails with
STORAGE_QUERY_GENERATOR_ARGUMENT_NOT_LITERAL. - Celigo multiplies the row counts of every
generate_seriesandrangecall in the statement, including calls in separateUNION ALLbranches, and the product must be at most 1,000,000. Exceeding that fails withSTORAGE_QUERY_GENERATOR_TOO_LARGE. - A statement that generates rows, through a generator, a recursive common table expression, or
unnestof a literal list, must also read at least one storage file. A generator with no file reference fails withSTORAGE_QUERY_NO_STORAGE_REFERENCE. A statement of constant expressions alone, such asSELECT 1, is allowed.
Nested data
JSON files produce nested structures, and Celigo SQL reads them directly. Column types are inferred automatically.
| Access | Syntax | Example |
|---|---|---|
| Struct field | Dot notation | customer.region |
| List element | Bracket indexing | items[1].sku |
| Explode a list into rows | UNNEST |
UNNEST(items) |
SELECT customer.region, item.sku, item.quantity
FROM '/staging/orders.json',
UNNEST(items) AS t(item)
WHERE customer.region = 'EU'In results, STRUCT values arrive as JSON objects and LIST values as arrays. A key whose values have different types across records is typed as JSON, arrives as a string containing JSON text, and is reported as a VARCHAR column. Nesting is typed at any depth unless you set maximum_depth on the JSON reader, in which case nesting below that depth is typed as JSON too. To work with a JSON-typed value as data, extract the fields you need with json_extract and cast them. See Celigo storage query read options and format behavior.
Type conversion
TRY_CAST returns NULL instead of failing when a value can't be converted. Use it on data you don't control:
SELECT order_id, TRY_CAST(total AS DECIMAL(10,2)) AS total_amount FROM '/staging/orders/' WHERE TRY_CAST(total AS DECIMAL(10,2)) IS NOT NULL
Behaviors that differ from PostgreSQL
These are the deviations to know before you port a query.
| Behavior | Celigo SQL | Notes |
|---|---|---|
| Division |
7 / 2 returns 3.5
|
/ always produces a floating-point result. Use // for integer division: 7 // 2 returns 3. This is the largest deviation from PostgreSQL. |
| Division by zero |
x / 0 and x // 0 don't raise an error |
They produce non-finite values, which arrive as null in results, because infinity and NaN aren't valid JSON. |
| 64-bit integers and decimals | Arrive as strings | 64-bit integers, unsigned 64-bit integers, 128-bit integers, and decimals arrive as strings so that no digits are lost, and every whole-number column inferred from a CSV or JSON file is typed as a 64-bit integer. COUNT, SUM over integers, and // follow the same rule; AVG and / return numbers. Declare the column's type in the reader, or cast the expression, to receive a number. |
| Mixed-type JSON field | Typed as JSON, arrives as a string of JSON text |
A key missing from some objects reads as NULL. |
| Multi-file schema drift | Columns are combined by name by default | Every column across all files in a queried folder appears; a file missing a column produces NULL for it. Set union_by_name = false on the reader to keep only the first file's columns. See Celigo storage query read options and format behavior. |
| Empty file | Returns zero rows | A zero-byte file queried directly completes with zero rows and one placeholder column. Inside a folder, it adds no rows. |
random(), now()
|
Allowed, nondeterministic | Each run evaluates them again and may produce different values. Avoid them where you need repeatable results. |
What Celigo rejects
Validation runs before execution. A rejected query returns a specific error naming what was rejected, and no query runs. Four checks apply:
-
Statement type — anything outside the
SELECTfamily, and any request carrying more than one statement. -
Functions — only allowlisted functions may appear. The allowlist is the engine's own function library minus the functions that report engine or host state, such as
version(),current_setting(), andgetenv(). A function outside the allowlist fails withSTORAGE_QUERY_FUNCTION_NOT_ALLOWED, and the error names the function. A misspelled function name produces the same error. -
Table references — every base-table reference must resolve to a common table expression defined in the same statement, or to a Celigo Storage file or folder path. Schema-qualified and catalog-qualified references such as
information_schema.tablesare rejected, as are wildcard patterns —*and?aren't valid path characters. - Statement size — the SQL text is capped at 16 KB.
Rejected function families
| Family | Why it's rejected |
|---|---|
| Dynamic SQL | Runs SQL assembled at runtime, which would bypass validation entirely. |
| Arbitrary filesystem and network readers | Read bytes outside Celigo Storage, list the filesystem, or reach the network — for example, glob, read_text, and any reader called with a raw s3:// or https:// URL. |
| Engine and secret introspection | Expose stored credentials, engine configuration, and internals — for example, version(), current_setting(), and getenv(). |
| Engine state mutation | Change engine state from inside a query. |
Allowed table functions
The only table functions allowed are the file readers (read_csv, read_json, read_ndjson, read_parquet, and their _auto variants), unnest, generate_series, and range. Every other table function is rejected with STORAGE_QUERY_TABLE_FUNCTION_NOT_ALLOWED, including glob, read_text, and query. Each reader's first argument must be a literal path or a literal list of paths, and it's validated exactly like a normal file reference, so no reader can read outside your permitted folders.