Summary
When a query against a DuckLake table references a PostgreSQL system column
(ctid, oid, xmin, xmax, cmin, cmax, tableoid), pg_ducklake
routes the query to DuckDB without stripping or substituting the system-column
reference. DuckDB's binder cannot resolve it and returns a raw internal error
that leaks implementation details to the client:
ERROR: (PGDuckDB/CreatePlan) Prepared query returned an error:
Binder Error: Referenced column "ctid" not found in FROM clause!
Candidate bindings: "recorded_at", "unit"
LINE 1: SELECT id, sensor_id, recorded_at, value, unit, ctid
FROM pgducklake.silver.sensor_readings t LIMIT 1001
The same query executes correctly on a regular heap table. A plain SELECT *
(without system columns) works fine on DuckLake tables.
This manifests in any SQL client that appends system columns to auto-generated
queries - DataGrip IDE appends CTID to its table-browser preview query, making
every DuckLake table fail to open in the UI with the error above.
Reproduction
CREATE TABLE silver.sensor_readings (
id bigint,
sensor_id text,
recorded_at timestamptz,
value double precision,
unit text
) USING ducklake;
INSERT INTO silver.sensor_readings VALUES (1, 'temp-01', now(), 21.4, 'celsius');
-- ✅ WORKS - plain SELECT
SELECT * FROM silver.sensor_readings LIMIT 10;
-- ❌ FAILS - system column reference (DataGrip table-browser query pattern)
SELECT t.*, ctid FROM silver.sensor_readings t LIMIT 1001;
-- ERROR: (PGDuckDB/CreatePlan) Prepared query returned an error:
-- Binder Error: Referenced column "ctid" not found in FROM clause!
-- ❌ FAILS - other system columns behave the same way
SELECT *, xmin FROM silver.sensor_readings;
SELECT *, tableoid FROM silver.sensor_readings;
Root cause
ctid and the other system columns are implicit attributes of every PostgreSQL
heap tuple. They are valid SQL in PostgreSQL and are silently expanded by the
parser before the planner sees them.
pg_ducklake intercepts the planned query and rewrites the table reference to a
DuckDB-qualified path (pgducklake.<schema>.<table>). At that point the
system-column references are already present in the query tree. DuckDB has no
concept of PostgreSQL system columns and its binder raises an error when it
cannot resolve them.
The error is returned as a raw DuckDB Binder Error string wrapped in an
XX000 (internal_error) PostgreSQL error, which leaks the internal
pgducklake.<schema>.<table> rewrite path and the DuckDB error class to the
client.
Suggested fix
In the pg_ducklake query interceptor, before handing off the query tree to
DuckDB, scan the target list and quals for Var nodes whose varattno is a
system-column attno (negative values in PostgreSQL's attribute numbering:
SelfItemPointerAttributeNumber for ctid, etc.) and either:
- Option A (NULL substitution): replace each system-column
Var with a
NULL cast to the appropriate type. This allows tools like DataGrip to
function normally - they receive NULL for ctid rather than an error.
- Option B (clean error): raise a proper PostgreSQL
feature_not_supported
error (SQLSTATE 0A000) at the PostgreSQL level before DuckDB is invoked,
with a message such as:
system columns (ctid, xmin, …) are not supported on DuckLake tables
Option A is preferable as it makes DuckLake tables transparent to standard
SQL tooling.
Workaround
Exclude system columns explicitly:
SELECT id, sensor_id, recorded_at, value, unit
FROM silver.sensor_readings
LIMIT 1001;
In DataGrip: Settings → Tools → Database → Query Execution → disable
"Add row identifiers to SELECT statements", or manually open the table with
a custom query rather than double-clicking.
Version: pg_ducklake 1.0.0, PostgreSQL 18.4, DataGrip 2026.1.4
Summary
When a query against a DuckLake table references a PostgreSQL system column
(
ctid,oid,xmin,xmax,cmin,cmax,tableoid), pg_ducklakeroutes the query to DuckDB without stripping or substituting the system-column
reference. DuckDB's binder cannot resolve it and returns a raw internal error
that leaks implementation details to the client:
The same query executes correctly on a regular heap table. A plain
SELECT *(without system columns) works fine on DuckLake tables.
This manifests in any SQL client that appends system columns to auto-generated
queries - DataGrip IDE appends
CTIDto its table-browser preview query, makingevery DuckLake table fail to open in the UI with the error above.
Reproduction
Root cause
ctidand the other system columns are implicit attributes of every PostgreSQLheap tuple. They are valid SQL in PostgreSQL and are silently expanded by the
parser before the planner sees them.
pg_ducklake intercepts the planned query and rewrites the table reference to a
DuckDB-qualified path (
pgducklake.<schema>.<table>). At that point thesystem-column references are already present in the query tree. DuckDB has no
concept of PostgreSQL system columns and its binder raises an error when it
cannot resolve them.
The error is returned as a raw DuckDB
Binder Errorstring wrapped in anXX000(internal_error) PostgreSQL error, which leaks the internalpgducklake.<schema>.<table>rewrite path and the DuckDB error class to theclient.
Suggested fix
In the pg_ducklake query interceptor, before handing off the query tree to
DuckDB, scan the target list and quals for
Varnodes whosevarattnois asystem-column attno (negative values in PostgreSQL's attribute numbering:
SelfItemPointerAttributeNumberfor ctid, etc.) and either:Varwith aNULLcast to the appropriate type. This allows tools like DataGrip tofunction normally - they receive NULL for ctid rather than an error.
feature_not_supportederror (
SQLSTATE 0A000) at the PostgreSQL level before DuckDB is invoked,with a message such as:
system columns (ctid, xmin, …) are not supported on DuckLake tablesOption A is preferable as it makes DuckLake tables transparent to standard
SQL tooling.
Workaround
Exclude system columns explicitly:
In DataGrip: Settings → Tools → Database → Query Execution → disable
"Add row identifiers to SELECT statements", or manually open the table with
a custom query rather than double-clicking.
Version: pg_ducklake 1.0.0, PostgreSQL 18.4, DataGrip 2026.1.4