Add Parquet support to view, less, and a new parquet2tab - #2
Merged
Conversation
Adds a parquet2tab subcommand (mirroring csv2tab) and teaches view and less to read Parquet files directly. Parquet is detected from its PAR1 magic bytes, in the same spirit as the existing gzip sniffing, so no flag is needed for a .parquet file. An explicit --parquet flag is also available, mirroring --csv. To make this work without duplicating the formatting stack, the readers now sit behind a RecordReader interface. The viewer, pager, and CSV exporter only ever needed ReadLine/Close/header access, so they consume the interface instead of *DelimitedTextFile. The exporter and sorter reach further into the text internals and were left on the concrete type. DelimitedTextFile keeps its exported Header field, so those two are untouched. Type mapping is the substance of the change. parquet.Value.String() renders the physical type only -- it would print a DATE as a day count and a DECIMAL(10,2) as its raw unscaled integer -- so ParquetFile builds a per-column formatter from the logical type instead: decimals get their scale applied, dates/times/timestamps are decoded from their integer representations, unsigned ints don't wrap negative, and UUID and the legacy INT96 timestamps are decoded properly. Schema handling: - nested structs flatten to dotted column names (addr.city) - lists and maps render as JSON in a single cell - NULL cells are empty by default, configurable with --na=STRING Parquet keeps its schema in a footer at the end of the file, so reading one requires random access. It cannot be read from a pipe; "-" is rejected with an explicit message rather than failing obscurely. Flags that are meaningless for Parquet (--no-header, --header-comment, --show-comments, --csv) are rejected when the user actually sets them. Note that parquet-go pulls mattn/go-runewidth from v0.0.2 to v0.0.15, which underpins termbox's width calculations. Pager output was diffed before and after, including CJK text, and is byte-identical. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds a
parquet2tabsubcommand (mirroringcsv2tab) and teachesviewandlessto read Parquet directly.Parquet is detected from its
PAR1magic bytes, in the same spirit as the existing gzip sniffing, so no flag is needed. An explicit--parquetflag is also available, mirroring--csv.Approach
Readers now sit behind a
RecordReaderinterface. The viewer, pager, and CSV exporter only ever neededReadLine/Close/header access, so they consume the interface instead of*DelimitedTextFile.exporter.goandsorter.goreach further into the text internals and stay on the concrete type —DelimitedTextFilekeeps its exportedHeaderfield, so both are untouched.Converting
CSVExporteris what letsparquet2tabreuse it wholesale, including the existing tab-escaping, rather than duplicating a second writer.Type mapping
This is the substance of the change.
parquet.Value.String()renders the physical type only — it would print aDATEas a day count and aDECIMAL(10,2)as its raw unscaled integer.ParquetFilebuilds a per-column formatter from the logical type instead:Decimal(p,s)big.Int(handles int32/int64/FLBA two's complement)Date2024-01-01Time/TimestampInteger{BitWidth,IsSigned}UUID, INT96Schema handling: nested structs flatten to dotted names (
addr.city); lists and maps render as JSON in one cell; NULLs are empty by default, configurable with--na=STRING.Pipe limitation
Parquet keeps its schema in a footer at the end of the file, so reading one requires random access. It cannot be read from a pipe —
-is rejected with an explicit message rather than failing obscurely. Flags meaningless for Parquet (--no-header,--header-comment,--show-comments,--csv) are rejected when actually set.Verification
make testgreen; six new tests lock down the formatting that's easy to get wrong (123.45not12345,2024-01-01not19723,4000000000not negative)examples/iris.txtis identicalCGO_ENABLED=0cross-compiles for linux/macos/windowsDependency note: parquet-go bumps
mattn/go-runewidthv0.0.2 → v0.0.15, which underpins termbox's width calculations. Pager output was diffed before and after, including CJK text, and is byte-identical — no pin needed.Binary size: 6.5 MB → 15.2 MB (brotli, klauspost/compress, lz4, protobuf). Worth a look if that matters for releases;
-ldflags="-s -w"would claw some back.Known limitation: an empty list and a NULL list both render as the NA string. Distinguishing them needs definition-level inspection, left out of scope.
🤖 Generated with Claude Code