feat: support 256-symbol alphabets and an optional prefix parameter #20 #18#24
Conversation
3fd3674 to
eae4459
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.
eae4459 to
fd629d4
Compare
802804e to
661c853
Compare
|
@coderabbitai full review |
✅ Action performedFull review finished. |
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
Disabled knowledge base sources:
📝 WalkthroughWalkthroughThe ChangesNanoid enhancements
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant Caller
participant nanoid
participant nanoid_optimized
Caller->>nanoid: Call with prefix, size, and alphabet
nanoid->>nanoid: Validate alphabet and compute parameters
nanoid->>nanoid_optimized: Generate random ID portion
nanoid_optimized-->>nanoid: Return random ID portion
nanoid-->>Caller: Return prefix plus random ID
Possibly related issues
Suggested labels: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 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 `@dev/test/unit_tests.sql`:
- Around line 111-118: Update the nanoid rejection test to capture the raised
exception’s MESSAGE_TEXT and assert it contains the expected 256-symbol limit
wording, such as “bigger than 256 symbols.” Keep re-raising assert_failure, but
replace the unconditional raise_exception acceptance with validation of the
rejection message.
- Around line 102-109: Extend the `alphabet256` test loop after generating
`generated_id` to assert every character belongs to `alphabet256`, using a
membership check such as `translate(generated_id, alphabet256, '') = ''`. Keep
the existing length assertion and notice output unchanged.
🪄 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: d1bdcb9c-9f41-421a-92cb-e563ebe39a9a
📒 Files selected for processing (2)
dev/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.
The previous limit of 255 was stricter than necessary: for a 256-symbol alphabet the computed mask is 255, so every random byte maps directly to a valid alphabet index. Raise the validation limit and error message to 256, matching the upstream ai/nanoid limit, and add unit tests covering a 256-symbol alphabet plus rejection of alphabets with more than 256 symbols.
6e7a3e8 to
681163e
Compare
|
@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. |
# Conflicts: # nanoid.sql
# Conflicts: # README.md # dev/test/run_tests.sh # nanoid.sql
## Summary Adds `prefix text DEFAULT ''` as the new last parameter of `nanoid()`, prepended to the generated id for Stripe-style typed ids (`usr_`, `ord_`, ...): - `size` keeps meaning the number of random symbols; the total id length is `length(prefix) + size`, so columns must be sized accordingly (e.g. `char(25)` for `nanoid(prefix => 'usr_')`) - A `NULL` prefix behaves like an empty prefix instead of producing a `NULL` id (`coalesce(prefix, '')`) - `nanoid_optimized()` stays unchanged; the prefix is concatenated in `nanoid()` - The old signature `nanoid(int, text, float)` is dropped on upgrade, as the install script already does today - README documents the parameter, the length semantics, and a `CREATE TABLE` example Stacked on #24; the diff of this PR is the prefix commit only. ## Verification - Unit tests: positional prefix, named notation `nanoid(prefix => 'ord_')`, and `NULL` prefix; `SELECT nanoid()` behavior is unchanged (21 characters, default alphabet) - Full multi-version suite passes on PostgreSQL 9.6 through 18 - Upgrade path verified: installing the new nanoid.sql over an existing installation leaves exactly one `nanoid(size, alphabet, additionalBytesFactor, prefix)` signature ## Credits Idea by @abdirahmn1. Closes #18 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added an optional `prefix` parameter to generated Nano IDs, supporting typed identifiers such as `usr_` and `ord_`. * Prefixes are included in the final ID without reducing the requested random portion size. * Prefixes can be provided positionally or by name. * **Bug Fixes** * A `NULL` prefix now behaves like no prefix (no longer yields a `NULL` identifier). * **Documentation** * Expanded “Prefixed ids” with new SQL examples and clarifications on total ID length and `NULL` prefix behavior. * **Tests** * Added unit coverage for prefix/size interactions, pattern expectations, and edge cases. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
Summary
This PR now carries two features: the prefix work from #29 was squash-merged into this branch, so its diff is part of this PR.
256-symbol alphabets (#20). Raises the maximum alphabet length from 255 to 256 symbols (validation, error message, and inline parameter documentation). For a 256-symbol alphabet every random byte maps directly to a valid alphabet index, so the previous limit was stricter than necessary; upstream ai/nanoid allows 256 as well.
Optional prefix parameter (#18, via #29). Adds
prefix text DEFAULT ''as the new last parameter ofnanoid(), prepended to the generated id for Stripe-style typed ids (usr_,ord_, ...):sizekeeps meaning the number of random symbols; the total id length islength(prefix) + size, so columns must be sized accordingly (e.g.char(25)fornanoid(prefix => 'usr_'))NULLprefix behaves like an empty prefix instead of producing aNULLid (coalesce(prefix, ''))nanoid_optimized()stays unchanged; the prefix is concatenated innanoid()nanoid(int, text, float)is dropped on upgrade, since the signature gains the prefix parameter and positional calls must stay unambiguousCREATE TABLEexampleVerification
nanoid(prefix => 'ord_'), andNULLprefix;SELECT nanoid()behaviour is unchanged (21 characters, default alphabet)nanoid(size, alphabet, additionalBytesFactor, prefix)signatureCredits
Prefix idea by @abdirahmn1.
Closes #20
Closes #18
Summary by CodeRabbit
New Features
NULLis treated as empty.Documentation