Skip to content

fix: close #77 SAX rich-typing edge cases (FieldOccurrence refactor + ns-decl injection)#80

Merged
teaguesterling merged 2 commits into
mainfrom
bd/sax-ns-order
Jun 1, 2026
Merged

fix: close #77 SAX rich-typing edge cases (FieldOccurrence refactor + ns-decl injection)#80
teaguesterling merged 2 commits into
mainfrom
bd/sax-ns-order

Conversation

@teaguesterling

Copy link
Copy Markdown
Owner

Brings in @bendrucker's sax-ns-order follow-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 from bendrucker:sax-ns-order); branch hosted on origin only because GitHub blocks cross-fork PRs from the upstream owner.

Commit 1 — 20b92c3 preserve document order for mixed text/XML SAX occurrences

Replaces the four parallel accumulator maps (current_values / current_xml_values / current_lists / current_xml_lists) with a single current_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 to current_fields[name].push_back({is_xml, std::move(value)}). Singleton-vs-list semantics become consumer-derived from occs.size().
  • InferSchemaFromStream: 4 separate emit loops → 1 with a ternary on occ.is_xml. Closes the synthetic-doc-order leak.
  • AccumulatorToRow LIST + 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.
  • STRUCT branch: now reads occs.front() explicitly, making the implicit "XML wins over text" semantics visible.

Net −62 lines.

Commit 2 — 3520d3b inject namespace declarations into reparsed SAX fragments

Closes the second #77 edge case: with namespaces:='keep', SAX captures a prefixed nested element (ns:tag) into a raw fragment but not the originating xmlns:ns declaration. ExtractValueFromXmlFragment then reparses under strict (non-recovery) mode and the unresolved prefix collapses the value to typed NULL.

  • SAXStartElementNs accumulates prefixed namespace declarations into a std::map<string,string> on the accumulator (skips default namespace — only prefixes need resolving; extraction matches by local name).
  • Persistent across records (NOT cleared by Reset()) since root-level xmlns: decls appear before the first record.
  • BuildNamespaceDeclarations() serializes as xmlns:prefix="uri"... with XmlEscapeAttr on URIs (legitimately needed for & in query strings).
  • InferSchemaFromStream declares accumulated namespaces on the synthetic <root> (uses records.back() — monotonic accumulation means it's the union of every decl seen while sampling).
  • AccumulatorToRow builds ns_decls once per row, threads through 4 ExtractValueFromXmlFragment callsites via a defaulted extra_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.
  • Default 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 a sax_ns_later_record.xml case 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.
  • Fixtures: 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.

Copilot AI review requested due to automatic review settings May 31, 2026 22:00

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 FieldOccurrence sequences.
  • 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.

Comment thread src/xml_sax_reader.cpp
scalar_map[name] = value;
}
}
acc->current_fields[name].push_back({is_xml, std::move(value)});
@teaguesterling
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
teaguesterling merged commit d5c7121 into main Jun 1, 2026
33 checks passed
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

read_xml SAX rich-typing: namespace declarations dropped on reparsed fragments and list order lost for mixed text/XML occurrences

3 participants