fix: close #77 SAX rich-typing edge cases (FieldOccurrence refactor + ns-decl injection)#80
Merged
Merged
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
This PR addresses SAX rich-typing edge cases for XML reading, focused on preserving mixed text/XML occurrence order and carrying namespace declarations into reparsed fragments.
Changes:
- Refactors SAX field accumulation to ordered
FieldOccurrencesequences. - Injects accumulated namespace declarations into synthetic/reparsed XML.
- Adds regression tests and fixtures for issue #77 SAX edge cases.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
src/xml_sax_reader.cpp |
Refactors SAX accumulation/materialization and adds namespace declaration capture/serialization. |
src/include/xml_sax_reader.hpp |
Adds FieldOccurrence and namespace declaration state/API. |
src/xml_schema_inference.cpp |
Extends fragment wrapping to accept injected namespace declarations. |
src/include/xml_schema_inference.hpp |
Updates ExtractValueFromXmlFragment signature with a defaulted namespace parameter. |
test/sql/github_issue_77_sax_edge_cases.test |
Adds regression coverage for mixed order and namespace handling. |
test/sql/xml_sax_accumulator_parity.test |
Updates comments for the new accumulator model. |
test/xml/sax_mixed_order.xml, test/xml/sax_ns_keep.xml, test/xml/sax_ns_later_record.xml |
Adds XML fixtures for the new regression tests. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| scalar_map[name] = value; | ||
| } | ||
| } | ||
| acc->current_fields[name].push_back({is_xml, std::move(value)}); |
teaguesterling
enabled auto-merge
May 31, 2026 22:05
The SAX accumulator stored a repeated field's text-shaped and nested-XML occurrences in separate maps, and AccumulatorToRow concatenated all text items before all XML items. A field interleaving bare-text and nested-XML siblings lost document order between the two kinds. Replace the four parallel maps with a single ordered sequence per field (current_fields -> vector<FieldOccurrence>), each occurrence tagged as text or XML and appended in element-close order. AccumulatorToRow and InferSchemaFromStream now consume occurrences in that order, so mixed lists keep their original sequence. Uniform lists are unaffected.
With namespaces:='keep', SAX streaming captures a prefixed nested element (ns:tag) into a raw fragment but not the originating xmlns: declaration. When ExtractValueFromXmlFragment wraps the fragment and reparses it under strict (non-recovery) parsing, the unresolved prefix fails to parse and the value collapses to a typed NULL. Accumulate prefixed namespace declarations seen during streaming and splice them into the synthetic wrapper element (via a new extra_ns_decls parameter on ExtractValueFromXmlFragment) and into the synthetic root used for schema inference, so prefixed names resolve. Default strip mode removes prefixes before capture and is unaffected.
teaguesterling
force-pushed
the
bd/sax-ns-order
branch
from
May 31, 2026 23:33
3520d3b to
dcfcbad
Compare
teaguesterling
added a commit
that referenced
this pull request
Jun 1, 2026
- vcpkg.json manifest 2.0.1 -> 2.1.0 - RELEASE_NOTES_v2.1.0.md covering the four merged PRs since v2.0.1: - #75 STRUCT widening + SAX rich-typing (@bendrucker) - #76 DuckDB main v1.6 build compatibility (@bendrucker) - #77 SAX edge cases (FieldOccurrence + ns decls) via #80 (@bendrucker) - #79 duckdb v1.5.3 stable bump + MSVC C++17 + mingw exclusion Co-Authored-By: Claude Opus 4.7 (1M context) <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.
Brings in @bendrucker's
sax-ns-orderfollow-up to PR #75, closing both edge cases tracked in #77. Opening on his behalf to keep release-prep moving — his offer from #75: "feel free to merge that branch into this PR if you want, or merge this as-is and I'll open a follow up to close #77." All commits authored by him (verbatim frombendrucker:sax-ns-order); branch hosted on origin only because GitHub blocks cross-fork PRs from the upstream owner.Commit 1 —
20b92c3preserve document order for mixed text/XML SAX occurrencesReplaces the four parallel accumulator maps (
current_values/current_xml_values/current_lists/current_xml_lists) with a singlecurrent_fields → vector<FieldOccurrence>ordered sequence per field, each occurrence tagged{is_xml, payload}and appended in element-close order.This is the "cleaner shape ... one map of (name) → Variant<Text, XmlFragment> that preserves document order" Ben himself flagged as out-of-scope in PR #75's body. Without this, the prior release would publish the parallel-maps shape that the author had already replaced.
SAXEndElementNs: the 3-branch "promote scalar → list / append / new scalar" dance collapses tocurrent_fields[name].push_back({is_xml, std::move(value)}). Singleton-vs-list semantics become consumer-derived fromoccs.size().InferSchemaFromStream: 4 separate emit loops → 1 with a ternary onocc.is_xml. Closes the synthetic-doc-order leak.AccumulatorToRowLIST + VARCHAR branches: one ordered loop per field replaces the parallel-list dispatch. This is the actual read_xml SAX rich-typing: namespace declarations dropped on reparsed fragments and list order lost for mixed text/XML occurrences #77 case-2 fix — the prior "Document order between text and XML items is not preserved" comment retires.occs.front()explicitly, making the implicit "XML wins over text" semantics visible.Net −62 lines.
Commit 2 —
3520d3binject namespace declarations into reparsed SAX fragmentsCloses the second #77 edge case: with
namespaces:='keep', SAX captures a prefixed nested element (ns:tag) into a raw fragment but not the originatingxmlns:nsdeclaration.ExtractValueFromXmlFragmentthen reparses under strict (non-recovery) mode and the unresolved prefix collapses the value to typed NULL.SAXStartElementNsaccumulates prefixed namespace declarations into astd::map<string,string>on the accumulator (skips default namespace — only prefixes need resolving; extraction matches by local name).Reset()) since root-levelxmlns:decls appear before the first record.BuildNamespaceDeclarations()serializes asxmlns:prefix="uri"...withXmlEscapeAttron URIs (legitimately needed for&in query strings).InferSchemaFromStreamdeclares accumulated namespaces on the synthetic<root>(usesrecords.back()— monotonic accumulation means it's the union of every decl seen while sampling).AccumulatorToRowbuildsns_declsonce per row, threads through 4ExtractValueFromXmlFragmentcallsites via a defaultedextra_ns_decls=""parameter on the function added in feat: STRUCT widening and SAX rich-typing for read_xml(union_by_name) #75 itself. Fully backwards-compatible.namespaces:='strip'is untouched — extra cost paid only when needed.Tests
test/sql/github_issue_77_sax_edge_cases.test: ns-decl injection (DOM-ref + SAX-via-maximum_file_size:=1× keep mode + strip-mode control), mixed-order assertions for the FieldOccurrence refactor, and asax_ns_later_record.xmlcase exercising the cumulative-across-records inference path (prefix first declared on a non-root record).test/sql/xml_sax_accumulator_parity.test: minor adjustments for the new internal API.sax_mixed_order.xml,sax_ns_keep.xml,sax_ns_later_record.xml.Closes
Closes #77.
cc @bendrucker — happy to amend the PR description if anything misrepresents the change.