feat!: port the safe-byte-cutoff algorithm from nanoid v6 #22 - #26
Conversation
539a0d5 to
802804e
Compare
Replace the pre-v6 power-of-two mask rejection with the modulo-cutoff rejection that ai/nanoid introduced in v6. Random bytes below 256 - (256 % alphabetLength) are accepted and mapped to a symbol with a modulo; higher bytes are rejected to avoid modulo bias. Benefits: - Far fewer rejected bytes for non-power-of-two alphabets. A 33-symbol alphabet now accepts 231 of 256 byte values instead of 33 of 64, measured 29% faster on PostgreSQL 17 (100k IDs: 0.91s vs 1.28s). - Single-symbol alphabets work now. Previously nanoid(5, 'a') failed with "cannot take logarithm of zero". - The step size formula accounts for the rejection rate itself, so the default additional bytes factor of 1.6 is a good fit for every alphabet. The README section on calculating a custom factor is replaced accordingly. Uniformity verified with chi-square tests: 30.1 (33 symbols, df=32) and 50.5 (64 symbols, df=63) over 200k+ generated characters. BREAKING CHANGE: the third parameter of nanoid_optimized() changed its meaning from a bitmask to the byte cutoff. Callers must pass 256 - (256 % length(alphabet)) instead of (2^n)-1, e.g. 256 instead of 63 for the default 64-symbol alphabet. The nanoid_optimized() calls in the regression tests are updated accordingly.
802804e to
661c853
Compare
|
@coderabbitai full review |
✅ Action performedFull review finished. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthrough
ChangesSafe byte cutoff algorithm and upgrade handling
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant nanoid
participant nanoid_optimized
participant gen_random_bytes
nanoid->>nanoid: Compute cutoff and step
nanoid->>nanoid_optimized: Pass generation parameters
nanoid_optimized->>gen_random_bytes: Request random bytes
gen_random_bytes-->>nanoid_optimized: Return random bytes
nanoid_optimized->>nanoid_optimized: Reject bytes at or above cutoff
nanoid_optimized-->>nanoid: Return generated identifier
Possibly related issues
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@nanoid.sql`:
- Around line 83-86: Preserve backward compatibility for the existing
nanoid_optimized() four-argument API: do not reinterpret its third argument from
the legacy mask to cutoff. Keep the current cutoff-based implementation under a
new versioned function or add a deprecated wrapper that accepts the old mask
semantics and translates or delegates appropriately, while directing callers
toward the new API.
In `@README.md`:
- Around line 95-99: Update the README section describing nanoid() batching to
show the implemented step calculation as min(1024, ceil(additionalBytesFactor *
256 * size / cutoff)). State that once the 1024-byte cap is reached, increasing
additionalBytesFactor cannot further reduce follow-up batches.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 2986b16f-bdb1-4b7a-a867-8481786f9f7d
📒 Files selected for processing (4)
README.mddev/test/regression_tests.sqldev/test/unit_tests.sqlnanoid.sql
…#22 The whole script now runs in one transaction, so a DROP blocked by dependent objects rolls back atomically instead of leaving a partially upgraded install (an old nanoid_optimized() receiving a cutoff as mask would generate constant ids). The current nanoid() signature is no longer dropped (CREATE OR REPLACE upgrades it in place, also under dependent column defaults), the 2022-era nanoid(int, text) signature is dropped to keep positional calls unambiguous, the step cap moved before the int cast (least), and the test suite gained an upgrade-path stage per PostgreSQL version.
|
@coderabbitai review |
✅ Action performedReview finished.
|
|
@coderabbitai full review |
✅ Action performedFull review finished. You're currently rate limited under our Fair Usage Limits Policy. Your recent PR review activity is in the 95th percentile or higher among CodeRabbit users, so adaptive limits apply. Your next review will be available in 8 minutes. |
|
@coderabbitai full review |
✅ Action performedFull review finished. |
…matrix #30 (#31) ## Summary Keeps the multi-version test suite honest about "latest minor of every major" and extends it to the PostgreSQL 19 prerelease: - `run_tests.sh` now pulls the image before each version run (native first, amd64 fallback); when the pull fails (offline), it falls back to the local cache with a note. A stale local cache had been silently testing 14.20 and 16.13 while 14.23 and 16.14 were the released latest minors (checked against the [PostgreSQL release archive](https://www.postgresql.org/docs/release/) on 2026-07-14). - The default matrix gains `19beta1` (PostgreSQL 19 Beta 1, released 2026-06-04, official `postgres:19beta1-alpine` image). The inline comment says to bump the prerelease as new betas/RCs land and to replace it with plain `19` at GA. - The README test section documents both behaviors. The documented PostgreSQL 9.6 minimum was re-verified as part of this work and stays: installation on 9.5.25 fails with `syntax error at or near "PARALLEL"` (the `PARALLEL` clause of `CREATE FUNCTION` exists since 9.6), and the suite passes on 9.6.24. ## Verification - `bash -n` passes - `dev/test/run_tests.sh 9.6 14 19beta1` passes: 14 now reports server 14.23 (freshly pulled latest minor instead of the stale cached 14.20), 9.6 exercises the amd64 pull fallback, and the current `nanoid.sql` passes the full unit and regression suites on 19beta1 ## Note Expect a small textual conflict with #26 in `run_tests.sh` and the README test section (the upgrade-path test stage added there touches adjacent lines); whichever lands second gets rebased as part of the usual stack maintenance. Closes #30 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Tests** * Expanded PostgreSQL compatibility testing to include versions 9.6 through 19 prerelease. * Tests now pull the latest minor release for each major version when available. * Added offline support by falling back to locally cached Docker images when pulls fail. * Updated documentation and test instructions to reflect the broader test matrix. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
# Conflicts: # README.md # dev/test/run_tests.sh # nanoid.sql
Summary
Replaces the pre-v6 power-of-two mask rejection with the modulo-cutoff rejection that ai/nanoid introduced in v6: random bytes below
256 - (256 % alphabetLength)are accepted and mapped with a modulo, higher bytes are rejected to avoid modulo bias.nanoid(5, 'a')previously crashed with "cannot take logarithm of zero"Breaking change
The third parameter of
nanoid_optimized()changes its meaning from a bitmask to the byte cutoff: pass256 - (256 % length(alphabet))instead of(2^n)-1, e.g.256instead of63for the default 64-symbol alphabet. Thenanoid_optimized()calls in the regression tests are updated accordingly.Verification
nanoid(102401)and the Introducing functionsCOSTfor more parallel operations? #16 parallel scenariosCloses #22
Summary by CodeRabbit
Improvements
Documentation
nanoid_optimizedparameter documentation (mask→cutoff).Tests