leptris 1.9.220.0 → 1.9.221.1

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: abec97f5dc76127e52554fe9254f7b9a93676c0583dde964e474a7e662884878
4
+ data.tar.gz: d2d30b60d8ad656c6ed3564e63e9ea7ecc006ebe17cdb026075dfd8407311dd8
5
5
  SHA512:
6
- metadata.gz: 5c6f747575b65cb29526fa1ac86cfe9856684345444c0c29cbb4b9c1f3850e02bf938f21d86f604adeafcc2ebf14f76b7b028bf6fdbf15d955cb04217abe94dc
7
- data.tar.gz: a871ecf4f747071d6b6aca705d0bd2ef037f63f2d009bc2d35dcdeefc8f269632e1a1c596296848e8cc78268e4aa6c913d0094140e0543f387505a8f8e3f4533
6
+ metadata.gz: fe37f35a8359f66544471ea70405b3895ce07acc64d9e92067fd26d9945b1c6ec2f2b82ca7a4b18628f59ca012c8347adbea24a173a8fc52bf725dacdd761e36
7
+ data.tar.gz: d2531467d1c319ff967ab0eb7b6cafc54beead04048d3d788300e8bc042cd1ce20784caf98d9e1646944d6db346cec66368bece7f1e1ace2adbf05d779039906
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
@@ -22,8 +22,15 @@ root = File.expand_path("..", __dir__)
22
22
  ext_dir = File.join(root, "ext", "leptris", "native")
23
23
  minor = RUBY_VERSION[/\A\d+\.\d+/]
24
24
  minor_us = minor.tr(".", "_")
25
+ INIT_PREFIX = "Init_native_"
25
26
 
26
27
  Dir.chdir(ext_dir) do
28
+ # aarch64-ucrt 1.9.221.0 shipped a DLL exporting only Init_native
29
+ # because make reused a stale native.obj from a failed earlier
30
+ # attempt (the log shows "creating Makefile -> linking" with no
31
+ # compile). Force a clean compile on every invocation.
32
+ FileUtils.rm_f(Dir.glob("#{ext_dir}/native.{o,obj,so,dll}"))
33
+ FileUtils.rm_f("#{ext_dir}/leptris/xml/native.so")
27
34
  system(RbConfig.ruby, "extconf.rb") or abort "extconf failed under #{RUBY_VERSION}"
28
35
  # mkmf's link binds the CURRENT Ruby's runtime DLL — exactly
29
36
  # what the versioned naming is for.
@@ -31,6 +38,18 @@ Dir.chdir(ext_dir) do
31
38
  abort "make failed under #{RUBY_VERSION}" unless success
32
39
  so = Dir.glob("native.{so,dll}").first
33
40
  abort "native bundle not produced under #{RUBY_VERSION}" unless so
41
+ # Functional export gate: dlopen the built DLL and resolve the
42
+ # MINOR-SUFFIXED init symbol. A PE DLL can carry the symbol in its
43
+ # string table while exporting only mkmf's Init_native (the arm64
44
+ # toolchain ignores our .def when its own wins the link) — only a
45
+ # real lookup proves native_<minor>.so will load.
46
+ require "fiddle"
47
+ begin
48
+ Fiddle.dlopen(File.expand_path(so))[INIT_PREFIX + minor_us]
49
+ rescue Fiddle::DLError => e
50
+ abort "#{so} does not export #{INIT_PREFIX + minor_us} — refusing to " \
51
+ "install a DLL that would 127 at require (#{e.message})"
52
+ end
34
53
  dest = File.join(root, "lib", "leptris", "xml", "native_#{minor_us}.so")
35
54
  # The artifact must reference only this minor's Ruby DLL.
36
55
  imported = `strings #{so} 2>/dev/null`[/[a-z0-9-]*ruby\d{3,}\.dll/i]
@@ -2524,7 +2524,23 @@ static VALUE leptris_native_resolve_rb(VALUE self, VALUE lib_path)
2524
2524
  return Qnil;
2525
2525
  }
2526
2526
 
2527
- void Init_native(void)
2527
+ /*
2528
+ * Windows export discipline: a PE DLL exports only what a .def file or
2529
+ * __declspec(dllexport) names. native_layer requires the MINOR-SUFFIXED
2530
+ * symbol (native_3_4.so -> Init_native_3_4), but mkmf generates its own
2531
+ * .def listing just Init_native, and depending on toolchain that def
2532
+ * can win over ours (aarch64-ucrt ld does not auto-export the rest, so
2533
+ * the lookup 127s and the platform silently degrades to the FFI
2534
+ * surface). dllexport pins all four entry points into the export table
2535
+ * on every Windows toolchain; it is a no-op elsewhere.
2536
+ */
2537
+ #if defined(_WIN32)
2538
+ # define LEPTRIS_INIT_EXPORT __declspec(dllexport)
2539
+ #else
2540
+ # define LEPTRIS_INIT_EXPORT
2541
+ #endif
2542
+
2543
+ LEPTRIS_INIT_EXPORT void Init_native(void)
2528
2544
  {
2529
2545
  VALUE m_leptris, m_xml, m_native;
2530
2546
 
@@ -2683,7 +2699,7 @@ void Init_native(void)
2683
2699
  * each needs its exact init name exported. All are defined here
2684
2700
  * unconditionally: a DLL only ever loads under the Ruby minor it
2685
2701
  * was built against (PE binds its build Ruby's runtime DLL). */
2686
- void Init_native_3_3(void) { Init_native(); }
2687
- void Init_native_3_4(void) { Init_native(); }
2688
- void Init_native_4_0(void) { Init_native(); }
2702
+ LEPTRIS_INIT_EXPORT void Init_native_3_3(void) { Init_native(); }
2703
+ LEPTRIS_INIT_EXPORT void Init_native_3_4(void) { Init_native(); }
2704
+ LEPTRIS_INIT_EXPORT void Init_native_4_0(void) { Init_native(); }
2689
2705
  #endif
@@ -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.1"
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.1
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