leptris 1.9.220.0 → 1.9.221.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4ee8a29e686e3a4079398135f42f0bfc4f9362d92238863ec93dba31b5875bc0
4
- data.tar.gz: 390e5a0b87b57a77818207491d5cc96ea41941bdc04c6edd57fb5942f1d39c2e
3
+ metadata.gz: 1dd22d3fd01dc9a0199f453734f1c0eb6b8825dc0e942edcb9a5b0a354efd776
4
+ data.tar.gz: 0e4078ce4fc95daccd043c090abbc8807705685bd8d914293440fcacfde77f22
5
5
  SHA512:
6
- metadata.gz: 5c6f747575b65cb29526fa1ac86cfe9856684345444c0c29cbb4b9c1f3850e02bf938f21d86f604adeafcc2ebf14f76b7b028bf6fdbf15d955cb04217abe94dc
7
- data.tar.gz: a871ecf4f747071d6b6aca705d0bd2ef037f63f2d009bc2d35dcdeefc8f269632e1a1c596296848e8cc78268e4aa6c913d0094140e0543f387505a8f8e3f4533
6
+ metadata.gz: 2653ad914dc2f4255433d698d706c7ccb89e1eda06115370739ebeb9a12e372a8ea3eb73780670143e267b5f1f2ce7ccabaf7b2bcf4ca496f3deca9bb9e4a539
7
+ data.tar.gz: c7f9c594e7567bfbceb42dc4a1ed13bc64719465c588a149fe03c7af82449973c55d08746a7875ed5c021014f0d1de7c2f4c01b5b48470f0b001b5f64ba13250
data/CHANGELOG.md CHANGED
@@ -5,6 +5,14 @@ All notable changes to Leptris will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.9.221.0] - 2026-09-22
9
+
10
+ ### Performance — engine sync (1.9.221)
11
+
12
+ - Vendored libleptris 1.9.221: per-document overflow teardown in
13
+ the dispatch row (#682). No binding-facing surface changes;
14
+ audit 342/342, suite 770/0.
15
+
8
16
  ## [1.9.220.0] - 2026-09-22
9
17
 
10
18
  ### Performance — engine sync (1.9.217–1.9.220)
data/README.adoc CHANGED
@@ -504,10 +504,69 @@ Row kinds: `:scalar`, `:collection`, `:nested` (via `plan:`), `:raw`
504
504
  offset + `type_tag` echo, matching `Node#byte_offset`). Namespace
505
505
  binding lives on plans: `ns:` is `:none` (default), `:any`, or
506
506
  `{ exact: "urn:..." }`; `flags:` also accepts `:cdata`, `:ordered`,
