Skip to content

feat: add slice($start, $end) method for range extraction#40

Merged
orieg merged 2 commits intomainfrom
feat/slice
Feb 28, 2026
Merged

feat: add slice($start, $end) method for range extraction#40
orieg merged 2 commits intomainfrom
feat/slice

Conversation

@orieg
Copy link
Owner

@orieg orieg commented Feb 27, 2026

Summary

  • Adds Judy::slice(mixed $start, mixed $end): Judy method that returns a new Judy array containing entries within the inclusive range [$start, $end]
  • Works for all 5 array types: BITSET, INT_TO_INT, INT_TO_MIXED, STRING_TO_INT, STRING_TO_MIXED
  • Deep-copies zvals for MIXED value types so the result is fully independent
  • Includes allocation error handling with cleanup via goto alloc_error

Implementation details

Type Range semantics API used
BITSET $start/$end are integer indices J1F/J1N to iterate, J1S to insert
INT_TO_INT $start/$end are integer indices JLF/JLN to iterate, JLI to insert
INT_TO_MIXED $start/$end are integer indices JLF/JLN to iterate, JLI + ZVAL_COPY
STRING_TO_INT $start/$end are strings (lexicographic) JSLF/JSLN + strcmp boundary
STRING_TO_MIXED $start/$end are strings (lexicographic) JSLF/JSLN + strcmp + ZVAL_COPY

Edge cases handled

  • $start > $end: returns empty array (no iteration)
  • Empty source array: returns empty array
  • Non-string args on string-keyed types: throws TypeError
  • Allocation failure during copy: frees partial result, throws exception

New helper

judy_create_result() is a generic factory that creates an initialized Judy result object for any type (replaces type-specific helpers for new code).

Test plan

  • tests/slice_bitset_001.phpt -- middle range, full range, single element, no match
  • tests/slice_int_to_int_001.phpt -- middle range, full range, single element, no match
  • tests/slice_int_to_mixed_001.phpt -- mixed values, deep copy independence, null values
  • tests/slice_string_to_int_001.phpt -- lexicographic range, single key, all, no match
  • tests/slice_string_to_mixed_001.phpt -- lexicographic range, deep copy independence
  • tests/slice_empty_001.phpt -- empty source, reversed range, type errors
  • All 73 tests pass (67 existing + 6 new)

Implement Judy::slice() that returns a new Judy array of the same type
containing entries within the inclusive range [$start, $end]. Supports
all 5 array types with proper deep-copy for MIXED value types.
@gemini-code-assist
Copy link

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a significant new core feature to the Judy PHP extension: the slice method. This method allows users to efficiently extract a sub-range of elements from any Judy array type, returning a new Judy array. It ensures data integrity through deep copying for mixed value types and includes robust error handling, making the Judy extension more versatile for range-based data manipulation.

