Skip to content

import_parquet segfaults on a partitioned columnar table: the columnar check is absent, so #1261 does not reach it #1263

Description

@OffgridwithJD

What

pgcolumnar.import_parquet() on a partitioned table with relam = pgcolumnar segfaults the backend and restarts the cluster. This is not
#1259's class and #1261 does not fix it: import_parquet never asks whether its
target is columnar at all.

  LOG:  server process (PID 3328813) was terminated by signal 11: Segmentation fault
  DETAIL:  Failed process was running: SELECT pgcolumnar.import_parquet('pp'::regclass, '/tmp/v2.parquet');
  LOG:  all server processes terminated; reinitializing
  LOG:  database system was not properly shut down; automatic recovery in progress

Signal 11, not an assert — so a production build is affected

#1259's family is an Assert that a non-assert build compiles out, leaving a
wrong answer. This one is a segfault, and I measured both:

  /usr/local/pg17a   PostgreSQL 17.6   --enable-cassert       signal 11
  /usr/local/pg18n   PostgreSQL 18     cassert flags: 0       signal 11

Neither run produced a failed Assert line — traps before = 0, traps after = 0. The crash is in ordinary code.

Reproduction, with both controls

  PREMISE file bytes=[757]
  PREMISE parent relkind=[p]  relfilenode=[0]
  CONTROL alive before = [up]

  import_parquet(PARENT, valid file)   rc=2   cluster DOWN, signal 11

  LEAF control       import_parquet('pp1', same file)   rc=0   imported 50 rows
  SIBLING control    import_arrow('pp',  valid file)    rc=1
                     ERROR: relation "pp" is not a columnar table

The leaf control says the call works and the file is good. The sibling
control is the sharper one: import_arrow on the same parent with the same
shape refuses cleanly, so the refusal is something this code could have done and
does not.

  CREATE TABLE src (a int, b text) USING pgcolumnar;
  INSERT INTO src SELECT g,'x'||g FROM generate_series(1,50) g;
  CREATE TABLE pp (a int, b text) PARTITION BY RANGE (a) USING pgcolumnar;
  CREATE TABLE pp1 PARTITION OF pp FOR VALUES FROM (0) TO (100);
  SELECT pgcolumnar.export_parquet('src'::regclass, '/tmp/v.parquet');
  SELECT pgcolumnar.import_parquet('pp'::regclass, '/tmp/v.parquet');   -- SIGSEGV

Cause: the check is absent, not wrong

  grep -c PgColumnarIsColumnarRelation src/columnar_parquet_reader.c   ->  0

pgcolumnar_import_parquet checks pg_read_server_files, then ACL_INSERT,
then RLS, then resolves the path, then table_open(relid, RowExclusiveLock) and
proceeds. It never asks whether the relation is columnar, so it is not one of
the 31 call sites
#1261 changed and the predicate fix cannot reach it.

Swept across the tree, it is the only file in that position:

  file                        entrypoints  getarg_oid(0)  predicate uses
  columnar_arrow.c                 2            2              2
  columnar_parallel_copy.c         2            1              1
  columnar_parallel_export.c       1            1              2
  columnar_parquet.c               1            1              1
  columnar_parquet_reader.c        5            1              0     <-
  columnar_projection.c            4            4              5
  columnar_vacuum.c               14           11             10
  columnar_visibilitymap.c         2            2              2

columnar_vacuum.c at 11 against 10 is a count and not a proof, so it is worth
a look rather than a claim.

Who can reach it

Not an ordinary user. import_parquet requires superuser or
pg_read_server_files, plus INSERT on the target:

  if (!has_privs_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES))
      ereport(ERROR, ... "must be superuser or a member of the
                          pg_read_server_files role to read a server file");

So the population is roles a deployment has deliberately granted file-reading to
— narrower than #1259's "grant USAGE on the schema", and still a role that is
not superuser taking the cluster down.

Found how

Following up my own caveat on #1259 that survived meant "did not abort" rather
than "correct". The earlier 22-probe recorded import_parquet as surviving —
because the file did not exist, so it failed at the open before reaching the
crash. It only appears when the path resolves, which is why a sweep that reuses
one fixture misses it.

🤖 Generated with Claude Code

https://claude.ai/code/session_01MpajdQbkVJ9ey1XyYHcikP

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions