Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,8 @@ compression-block run iterator (`ColumnarBlockReader`) that exposes a chunk as

### columnar_bloom.c
Per-chunk bloom filters for equality chunk-group skipping. The writer hashes each
non-null value (hashable, non-collatable columns only) and builds a filter per
non-null value (hashable columns whose collation is non-collatable or
deterministic; nondeterministic collations are left unbloomed) and builds a filter per
chunk; the reader probes it for an equality predicate the min/max range could not
rule out, skipping the group when the value is provably absent. Never a false
negative, so results are unaffected.
Expand Down Expand Up @@ -247,7 +248,8 @@ FlatBuffers builder emits the Schema and RecordBatch messages (MetadataVersion
V5); rows are read in physical order via the scalar reader and buffered one
RecordBatch at a time (validity bitmap, then values, with utf8/binary offsets).
No libarrow dependency. Supported types are int2/int4/int8, float4/float8, bool,
text/varchar, and bytea; other types are rejected. Little-endian hosts only.
text/varchar, bytea, date/time/timestamp/timestamptz, uuid, numeric, and
json/jsonb; other types are rejected. Little-endian hosts only.

### columnar_parquet.c
Parquet export (`pgcolumnar.export_parquet`, gap 27). A self-contained Thrift
Expand Down
5 changes: 0 additions & 5 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,6 @@ disk. It never changes the values that a table returns.
| `pgcolumnar.enable_index_only_scan` | boolean | `on` | Allow index-only scans on columnar tables, served by the columnar visibility-map fork. Set to `off` to force a plain index scan. |
| `pgcolumnar.enable_projection_scan` | boolean | `on` | Let the planner scan a covering projection instead of the base table when one serves the query better. |

### Column cache

| Setting | Type | Default | Description |
| --- | --- | --- | --- |

### Maintenance and disk reclaim

| Setting | Type | Default | Description |
Expand Down
33 changes: 18 additions & 15 deletions src/columnar.h
Original file line number Diff line number Diff line change
Expand Up @@ -396,8 +396,8 @@ typedef struct ColumnarRowRange
uint64 rowCount;
} ColumnarRowRange;

/* all-visible chunk-group row ranges: stripe committed past the horizon and no
* deletes (committed or in-progress). Returns a List of ColumnarRowRange *. */
/* row groups every one of whose rows is deleted as-of oldestXmin. Returns a
* List of palloc'd uint64 group numbers. */
extern List *ColumnarComputeFullyDeletedGroups(uint64 storageId,
TransactionId oldestXmin);
extern void ColumnarRetireGroup(uint64 storageId, uint64 groupNumber);
Expand All @@ -409,6 +409,8 @@ extern bool ColumnarTrailingFreeSpaceSafe(uint64 storageId, uint64 liveEnd,
TransactionId oldestXmin);
extern void ColumnarDeleteFreeSpaceAtOrAbove(uint64 storageId, uint64 liveEnd);
extern void ColumnarReconcileFreeList(Relation dataRel);
/* all-visible chunk-group row ranges: stripe committed past the horizon and no
* deletes (committed or in-progress). Returns a List of ColumnarRowRange *. */
extern List *ColumnarComputeAllVisibleGroups(uint64 storageId,
TransactionId oldestXmin);