Highlights

  • New Judy::slice() method: Implemented Judy::slice(mixed $start, mixed $end) to extract a new Judy array containing entries within an inclusive range.
  • Broad Type Support: The slice method is compatible with all five Judy array types: BITSET, INT_TO_INT, INT_TO_MIXED, STRING_TO_INT, and STRING_TO_MIXED.
  • Deep Copy for Mixed Values: Ensured deep-copying of zvals for MIXED value types, guaranteeing the independence of the resulting sliced array.
  • Robust Error Handling: Incorporated allocation error handling with proper cleanup using goto alloc_error and throws exceptions on failure.
  • New Helper Function: Introduced judy_create_result() as a generic factory for initializing new Judy result objects, replacing type-specific helpers.
  • Comprehensive Edge Case Management: Addressed various edge cases, including $start > $end (returns empty array), empty source arrays, TypeError for non-string arguments on string-keyed types, and graceful handling of allocation failures.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • README.md
    • Updated the feature checklist to mark slice($start, $end) as implemented.
  • package.xml
    • Added six new test files for the slice method across different Judy array types.
  • php_judy.c
    • Added a static helper function judy_create_result for consistent Judy object initialization.
    • Implemented the PHP_METHOD(judy, slice) function, providing range extraction logic for all Judy array types.
    • Declared PHP_METHOD(judy, slice) to make it available.
    • Defined arginfo_judy_slice for argument type information.
    • Registered the slice method in judy_class_methods.
  • tests/slice_bitset_001.phpt
    • Added a test case for Judy::BITSET slice method, covering middle, full, single element, and no-match ranges.
  • tests/slice_empty_001.phpt
    • Added a test case for Judy::slice handling empty source arrays, reversed ranges, and type errors for string-keyed arrays.
  • tests/slice_int_to_int_001.phpt
    • Added a test case for Judy::INT_TO_INT slice method, covering middle, full, single element, and no-match ranges.
  • tests/slice_int_to_mixed_001.phpt
    • Added a test case for Judy::INT_TO_MIXED slice method, including mixed values and deep copy verification.
  • tests/slice_string_to_int_001.phpt
    • Added a test case for Judy::STRING_TO_INT slice method, covering lexicographical ranges, single keys, full range, and no-match scenarios.
  • tests/slice_string_to_mixed_001.phpt
    • Added a test case for Judy::STRING_TO_MIXED slice method, including mixed values and deep copy verification.
Activity
  • The pull request was created by orieg.
  • The pull request includes a detailed summary of changes, implementation details, edge cases handled, and a comprehensive test plan.
  • Six new test files were added to validate the new slice functionality across various Judy array types and scenarios.
  • The README.md was updated to reflect the completion of the slice feature.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a slice() method for efficient range extraction from Judy arrays. However, it contains a critical stack-based buffer overflow vulnerability in the handling of string-keyed arrays (JudySL) due to the use of a fixed-size stack buffer for retrieving keys, which does not account for keys exceeding the buffer size. Additionally, there's a potential critical bug in the error handling path that could lead to a double-free, the element counter for integer-keyed array types (BITSET, INT_TO_INT, INT_TO_MIXED) is not being updated, and there is some code duplication in the string-keyed array handling that could be refactored. Specific comments have been added with suggestions to address these points.

@github-actions
Copy link

github-actions bot commented Feb 27, 2026

Test Results

PHP Platform Arch TS Tests Pass Fail Skip Duration
8.1 Linux x64 - 73 73 0 0 0.6s
8.2 Linux x64 - 73 73 0 0 0.8s
8.3 Linux x64 - 73 73 0 0 0.8s
8.4 Linux x64 - 73 73 0 0 0.8s
8.5 Linux x64 - 73 73 0 0 0.8s
8.1 Windows x64 nts 73 73 0 0 4.6s
8.2 Windows x64 nts 73 73 0 0 4.6s
8.3 Windows x64 nts 73 73 0 0 4.6s
8.4 Windows x64 nts 73 73 0 0 4.8s
8.5 Windows x64 nts 73 73 0 0 4.7s
Total 730 730 0 0

Benchmark Summary (Judy vs PHP Array)

Ratio = Judy / Array. Bold = Judy wins (≤0.95x). Plain = Array is faster/smaller.

Time (Write / Read) — Linux