507
- `:ns_lenient` (#754 out-of-namespace adoption). Children the plan
508
- does not describe are skipped; undescribed document order within a
509
- row is preserved. `#walk` returns a lazy `PlanValue` tree (the
510
- result outlives the document); `#to_ruby` materializes it.
507
+ `:ns_lenient` (#754 out-of-namespace adoption), and `:order_spine`
508
+ (#1273 unmatched sibling text/comment/PI runs emit as positioned
509
+ scalars so ordered hosts rebuild element order without re-parsing).
510
+ Children the plan does not describe are skipped; undescribed document
511
+ order within a row is preserved. `#walk` returns a lazy `PlanValue`
512
+ tree (the result outlives the document); `#to_ruby` materializes it.
513
+
514
+ === Typed scalars and the fused entry (#230/#298)
515
+
516
+ Plan rows accept `type: :integer | :float | :boolean` — the walk
517
+ parses the values natively (in-pass, libleptris #1269a) and
518
+ `#to_ruby` / the bulk face return `Integer` / `Float` / `true` /
519
+ `false`. Unparseable values fall back to the raw String;
520
+ `PlanValue#string_value` stays the raw escape, and
521
+ `#int_value` / `#float_value` / `#bool_value` expose the parsed
522
+ accessors directly.
523
+
524
+ `Descriptor#materialize(source)` is the fused entry: source bytes →
525
+ typed rows in ONE C call — parse, walk, and free happen natively
526
+ (#1269b); the standalone `PlanValue` tree is all that surfaces.
527
+
528
+ [source,ruby]
529
+ ----
530
+ descriptor = Leptris::XML::Descriptor.build(
531
+ name: "iso",
532
+ children: [{ name: "row", kind: :collection, plan: {
533
+ name: "row",
534
+ attributes: [{ name: "id", kind: :scalar, type: :integer }],
535
+ children: [
536
+ { name: "price", kind: :scalar, type: :float },
537
+ { name: "active", kind: :scalar, type: :boolean },
538
+ ] } },
539
+ ])
540
+
541
+ rows = descriptor.materialize(xml_source) # one C call
542
+ rows.as_kwarg_hash[:children]["row"] # typed, hydrator-ready
543
+ ----
544
+
545
+ === Predicate rows (#1272) and order identity (#1273)
546
+
547
+ Same-wire-name rows can partition on attribute values with `when:` —
548
+ AND across pairs, exclusive routing (first matching row wins per
549
+ occurrence):
550
+
551
+ [source,ruby]
552
+ ----
553
+ Leptris::XML::Descriptor.build(
554
+ name: "r",
555
+ children: [
556
+ { name: "item", kind: :collection, when: { "kind" => "a" },
557
+ plan: { name: "item", children: [{ name: "name", kind: :scalar }] } },
558
+ { name: "item", kind: :collection, when: { "kind" => "b" },
559
+ plan: { name: "item", children: [{ name: "name", kind: :scalar }] } },
560
+ ])
561
+ ----
562
+
563
+ `PlanValue#node_kind` and `#order_index` carry document-order
564
+ identity for matched rows (and, under `:order_spine`, for the
565
+ interleaved text runs) — ordered/mixed content reconstructs
566
+ without fragment re-parsing. `PlanValue#as_kwarg_hash` is the
567
+ bulk hydrator face: one Ruby call per node returns the typed
568
+ attribute/children hash (the crossings floor on the 5k-row ISO
569
+ fixture is ~4 FFI crossings per row, pinned by spec).
511
570
 
512
571
  == XQuery
513
572
 
data/Rakefile CHANGED
@@ -8,7 +8,7 @@ RSpec::Core::RakeTask.new(:spec)
8
8
  # Pin for `rake compile` and the platform-gem builds. Keep in lockstep
9
9
  # with .github/workflows/build.yml (which calls `rake compile`) and the
10
10
  # CHANGELOG when libleptris releases.
11
- LIBLEPTRIS_VERSION = "1.9.220"
11
+ LIBLEPTRIS_VERSION = "1.9.221"
12
12
  # Vendored alongside libleptris for fn:normalize-unicode (TODO
13
13
  # .restructure/20): built per platform with a RELOCATABLE @rpath
14
14
  # install name, loaded by ffi.rb before libleptris so the
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Leptris
4
- VERSION = "1.9.220.0"
4
+ VERSION = "1.9.221.0"
5
5
  end
data/vendor-src/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  Sources vendored in this gem (recompile rights; the ruby
2
2
  variant builds them at install via extconf.rb):
3
3
 
4
- - libleptris v1.9.220
4
+ - libleptris v1.9.221
5
5
  - utf8proc v2.11.0
6
6
 
7
7
  Rebuild by hand:
@@ -1,5 +1,13 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.9.221] - 2026-09-22
4
+
5
+ ### Performance
6
+
7
+ - per-document overflow teardown (#682 dispatch row) (dom)
8
+
9
+
10
+
3
11
  ## [1.9.220] - 2026-09-21
4
12
 
5
13
  ### Performance
@@ -3,7 +3,7 @@
3
3
  cmake_minimum_required(VERSION 3.20)
4
4
 
5
5
  project(leptris
6
- VERSION 1.9.220
6
+ VERSION 1.9.221
7
7
  DESCRIPTION "Fast XML parser and XPath evaluator in pure C"
8
8
  LANGUAGES C CXX
9
9
  )
@@ -9,6 +9,7 @@
9
9
  */
10
10
 
11
11
  #include "compact.h"
12
+ #include "../leptris_internal.h" /* struct leptris_document (overflow_entries) */
12
13
  #include "../common/port.h"
13
14
  #include <stdlib.h>
14
15
  #include <string.h>
@@ -158,6 +159,14 @@ int leptris_compact_overflow_set(LeptrisCompactOverflowTable* table,
158
159
  table->buckets[index] = entry;
159
160
  table->entry_count++;
160
161
 
162
+ /* #682: parallel per-document chain — teardown walks exactly
163
+ * this doc's entries instead of scanning the whole table. */
164
+ if (doc) {
165
+ entry->doc_next =
166
+ (struct leptris_compact_overflow_entry*)doc->overflow_entries;
167
+ doc->overflow_entries = entry;
168
+ }
169
+
161
170
  if (table->entry_count >= table->bucket_count) {
162
171
  overflow_table_grow(table);
163
172
  }
@@ -208,26 +217,40 @@ void leptris_compact_cleanup(void) {
208
217
  void leptris_compact_cleanup_document(struct leptris_document* doc) {
209
218
  if (!doc || !g_overflow_table) return;
210
219
 
211
- size_t removed_count = 0;
220
+ /* #682: walk THIS document's entry chain (linked at set time)
221
+ * instead of scanning every bucket of the global table — the
222
+ * old full-table sweep ran on every document free and dominated
223
+ * apply-heavy XSLT profiles (result docs register thousands of
224
+ * cross-block edge entries; the sampler pinned 35% of the
225
+ * dispatch bench in this loop). */
226
+ LeptrisCompactOverflowEntry* e =
227
+ (LeptrisCompactOverflowEntry*)doc->overflow_entries;
228
+ doc->overflow_entries = NULL;
212
229
 
213
- for (size_t i = 0; i < g_overflow_table->bucket_count; i++) {
214
- LeptrisCompactOverflowEntry** entry_ptr = &g_overflow_table->buckets[i];
215
- LeptrisCompactOverflowEntry* entry = *entry_ptr;
216
-
217
- while (entry) {
218
- if (entry->doc == doc) {
219
- /* Unlink only entries live in SLABS owned by the
220
- * table; free()ing them corrupted the heap whenever
221
- * a document had overflow entries (common since the
222
- * #450 sibling-edge work routed far links here). */
223
- *entry_ptr = entry->next;
224
- entry = *entry_ptr;
225
- removed_count++;
226
- } else {
227
- entry_ptr = &entry->next;
228
- entry = entry->next;
230
+ size_t removed_count = 0;
231
+ while (e) {
232
+ LeptrisCompactOverflowEntry* next = e->doc_next;
233
+ if (e->doc == doc) {
234
+ /* Stale chain links (entry re-registered under another
235
+ * doc) are skipped — the ->doc check keeps them alive
236
+ * for their true owner. */
237
+ size_t index = hash_pointer(e->key,
238
+ g_overflow_table->bucket_count);
239
+ LeptrisCompactOverflowEntry** entry_ptr =
240
+ &g_overflow_table->buckets[index];
241
+ while (*entry_ptr) {
242
+ if (*entry_ptr == e) {
243
+ /* Unlink only — entries live in SLABS owned by
244
+ * the table; free()ing them corrupted the heap
245
+ * (#450 sibling-edge follow-up). */
246
+ *entry_ptr = e->next;
247
+ removed_count++;
248
+ break;
249
+ }
250
+ entry_ptr = &(*entry_ptr)->next;
229
251
  }
230
252
  }
253
+ e = next;
231
254
  }
232
255
 
233
256
  g_overflow_table->entry_count -= removed_count;
@@ -60,6 +60,9 @@ typedef struct leptris_compact_overflow_entry {
60
60
  void* value; /* Actual pointer value */
61
61
  struct leptris_document* doc; /* Document that owns this entry */
62
62
  struct leptris_compact_overflow_entry* next;
63
+ /* #682: per-document chain — teardown walks THIS doc's entries
64
+ * instead of scanning every bucket of the global table. */
65
+ struct leptris_compact_overflow_entry* doc_next;
63
66
  } LeptrisCompactOverflowEntry;
64
67
 
65
68
  /**
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg/master/scripts/vcpkg.schema.json",
3
3
  "name": "leptris",
4
- "version": "1.9.220",
4
+ "version": "1.9.221",
5
5
  "description": "High-performance XML parser and XPath 1.0 engine in C (libleptris)",
6
6
  "homepage": "https://github.com/leptris/leptris",
7
7
  "license": "MIT",
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: leptris
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.9.220.0
4
+ version: 1.9.221.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-09-21 00:00:00.000000000 Z
11
+ date: 2026-09-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ffi