Expand Down Expand Up @@ -564,11 +566,6 @@ extern bool ColumnarReadNextRow(ColumnarReadState *readState,
extern void ColumnarRescanRead(ColumnarReadState *readState);
extern void ColumnarEndRead(ColumnarReadState *readState);

/*
* Parallel scan (gap 23): point the read state at a shared atomic that hands out
* stripe indices, so several workers scanning the same relation each claim
* distinct stripes. Set by the custom scan's DSM init callbacks.
*/
/*
* Restrict a scan to a set of row groups (issue #149). Groups outside the set
* are skipped without their bytes being read. Must be called before the first
Expand All @@ -586,6 +583,11 @@ extern int64 ColumnarWriteParquetFile(Relation rel, Snapshot snapshot,
int nRestrictGroups);
extern void ColumnarParquetCheckExportable(Relation rel);

/*
* Parallel scan (gap 23): point the read state at a shared atomic that hands out
* stripe indices, so several workers scanning the same relation each claim
* distinct stripes. Set by the custom scan's DSM init callbacks.
*/
extern void ColumnarReadSetParallelCounter(ColumnarReadState *readState,
pg_atomic_uint32 *counter);

Expand All @@ -599,12 +601,6 @@ extern void ColumnarReadStats(ColumnarReadState *readState,
uint64 *groupsTotal);
extern uint64 ColumnarVectorsSkipped(ColumnarReadState *readState);

/*
* Fetch a single row by its 1-based row number (spec 6), for the table AM's
* fetch-by-tid callback used by UPDATE. Fills values/nulls (by-reference values
* are allocated in the current memory context) and returns true when the row
* exists and is not marked deleted in the delete vector.
*/
/* cached base-liveness for a projection scan (gap 26): build once per scan,
* probe per row with a binary search instead of a per-row catalog scan */
typedef struct ColumnarLivenessCache ColumnarLivenessCache;
Expand All @@ -613,6 +609,12 @@ extern ColumnarLivenessCache *ColumnarBuildLivenessCache(Relation rel,
extern bool ColumnarLivenessCacheIsLive(ColumnarLivenessCache *cache,
uint64 rowNumber);
extern void ColumnarFreeLivenessCache(ColumnarLivenessCache *cache);
/*
* Fetch a single row by its 1-based row number (spec 6), for the table AM's
* fetch-by-tid callback used by UPDATE. Fills values/nulls (by-reference values
* are allocated in the current memory context) and returns true when the row
* exists and is not marked deleted in the delete vector.
*/
extern bool ColumnarReadRowByNumber(Relation rel, Snapshot snapshot,
uint64 rowNumber, Datum *values, bool *nulls);

Expand Down Expand Up @@ -818,8 +820,9 @@ extern void ColumnarVectorInit(void);
* padding, so a four-byte varlena header starts wherever the previous value
* ended. VARSIZE_ANY reads that header by casting to varattrib_4b and loading a
* uint32, which is undefined behaviour on an unaligned address: it happens to
* work on x86_64 and is a SIGBUS on a strict-alignment target, which
* docs/limitations.md promises to support.
* work on x86_64 and is a SIGBUS on a strict-alignment target; unaligned reads
* are covered by the sanitizer gate, though non-x86_64 architectures are
* untested (docs/limitations.md).
*
* This is not a crafted-input problem. An ordinary INSERT of low-cardinality
* text reports it six times under UBSAN, because encode_dict walks exactly such
Expand Down
18 changes: 10 additions & 8 deletions src/columnar_arrow.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,19 @@
* columnar_arrow.c
* Arrow IPC stream export for pgColumnar (gap 27, piece 1).
*
* columnar.export_arrow(rel regclass, path text) writes a columnar table
* pgcolumnar.export_arrow(rel regclass, path text) writes a columnar table
* to an Apache Arrow IPC *stream* file: a Schema message, one RecordBatch
* message per ARROW_BATCH_ROWS rows, and an end-of-stream marker. The
* writer is self-contained -- it hand-builds the FlatBuffers metadata and
* the record-batch body buffers, so there is no libarrow/libparquet build
* or run-time dependency. Rows are read in physical order via the scalar
* reader; deleted rows are skipped by the reader.
*
* First slice type mapping: int2/4/8, float4/8, bool, text/varchar (Utf8),
* bytea (Binary). Any other column type is rejected. Little-endian hosts
* only (the Arrow body mirrors native scalar bytes).
* Type mapping: int2/4/8, float4/8, bool, text/varchar (Utf8), bytea
* (Binary), date/time/timestamp/timestamptz, uuid, numeric, and json/jsonb;
* 1-D arrays (List) and composites (Struct) are also exported. A scalar type
* with no mapping is rejected. Little-endian hosts only (the Arrow body
* mirrors native scalar bytes).
*
* Independent MIT implementation built from the Apache Arrow columnar format
* and IPC specifications (Schema.fbs, Message.fbs, encapsulated message format)
Expand Down Expand Up @@ -939,7 +941,7 @@ write_record_batch(FILE *f, ArrowCol *cols, int ncols, int64 nrows)

/*
* columnar_export_arrow
* SQL: columnar.export_arrow(rel regclass, path text) -> bigint.
* SQL: pgcolumnar.export_arrow(rel regclass, path text) -> bigint.
* Write a columnar table to an Arrow IPC stream file; returns the number
* of rows written.
*/
Expand Down Expand Up @@ -1139,9 +1141,9 @@ columnar_export_arrow(PG_FUNCTION_ARGS)
}

/* =========================================================================
* Arrow IPC stream import: columnar.import_arrow(rel regclass, path text).
* Arrow IPC stream import: pgcolumnar.import_arrow(rel regclass, path text).
*
* Reads an Arrow IPC *stream* file (as columnar.export_arrow writes, and as
* Reads an Arrow IPC *stream* file (as pgcolumnar.export_arrow writes, and as
* pyarrow writes for non-dictionary arrays) and inserts its rows into an
* existing columnar table whose column types match the file's schema, using
* the reverse of the export type mapping. Uncompressed bodies only; a
Expand Down Expand Up @@ -1725,7 +1727,7 @@ imp_check_bounds(ImpNode *n, const uint8 *body, const int64 *bufOff,

/*
* columnar_import_arrow
* SQL: columnar.import_arrow(rel regclass, path text) -> bigint.
* SQL: pgcolumnar.import_arrow(rel regclass, path text) -> bigint.
* Insert the rows of an Arrow IPC stream file into a columnar table;
* returns the number of rows inserted.
*/
Expand Down
11 changes: 5 additions & 6 deletions src/columnar_delete_vector.c
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,8 @@ delete_vector_get_buffer(Relation rel, uint64 storageId)

/*
* delete_vector_find_row_group
* Native (PGCN v1) analog of delete_vector_find_stripe: return the row group that
* contains rowNumber, rebuilding the cache from the catalog on a miss.
* Native (PGCN v1): return the row group that contains rowNumber,
* rebuilding the cache from the catalog on a miss.
*/
static NativeRowGroupMetadata *
delete_vector_find_row_group(DeleteVectorBuffer *buf, uint64 rowNumber)
Expand Down Expand Up @@ -226,10 +226,9 @@ delete_vector_get_chunk(DeleteVectorBuffer *buf, uint64 stripeId, int chunkId,
* ColumnarMarkRowDeleted
* Record that the row with the given 1-based row number is deleted, by
* setting its bit in the in-memory delete buffer for its chunk group.
* Chunk-group boundaries are computed arithmetically: every chunk group
* but the last in a stripe holds exactly chunkRowCount rows (the writer
* fills a group before starting the next), so no chunk_group catalog read
* is needed here.
* The mark targets the whole enclosing row group as one bitmap (chunk id
* 0), sized to the row group's rowCount, found via
* delete_vector_find_row_group using its firstRowNumber and rowCount.
*/
void
ColumnarMarkRowDeleted(Relation rel, uint64 rowNumber)
Expand Down
4 changes: 2 additions & 2 deletions src/columnar_encoding.c
Original file line number Diff line number Diff line change
Expand Up @@ -2205,11 +2205,11 @@ ColumnarEncodeChunk(const char *raw, uint32 rawLen, Form_pg_attribute att,

/*
* Choose the candidates to apply. With sampling on and a chunk big enough to
* sample, each candidate is measured on a strided sample and only the best
* sample, each candidate is measured on a windowed sample and only the best
* two are applied to the whole chunk; two rather than one because the sample
* ranks closely-matched candidates unreliably, and the second application is
* cheap next to the three it replaces. With sampling off, or a chunk too
* small for a stride, every candidate is applied as before.
* small to sample, every candidate is applied as before.
*/
if (w > 0 && columnar_encoding_sample_rows >= ENCODE_SAMPLE_MIN)
{
Expand Down
13 changes: 7 additions & 6 deletions src/columnar_metadata.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
*
* columnar_metadata.c
* Access to the "columnar" metadata catalog tables and the storage-id
* sequence (spec 7). Metadata are ordinary heap tables keyed by storage
* id; we read and write them with direct catalog access so we do not
* depend on SPI reentrancy.
* sequence (spec 7). Most metadata are ordinary heap tables keyed by
* storage id (the options and projection_declaration tables are keyed by
* relation OID instead); we read and write them with direct catalog access
* so we do not depend on SPI reentrancy.
*
*-------------------------------------------------------------------------
*/
Expand Down Expand Up @@ -161,7 +162,7 @@ columnar_index_oid(const char *name)

/*
* ColumnarNextStorageId
* Draw the next value from columnar.storageid_seq (spec 3, 7.6).
* Draw the next value from pgcolumnar.storageid_seq (spec 3, 7.6).
*/
uint64
ColumnarNextStorageId(void)
Expand All @@ -173,7 +174,7 @@ ColumnarNextStorageId(void)
if (!OidIsValid(seqOid))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("columnar.storageid_seq does not exist")));
errmsg("pgcolumnar.storageid_seq does not exist")));

value = nextval_internal(seqOid, false);
return (uint64) value;
Expand Down Expand Up @@ -488,7 +489,7 @@ insert_free_space_row(Relation rel, TupleDesc td, uint64 storageId,
* to a whole page), so free ranges tile the file page-aligned and a split
* remnant or a coalesced union stays page-aligned.
*
* When columnar.reclaim_coalesce is on, the new range is merged with any
* When pgcolumnar.reclaim_coalesce is on, the new range is merged with any
* immediately adjacent existing free range (left neighbor ending at this
* offset, or right neighbor starting at this range's end) before insertion,
* so a later request larger than either neighbor can still be satisfied from
Expand Down
4 changes: 4 additions & 0 deletions src/columnar_parallel_copy.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
* PREPAREDs them all, so a failure in any range leaves no partial load. Distinct
* partitions means distinct storage, which is what makes this parallel (no
* shared per-storage write lock) and 2PC-safe (no deadlock).
* - the single (non-partitioned) columnar table shape: N loaders write ONE shared
* storage concurrently. Here parallelism does not come from distinct storage;
* it comes from the coordinator pre-creating and committing the storage row so
* the loaders skip its creation lock, each writing via columnar_bulk_parallel_writer.
* - a standalone byte splitter (columnar_file_split_offsets) exposed to SQL: N+1
* line-aligned offsets, a diagnostic the parallel load itself no longer calls.
* Text format only for now, numeric/date-time partition keys only (their text form
Expand Down
4 changes: 2 additions & 2 deletions src/columnar_parquet.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
* columnar_parquet.c
* Parquet file export for pgColumnar (gap 27, piece 2).
*
* columnar.export_parquet(rel regclass, path text) writes a columnar table
* pgcolumnar.export_parquet(rel regclass, path text) writes a columnar table
* to a Parquet file. The writer is self-contained -- it emits the Thrift
* compact-protocol metadata and PLAIN-encoded, UNCOMPRESSED data pages
* directly -- so there is no libparquet build or run-time dependency. Rows
* are read in physical order via the scalar reader; one row group is
* emitted per PARQUET_ROWGROUP_ROWS rows, with one DATA_PAGE per column.
*
* First-slice type mapping (matches columnar.export_arrow): int2/int4 ->
* First-slice type mapping (matches pgcolumnar.export_arrow): int2/int4 ->
* INT32 (int2 tagged INT_16), int8 -> INT64, float4 -> FLOAT, float8 ->
* DOUBLE, bool -> BOOLEAN, text/varchar -> BYTE_ARRAY (UTF8), bytea ->
* BYTE_ARRAY. All columns are OPTIONAL; nulls are carried in definition
Expand Down
4 changes: 2 additions & 2 deletions src/columnar_parquet_codec.c
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,8 @@ snappy_raw_uncompress(const uint8 *in, size_t inlen, StringInfo out)
*
* On success *out / *outlen point at the decompressed bytes: either straight into
* `src` (uncompressed), or into `scratch` (any real codec). `usize` is the
* uncompressed size from the page header, which zstd and lz4_raw require up front
* (they do not self-describe the output length the way Snappy and gzip do); pass
* uncompressed size from the page header, which zstd, lz4_raw and gzip require up
* front (only Snappy self-describes its output length, via a leading varint); pass
* the value portion's uncompressed size for a v2 data page, where the levels are
* stored uncompressed ahead of the compressed values.
*
Expand Down
28 changes: 10 additions & 18 deletions src/columnar_parquet_reader.c
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
/*-------------------------------------------------------------------------
*
* columnar_parquet_reader.c
* Parquet file import: columnar.import_parquet(rel regclass, path text).
* Parquet file import: pgcolumnar.import_parquet(rel regclass, path text).
*
* A self-contained Parquet reader with no libparquet/libarrow dependency. It
* parses the Thrift compact-protocol file metadata, decompresses Snappy (and
* handles uncompressed) data pages, and decodes PLAIN and dictionary
* (RLE_DICTIONARY / PLAIN_DICTIONARY) encodings from both DATA_PAGE (v1) and
* DATA_PAGE_V2 pages -- the combination pyarrow writes by default. Rows are
* inserted into an existing target table (its tuple descriptor defines the
* expected columns and types), mirroring columnar.import_arrow.
* expected columns and types), mirroring pgcolumnar.import_arrow.
*
* Independent MIT implementation built from the Apache Parquet format and Thrift
* compact-protocol specifications, the Snappy format description, and the public
Expand Down Expand Up @@ -2330,14 +2330,6 @@ build_imp_targets(TupleDesc tupdesc, PqFile *pf,
return tops;
}

/*
* Read an entire server-side Parquet file into a palloc'd buffer and parse its
* footer metadata into *pf. On success the file bytes are returned in *bufOut
* (length *lenOut), allocated in the caller's memory context. This never returns
* on failure: any open/size/read/format/metadata error is reported with ereport,
* so the caller does not need to check a return value or clean the buffer up.
* The caller is responsible for any privilege check before calling.
*/
/* strcmp comparator for list_sort over a List of cstrings */
static int
pq_list_str_cmp(const ListCell *a, const ListCell *b)
Expand Down Expand Up @@ -2890,7 +2882,7 @@ pq_read_file_into(const char *path, TupleDesc tupdesc, TupleTableSlot *slot,
}

/*
* columnar.import_parquet(rel regclass, path text) -> bigint
* pgcolumnar.import_parquet(rel regclass, path text) -> bigint
*/
Datum
columnar_import_parquet(PG_FUNCTION_ARGS)
Expand Down Expand Up @@ -2958,7 +2950,7 @@ columnar_import_parquet(PG_FUNCTION_ARGS)
}

/*
* columnar.read_parquet(path text) returns setof record
* pgcolumnar.read_parquet(path text) returns setof record
*
* Stream a server-side Parquet file's rows in place, without importing. The caller
* supplies a column definition list:
Expand Down Expand Up @@ -3036,7 +3028,7 @@ columnar_read_parquet(PG_FUNCTION_ARGS)
}

/*
* columnar.parquet_schema(path text)
* pgcolumnar.parquet_schema(path text)
* -> table(column_name text, data_type text, nullable bool)
*
* Read a server-side Parquet file's footer and report its leaf columns with the
Expand Down Expand Up @@ -3331,9 +3323,9 @@ pq_percent_decode(const char *src, const char *file)
* function, so the declared type decides what a directory name means, and a value
* that will not convert raises through the normal input-function error.
*
* The value text is taken literally. Hive percent-encodes characters that cannot
* appear in a path component, and that decoding is deliberately not done here;
* see the limitation in the docs.
* The value text is percent-decoded (see pq_percent_decode). Hive percent-encodes
* characters that cannot appear in a path component, so a value written as a%3Db
* is read as the string "a=b".
*/
static void
pqfdw_partition_values(const char *root, const char *file, TupleDesc tupdesc,
Expand Down Expand Up @@ -4101,8 +4093,8 @@ pgcolumnar_parquet_fdw_handler(PG_FUNCTION_ARGS)
}

/*
* Option validator: the only accepted option is "path" on a foreign table. Server,
* wrapper, and user-mapping objects take no options.
* Option validator: the accepted options are "path" and "partition_columns" on a
* foreign table. Server, wrapper, and user-mapping objects take no options.
*/
Datum
pgcolumnar_parquet_fdw_validator(PG_FUNCTION_ARGS)
Expand Down
Loading
Loading