Scenario PHP 8.1 PHP 8.2 PHP 8.3 PHP 8.4 PHP 8.5
Sparse Int 100K 3.0x / 2.5x 2.9x / 2.7x 2.9x / 2.7x 2.9x / 2.6x 2.9x / 2.7x
Sparse Int 500K 3.3x / 2.7x 3.0x / 1.5x 3.0x / 2.7x 2.8x / 2.7x 3.1x / 1.8x
Sparse Int 1M 2.8x / 2.4x 3.1x / 2.1x 2.7x / 2.3x 2.7x / 2.3x 2.4x / 1.5x
Sparse Int 10M 2.1x / 2.4x 1.9x / 2.4x 1.8x / 2.4x 1.7x / 2.5x 1.8x / 2.3x
String 100K 3.7x / 3.1x 2.0x / 2.3x 3.5x / 3.3x 2.3x / 3.2x 1.8x / 1.9x
String 500K 2.3x / 2.3x 2.4x / 2.1x 2.3x / 2.3x 2.2x / 1.6x 2.2x / 1.8x
String 1M 2.7x / 2.3x 2.4x / 2.0x 2.5x / 2.4x 2.4x / 2.3x 2.4x / 1.8x
String 10M 2.7x / 2.3x 2.7x / 2.3x 2.9x / 2.4x 2.6x / 2.3x 2.7x / 2.3x

Memory — Linux

Scenario PHP 8.1 PHP 8.2 PHP 8.3 PHP 8.4 PHP 8.5
Sparse Int 100K 0.26x 0.26x 0.26x 0.26x 0.26x
Sparse Int 500K 0.46x 0.46x 0.46x 0.46x 0.46x
Sparse Int 1M 0.46x 0.46x 0.46x 0.46x 0.46x
Sparse Int 10M 0.29x 0.29x 0.29x 0.29x 0.29x
String 100K 0.61x 0.61x 0.61x 0.61x 0.61x
String 500K 0.76x 0.76x 0.76x 0.76x 0.76x
String 1M 0.76x 0.76x 0.76x 0.76x 0.76x
String 10M 0.48x 0.48x 0.48x 0.48x 0.48x

Time (Write / Read) — Windows

Scenario PHP 8.1 PHP 8.2 PHP 8.3 PHP 8.4 PHP 8.5
Sparse Int 100K 5.2x / 2.8x 4.4x / 2.6x 5.0x / 2.8x 5.1x / 2.6x 4.0x / 2.0x
Sparse Int 500K 4.6x / 1.9x 4.4x / 1.8x 4.4x / 2.2x 4.3x / 2.3x 4.8x / 3.0x
Sparse Int 1M 3.1x / 1.4x 3.7x / 2.4x 3.7x / 2.9x 2.9x / 2.5x 4.7x / 2.4x
Sparse Int 10M 2.4x / 2.2x 2.8x / 2.1x 2.2x / 2.3x 2.4x / 2.5x 3.2x / 2.4x
String 100K 3.5x / 3.8x 4.1x / 4.1x 3.9x / 4.8x 3.2x / 3.5x 3.8x / 4.5x
String 500K 3.6x / 2.4x 3.0x / 2.6x 3.9x / 2.9x 3.4x / 2.5x 3.9x / 2.9x
String 1M 3.6x / 2.4x 3.2x / 2.8x 3.4x / 2.2x 3.2x / 2.2x 3.7x / 2.2x
String 10M 3.3x / 2.3x 3.3x / 3.3x 3.3x / 2.2x 3.8x / 2.8x 3.6x / 2.4x

Memory — Windows

Scenario PHP 8.1 PHP 8.2 PHP 8.3 PHP 8.4 PHP 8.5
Sparse Int 100K 0.23x 0.23x 0.23x 0.23x 0.23x
Sparse Int 500K 0.46x 0.46x 0.46x 0.46x 0.46x
Sparse Int 1M 0.46x 0.46x 0.46x 0.46x 0.46x
Sparse Int 10M 0.29x 0.29x 0.29x 0.29x 0.29x
String 100K 0.51x 0.51x 0.51x 0.51x 0.51x
String 500K 0.76x 0.76x 0.76x 0.76x 0.76x
String 1M 0.76x 0.76x 0.76x 0.76x 0.76x
String 10M 0.48x 0.48x 0.48x 0.48x 0.48x
Raw benchmark data

Write Time — Linux

