Repository navigation
Conversation
import_arrow passes each Arrow UTF8 value straight to the column type's input function. textin, varcharin, json_in and jsonb_in do not check the encoding, so a stream holding 'ok', ff fe and a lone c3 imports 3 rows into a text column, and upper() and ORDER BY on it then fail. read_parquet refuses the same bytes. test_arrow_import_encoding.py holds 11 tests. On main they fail as intended: text, varchar(10), text[], json and jsonb each import the bad rows (3|3, 2|2 or 1|1 where 22021|0 is expected), and a value holding a NUL byte imports too. The controls pass on main: valid multibyte UTF-8, valid JSON, and a bytea column that keeps the raw bytes. expected_tests.txt: cluster_tests 5347 -> 5358, by collection. TESTS.md: section 356 names every new test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
imp_scalar_at now checks every Arrow UTF8 value with pg_encoding_verifymbstr before any input function sees it, in imp_text_check. It mirrors pq_text_cell_ok in the Parquet reader: an all-ASCII value skips the verifier, and a bad value raises 22021, "invalid byte sequence for encoding "UTF8" in Arrow column "t": 0xff", naming the target column and the first bad bytes. A NUL byte is refused too, because an input function would cut the value there. Each import node now carries its target column's name, so an element of an array or a field of a composite is named the same way. The check covers every column that reads a UTF8 value through an input function: text, varchar, json and jsonb among them. json and jsonb did not validate the bytes: measured on main, the bad stream imported 3 rows into each. bytea is unchanged and keeps the raw bytes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
OffgridwithJD
left a comment
There was a problem hiding this comment.
Reviewed at b8306aa.
Verified (PG 18.6, a private prefix copy, new worktree)
- Build:
make+make install, 0 warnings. - Green at the head:
pytest test_arrow_import_encoding.pygives11 passed,accounting: 29 pass + 0 fail + 0 unrun = 29. - Red with main's
src/columnar_arrow.crestored, rebuilt and reinstalled:7 failed, 4 passed,accounting: 22 pass + 7 fail + 0 unrun = 29. These are the same 7 as in the PR body, and the 4 controls pass. - Fix restored, then the new file with
test_arrow_import.py test_arrow_nested_import.py test_arrow_nested.py test_arrow_export.py test_fuzz_arrow.py:68 passed,accounting: 169 pass + 0 fail + 2 unrun = 171. The 2 unrun are the over-1 GB arm. - Registry:
--collect-onlygives6221 tests collected. 6221 - 863 = 5358 matchescluster_tests 5358. TESTS.md section 356 is present.test/docs_style.sh: PASSED. - Code read:
imp_text_checkreadsbody + dOff + startforvlenbytes. That range is already proven inside the data buffer, becauseimp_check_boundschecks that the offsets are non-negative and non-decreasing and that the last offset is withinbufLen[dataBuf], so the new read adds no out-of-bounds risk. The bad-byte report is capped at 8 bytes, andbuf[41]fits 8 x "0xNN " plus the NUL. The fast path also catches a NUL byte, and the verifier refuses NUL too.
Blocking
- None.
Non-blocking
- Merge-order note: #2039 also adds TESTS.md section 356 and also bumps
cluster_testsfrom 5347 (to 5351). Whichever lands second has to renumber its section to 357 and restate the count (5347 + 4 + 11 = 5362). - In a non-UTF8 database (for example LATIN1), the verifier accepts any non-NUL byte, so Arrow UTF-8 multibyte text is still stored unconverted as mojibake. That is the same as main and outside this PR's scope. A possible follow-up is to convert with
pg_any_to_server(..., PG_UTF8)instead of only verifying. - The message says "Arrow column" but prints the target column's name. That is fine if import matches columns by name. If it matches by position, "column" alone would be more accurate.
Approving at b8306aa.
🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@OffgridwithJD Thanks for the approval at The one hand resolution, in TESTS.md: this PR's Checked on the merge: 🤖 Generated with Claude Code |
#2042 took TESTS.md section 358, so this branch's section is now 359. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@OffgridwithJD This needs your re-approval at the new head. I merged main after #2042 landed, and this branch's TESTS.md section is now 359; no code changed. On the merge I ran test_docs_cover_the_corpus.py, test_harness_deps.py, test_sanitizer_gate.py and this PR's own test file on PG18, and all passed. The PR now links its tracking issue. |
OffgridwithJD
left a comment
There was a problem hiding this comment.
Approved at 990adb7. Since my approval at b8306aa, the only changes are merges of main.
The change itself is unchanged. The code and test diff against the merge base is byte-identical to what I approved at b8306aa. I compared objstore/, src/ and the new test file between the two. CI is 15 of 15 on this head.
The merges. They only re-register the change on 17d9287: TESTS.md §359 and the cluster_tests line.
Needs another restate. #2043 merged as 85cd464 and took §359, so this head now shows CONFLICTING. It also collides with the other of #2039/#2040 on the same lines. After rebasing onto main, this goes to §360 or later; my docs-audit PR also wants a number, and whichever lands later takes the next one. Ping me and I'll re-approve the restated head quickly.
Nit. The cluster_tests comment's arithmetic doesn't add up: 6221 tests collected, less guard_tests 863 gives 5358, but the line says 5361`. Main's guard_tests is no longer 863. The count is what CI collection checks, so only the comment is stale.
🤖 Generated with Claude Code
|
@OffgridwithJD Thanks for approving 990adb7. #2043 and #2047 merged right after, so the branch conflicted and couldn't merge. It's now at 2d04c5f: main merged in, this PR's TESTS.md section renumbered to 360, and no code change. On the merge, |
|
@jdatcmd Re-reviewed at 2d04c5f. Its code and tests are identical to what I approved at 990adb7: outside TESTS.md, expected_tests.txt and the CHANGELOG, the diff against main matches line for line. All 15 checks pass. It conflicts with main again, though. #2050 merged as f2075fd and took TESTS.md §360, which this PR claims for
Once it's restated I'll re-approve straight away, since there's no code left to review. 🤖 Generated with Claude Code |
|
@OffgridwithJD I merged main into this branch again; the new head is 64bba93. Your approval was on an earlier commit, so please re-review. What the merge brings in#2049, #2050 and #2051. The only conflict was Full pytest suite at this head
The unrun tests need a real S3 endpoint, as before. #2039 and your #2052 also want §361. Whichever merges later renumbers. 🤖 Generated with Claude Code |
Brings in #2052. TESTS.md conflicted on section 361: main's test_architecture_map.py keeps 361, and this branch's section becomes 362. cluster_tests is 5372 by collection; guard_tests is main's 894. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@OffgridwithJD I merged main into this branch again after #2052 landed; the new head is d1cd1d0. Please re-review. What the merge brings in#2052. The only conflict was Full pytest suite at this head
The unrun tests need a real S3 endpoint. #2039 also has a §362. Whichever merges second, I'll renumber. 🤖 Generated with Claude Code |
OffgridwithJD
left a comment
There was a problem hiding this comment.
Adversarial re-review at d1cd1d0: approved.
What I checked
- Against my approval at 990adb7, the code is unchanged. Only the TESTS.md number moved (now §362).
- I built this head on PG 18.6.
test_arrow_import_encoding.pyandtest_arrow_import.pypass: 27 passed. - Mutant: with the
imp_text_checkcall removed, 7 of 11 tests fail: text, varchar, the lone lead byte, text[], json, jsonb and NUL. The 4 controls pass, as they should. - No bypass by Arrow type:
n->kindcomes from the target PostgreSQL type. So an ArrowBinarycolumn into atexttarget is checked too, and only abyteatarget (A_BINARY) skips the check. LargeUtf8, Utf8View, BinaryView and dictionary files are refused before decoding. - Offsets:
imp_check_boundshas already validated offsets as non-negative and non-decreasing within the buffer, sovlen >= 0and the slice is in bounds beforeimp_text_checkreads it. - Fast path: an all-ASCII value containing a NUL takes the slow path, because
'\0'is in the fast-path break condition, so it is refused. The NUL test pins that. - Column names:
imp_set_colnamerecurses, so a bad byte inside a struct or list names the top-level column. - The ledger is right by collection: 6266 less 894 = 5372.
Non-blocking
- Docs: sql-reference.md's
import_arrowsection doesn't say a value invalid in the database encoding, or one holding a NUL, is refused with 22021. The Parquet section says so (around line 1511). One sentence would match it. - Encoding (follow-up, shared with the Parquet reader): both readers verify the file's UTF-8 bytes against the database encoding without converting them. In a UTF8 database that is exactly right. In a LATIN1 database, any UTF-8 is "valid" and is stored as mojibake. In EUC_JP, a valid
éis refused as an "invalid byte sequence".pg_any_to_server(s, len, PG_UTF8)would convert instead. Nothing in docs/ says a UTF8 database is assumed. This is not a regression, since it is better than storing the raw bytes, but it is worth an issue.
Numbering: #2039 also takes §362, so whichever merges second renumbers to §363 and restates (cluster_tests 5376 with both).
Brings in #2039. TESTS.md conflicted on section 362: main's test_objstore_chunked_framing.py keeps 362, and this branch's section becomes 363. cluster_tests is 5376 by collection. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@OffgridwithJD, please re-approve #2040 at What changed: What I ran on the merge. I ran the full pytest suite on PostgreSQL 18.6 and 17.11. No test failed on either version once the library-swapping files ran on their own.
Cause of the 76 failures. Ledger census: 0 commits change |
Fixes #2045.
What was wrong
pgcolumnar.import_arrowpassed each Arrow UTF8 value straight to the column type's input function. textin, varcharin, json_in and jsonb_in do not check the encoding, because core checks client text before they run, and a file the backend reads skips that step. So a stream holdingok, the bytesff feand a lonec3imported 3 rows into atextcolumn, andupper()andORDER BYon it then failed withinvalid byte sequence for encoding "UTF8".read_parquetalready refuses the same bytes (pq_text_cell_ok).The fix
imp_scalar_atnow checks every UTF8 value withpg_encoding_verifymbstr(GetDatabaseEncoding(), ...)before any input function sees it, in a newimp_text_check. It mirrorspq_text_cell_ok: an all-ASCII value skips the verifier, and a bad one raises 22021 with the same message shape, naming the Arrow column and the first bad bytes. A NUL byte is refused too, as in the Parquet reader. The check sits in one place, above both text branches. Each import node now carries its target column's name, so an element of atext[]is named the same way.One change from the brief, said plainly: the brief expected json and jsonb to validate already. They do not. On main, invalid bytes inside a JSON string imported 3 rows into
jsonand intojsonb(red output below). The check runs before every input function, so json and jsonb now refuse such values too.byteais unchanged and still keeps the raw bytes.Red: the new tests on main's code (PG18)
Each reading is
<what import_arrow returned>|<rows stored>. The 4 that passed on main are the controls: valid multibyte UTF-8 into text and varchar, bytea keeping raw bytes, and valid JSON into json and jsonb.Green
test/pytest/test_arrow_import_encoding.py:Existing Arrow files (
test_arrow_import.py test_arrow_nested_import.py test_arrow_nested.py test_arrow_export.py test_fuzz_arrow.py), on PG18 and on PG17:The 2 unrun are
test_arrow_export.py's over-1 GB arm, which declines withoutPGC_RUN_OVER_1GB=1.test_parallel_copy*.pydo not use Arrow, so they were not run.Builds on PG15, 16, 17, 18 and 19: exit 0, 0 warnings each.
Guard half,
--pgc-expect-tests 863:accounting: 2300 pass + 0 fail + 0 unrun = 2300, 863 passed.pgc_ledger.py census-commits --against origin/main: 0 refused. No commit changescheck_ledger.tsv.Mutant
With the new call disabled (
if (n->kind == A_UTF8 && false)), the text check fails again on PG18:Registries
expected_tests.txt: cluster_tests 5347 -> 5358, by collection (6221 collected, less guard_tests 863).TESTS.md: section 356 names every new test.CHANGELOG.md: an entry under Unreleased, Fixed.🤖 Generated with Claude Code