Scenario Subject PHP 8.1 PHP 8.2 PHP 8.3 PHP 8.4 PHP 8.5
Sparse Int 100K Judy 0.0120s 0.0120s 0.0121s 0.0121s 0.0122s
Sparse Int 100K PHP Array 0.0040s 0.0041s 0.0042s 0.0042s 0.0042s
Sparse Int 500K Judy 0.0633s 0.0690s 0.0629s 0.0632s 0.0696s
Sparse Int 500K PHP Array 0.0192s 0.0230s 0.0207s 0.0226s 0.0221s
Sparse Int 1M Judy 0.1301s 0.1962s 0.1367s 0.1708s 0.1700s
Sparse Int 1M PHP Array 0.0464s 0.0625s 0.0512s 0.0622s 0.0712s
Sparse Int 10M Judy 2.8192s 2.9502s 2.7308s 2.9457s 2.8077s
Sparse Int 10M PHP Array 1.3517s 1.5474s 1.5169s 1.7165s 1.5751s
String 100K Judy 0.0224s 0.0257s 0.0223s 0.0225s 0.0254s
String 100K PHP Array 0.0061s 0.0127s 0.0064s 0.0098s 0.0139s
String 500K Judy 0.1543s 0.1780s 0.1527s 0.1601s 0.1726s
String 500K PHP Array 0.0673s 0.0751s 0.0654s 0.0714s 0.0777s
String 1M Judy 0.3837s 0.4060s 0.3516s 0.3909s 0.3800s
String 1M PHP Array 0.1441s 0.1711s 0.1405s 0.1617s 0.1579s
String 10M Judy 5.3364s 5.6399s 5.3163s 5.5600s 5.6770s
String 10M PHP Array 1.9738s 2.1195s 1.8636s 2.1242s 2.1069s

Read Time — Linux

Scenario Subject PHP 8.1 PHP 8.2 PHP 8.3 PHP 8.4 PHP 8.5
Sparse Int 100K Judy 0.0094s 0.0096s 0.0095s 0.0095s 0.0094s
Sparse Int 100K PHP Array 0.0037s 0.0036s 0.0035s 0.0036s 0.0035s
Sparse Int 500K Judy 0.0565s 0.0676s 0.0552s 0.0578s 0.0574s
Sparse Int 500K PHP Array 0.0211s 0.0460s 0.0203s 0.0213s 0.0313s
Sparse Int 1M Judy 0.1400s 0.1820s 0.1383s 0.1611s 0.1478s
Sparse Int 1M PHP Array 0.0577s 0.0880s 0.0609s 0.0690s 0.0981s
Sparse Int 10M Judy 3.2545s 3.5177s 3.1825s 3.6516s 3.3517s
Sparse Int 10M PHP Array 1.3402s 1.4709s 1.3252s 1.4749s 1.4268s
String 100K Judy 0.0161s 0.0169s 0.0163s 0.0163s 0.0163s
String 100K PHP Array 0.0052s 0.0072s 0.0050s 0.0051s 0.0088s
String 500K Judy 0.1611s 0.1824s 0.1472s 0.1672s 0.1620s
String 500K PHP Array 0.0713s 0.0861s 0.0653s 0.1045s 0.0911s
String 1M Judy 0.3642s 0.4232s 0.3579s 0.4480s 0.3756s
String 1M PHP Array 0.1573s 0.2130s 0.1494s 0.1985s 0.2107s
String 10M Judy 5.6814s 5.9483s 5.5227s 6.0934s 5.9633s
String 10M PHP Array 2.4685s 2.6296s 2.3243s 2.6671s 2.5866s

Memory — Linux

Scenario Subject PHP 8.1 PHP 8.2 PHP 8.3 PHP 8.4 PHP 8.5
Sparse Int 100K Judy 1.84 mb 1.84 mb 1.85 mb 1.84 mb 1.85 mb
Sparse Int 100K PHP Array 7 mb 7 mb 7 mb 7 mb 7 mb
Sparse Int 500K Judy 9.2 mb 9.19 mb 9.19 mb 9.19 mb 9.19 mb
Sparse Int 500K PHP Array 20 mb 20 mb 20 mb 20 mb 20 mb
Sparse Int 1M Judy 18.36 mb 18.36 mb 18.34 mb 18.38 mb 18.38 mb
Sparse Int 1M PHP Array 40 mb 40 mb 40 mb 40 mb 40 mb
Sparse Int 10M Judy 183.6 mb 183.59 mb 183.53 mb 183.61 mb 183.6 mb
Sparse Int 10M PHP Array 640 mb 640 mb 640 mb 640 mb 640 mb
String 100K Judy 3.05 mb 3.05 mb 3.05 mb 3.05 mb 3.05 mb
String 100K PHP Array 5 mb 5 mb 5 mb 5 mb 5 mb
String 500K Judy 15.26 mb 15.26 mb 15.26 mb 15.26 mb 15.26 mb
String 500K PHP Array 20 mb 20 mb 20 mb 20 mb 20 mb
String 1M Judy 30.52 mb 30.52 mb 30.52 mb 30.52 mb 30.52 mb
String 1M PHP Array 40 mb 40 mb 40 mb 40 mb 40 mb
String 10M Judy 305.18 mb 305.18 mb 305.18 mb 305.18 mb 305.18 mb
String 10M PHP Array 640 mb 640 mb 640 mb 640 mb 640 mb

Write Time — Windows

Scenario Subject PHP 8.1 PHP 8.2 PHP 8.3 PHP 8.4 PHP 8.5
Sparse Int 100K Judy 0.0287s 0.0279s 0.0288s 0.0291s 0.0284s
Sparse Int 100K PHP Array 0.0055s 0.0063s 0.0058s 0.0057s 0.0071s
Sparse Int 500K Judy 0.1546s 0.1498s 0.1491s 0.1556s 0.1484s
Sparse Int 500K PHP Array 0.0334s 0.0342s 0.0336s 0.0366s 0.0310s
Sparse Int 1M Judy 0.3276s 0.3145s 0.3197s 0.3380s 0.3505s
Sparse Int 1M PHP Array 0.1050s 0.0843s 0.0854s 0.1166s 0.0750s
Sparse Int 10M Judy 5.2981s 4.9400s 4.7399s 5.7676s 5.3483s
Sparse Int 10M PHP Array 2.1996s 1.7381s 2.1667s 2.3947s 1.6696s
String 100K Judy 0.0491s 0.0469s 0.0517s 0.0512s 0.0479s
String 100K PHP Array 0.0141s 0.0115s 0.0134s 0.0158s 0.0127s
String 500K Judy 0.3141s 0.3005s 0.3347s 0.3318s 0.3411s
String 500K PHP Array 0.0866s 0.1018s 0.0865s 0.0981s 0.0869s
String 1M Judy 0.7097s 0.6674s 0.6750s 0.7339s 0.6910s
String 1M PHP Array 0.1986s 0.2066s 0.1959s 0.2289s 0.1856s
String 10M Judy 9.2957s 9.6433s 8.9262s 11.1973s 9.1910s
String 10M PHP Array 2.7944s 2.9101s 2.7159s 2.9121s 2.5889s

Read Time — Windows

Scenario Subject PHP 8.1 PHP 8.2 PHP 8.3 PHP 8.4 PHP 8.5
Sparse Int 100K Judy 0.0155s 0.0166s 0.0154s 0.0159s 0.0152s
Sparse Int 100K PHP Array 0.0056s 0.0064s 0.0056s 0.0060s 0.0076s
Sparse Int 500K Judy 0.1098s 0.0942s 0.0861s 0.1063s 0.0972s
Sparse Int 500K PHP Array 0.0576s 0.0516s 0.0384s 0.0471s 0.0323s
Sparse Int 1M Judy 0.2415s 0.2310s 0.2799s 0.3278s 0.2457s
Sparse Int 1M PHP Array 0.1669s 0.0970s 0.0949s 0.1310s 0.1023s
Sparse Int 10M Judy 4.2011s 4.4128s 4.0693s 4.9173s 4.3228s
Sparse Int 10M PHP Array 1.9397s 2.0691s 1.7967s 1.9485s 1.7724s
String 100K Judy 0.0297s 0.0315s 0.0342s 0.0365s 0.0296s
String 100K PHP Array 0.0078s 0.0077s 0.0071s 0.0104s 0.0066s
String 500K Judy 0.2369s 0.2404s 0.2553s 0.2889s 0.2675s
String 500K PHP Array 0.0968s 0.0914s 0.0888s 0.1137s 0.0915s
String 1M Judy 0.5782s 0.5649s 0.5026s 0.6166s 0.5410s
String 1M PHP Array 0.2437s 0.2042s 0.2263s 0.2797s 0.2461s
String 10M Judy 7.7349s 8.5389s 7.3758s 9.4160s 7.5985s
String 10M PHP Array 3.4063s 2.6254s 3.3070s 3.3146s 3.1032s

Memory — Windows

Scenario Subject PHP 8.1 PHP 8.2 PHP 8.3 PHP 8.4 PHP 8.5
Sparse Int 100K Judy 1.84 mb 1.84 mb 1.85 mb 1.84 mb 1.85 mb
Sparse Int 100K PHP Array 8 mb 8 mb 8 mb 8 mb 8 mb
Sparse Int 500K Judy 9.2 mb 9.18 mb 9.17 mb 9.19 mb 9.17 mb
Sparse Int 500K PHP Array 20 mb 20 mb 20 mb 20 mb 20 mb
Sparse Int 1M Judy 18.38 mb 18.36 mb 18.35 mb 18.38 mb 18.36 mb
Sparse Int 1M PHP Array 40 mb 40 mb 40 mb 40 mb 40 mb
Sparse Int 10M Judy 183.58 mb 183.6 mb 183.64 mb 183.56 mb 183.61 mb
Sparse Int 10M PHP Array 640 mb 640 mb 640 mb 640 mb 640 mb
String 100K Judy 3.05 mb 3.05 mb 3.05 mb 3.05 mb 3.05 mb
String 100K PHP Array 6 mb 6 mb 6 mb 6 mb 6 mb
String 500K Judy 15.26 mb 15.26 mb 15.26 mb 15.26 mb 15.26 mb
String 500K PHP Array 20 mb 20 mb 20 mb 20 mb 20 mb
String 1M Judy 30.52 mb 30.52 mb 30.52 mb 30.52 mb 30.52 mb
String 1M PHP Array 40 mb 40 mb 40 mb 40 mb 40 mb
String 10M Judy 305.18 mb 305.18 mb 305.18 mb 305.18 mb 305.18 mb
String 10M PHP Array 640 mb 640 mb 640 mb 640 mb 640 mb

- Hoist JSLI call + PJERR check out of the if/else for STRING_TO_INT
  vs STRING_TO_MIXED, reducing duplication
- Remove redundant judy_free_array_internal() in alloc_error path;
  zval_ptr_dtor already triggers free_obj cleanup
@orieg orieg merged commit 4821042 into main Feb 28, 2026
13 checks passed
@orieg orieg deleted the feat/slice branch February 28, 2026 00:42
orieg added a commit that referenced this pull request Feb 28, 2026
All four phases of the performance overhaul and feature extensions
(issue #37) are complete:
- Phase 1: Fast ZPP migration (PR #38)
- Phase 2: BITSET set operations (PR #39)
- Phase 3: Range slicing (PR #40)
- Phase 4: JsonSerializable + serialize/unserialize (PR #41)
@orieg orieg mentioned this pull request Feb 28, 2026
2 tasks
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.

1 participant