Skip to content

feat: support 256-symbol alphabets and an optional prefix parameter #20 #18#24

Merged
nik-sta merged 12 commits into
mainfrom
feat/20-alphabet-256
Jul 20, 2026
Merged

feat: support 256-symbol alphabets and an optional prefix parameter #20 #18#24
nik-sta merged 12 commits into
mainfrom
feat/20-alphabet-256

Conversation

@nik-sta

@nik-sta nik-sta commented Jul 13, 2026

Copy link
Copy Markdown
Member

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 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, since the signature gains the prefix parameter and positional calls must stay unambiguous
  • README documents the parameter, the length semantics, and a CREATE TABLE example

Verification

  • Unit tests: a 256-symbol alphabet generates correct ids, and alphabets with more than 256 symbols are still rejected
  • Unit tests: positional prefix, named notation nanoid(prefix => 'ord_'), and NULL prefix; SELECT nanoid() behaviour is unchanged (21 characters, default alphabet)
  • Full multi-version suite (PostgreSQL 9.6 through 18) passed on the stack
  • Upgrade path verified: installing the new nanoid.sql over an existing installation leaves exactly one nanoid(size, alphabet, additionalBytesFactor, prefix) signature

Credits

Prefix idea by @abdirahmn1.

Closes #20
Closes #18

Summary by CodeRabbit

  • New Features

    • Added optional prefixes to generated Nano IDs. Prefixes do not count toward the requested ID length, and NULL is treated as empty.
    • Expanded alphabet support to up to 256 symbols.
    • Added validation that rejects alphabets exceeding 256 symbols.
  • Documentation

    • Added usage examples and guidance for prefixed IDs, including recommended column sizing.

@nik-sta nik-sta added enhancement New feature or request upstream-parity Tracks feature parity with the original ai/nanoid library labels Jul 13, 2026
@nik-sta
nik-sta force-pushed the feat/20-alphabet-256 branch from 3fd3674 to eae4459 Compare July 13, 2026 20:08
@nik-sta
nik-sta changed the base branch from main to feat/22-safe-byte-cutoff July 13, 2026 20:08
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.
@nik-sta
nik-sta force-pushed the feat/20-alphabet-256 branch from eae4459 to fd629d4 Compare July 13, 2026 20:35
@nik-sta
nik-sta force-pushed the feat/22-safe-byte-cutoff branch from 802804e to 661c853 Compare July 13, 2026 20:35
@nik-sta

nik-sta commented Jul 13, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 79a89324-7de1-4660-8572-394bba03e9dd

📥 Commits

Reviewing files that changed from the base of the PR and between 681163e and 3040bd2.

📒 Files selected for processing (3)
  • README.md
  • dev/test/unit_tests.sql
  • nanoid.sql

Disabled knowledge base sources:

  • Jira integration is disabled
  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.


📝 Walkthrough

Walkthrough

The nanoid function now accepts prefixes and custom alphabets containing up to 256 symbols. Unit tests cover prefix behavior, 256-symbol generation, and rejection of larger alphabets. The README documents prefixed ID usage and total-length semantics.

Changes

Nanoid enhancements

Layer / File(s) Summary
API and alphabet validation
nanoid.sql
The function gains a NULL-safe prefix parameter, removes an older conflicting signature during upgrades, prepends prefixes to generated values, and accepts alphabets up to 256 symbols.
Behavior and boundary tests
dev/test/unit_tests.sql
Tests cover 256-symbol alphabets, rejection beyond the limit, and positional, named, NULL, multibyte, and mixed-notation prefixes.
Prefixed ID usage documentation
README.md
Examples document prefixed calls, size and total-length behavior, NULL handling, and typed-prefix table defaults.

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
Loading

Possibly related issues

  • viascom/nanoid-mysql-mariadb#5 — Describes porting these PostgreSQL nanoid enhancements to MySQL/MariaDB.

Suggested labels: breaking-change

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes match the issue requirements by accepting 256-symbol alphabets, rejecting larger ones, updating docs, and adding tests.
Out of Scope Changes check ✅ Passed The PR stays in scope, with only the validation, docs, and test updates needed for the 256-symbol alphabet change.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title correctly captures the 256-symbol alphabet support, though it mentions an optional prefix parameter not shown in this changeset.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/20-alphabet-256

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

📥 Commits

Reviewing files that changed from the base of the PR and between 661c853 and fd629d4.

📒 Files selected for processing (2)
  • dev/test/unit_tests.sql
  • nanoid.sql

Comment thread dev/test/unit_tests.sql
Comment thread dev/test/unit_tests.sql
nik-sta added 5 commits July 13, 2026 23:53
…#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.
@nik-sta
nik-sta force-pushed the feat/20-alphabet-256 branch from 6e7a3e8 to 681163e Compare July 13, 2026 22:38
@nik-sta

nik-sta commented Jul 14, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@nik-sta

nik-sta commented Jul 14, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
✅ Action performed

Full 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.

@nik-sta

nik-sta commented Jul 14, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

## 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 -->
@nik-sta nik-sta changed the title feat: support alphabets with up to 256 symbols #20 feat: support 256-symbol alphabets and an optional prefix parameter #20 #18 Jul 20, 2026
@nik-sta
nik-sta deleted the branch main July 20, 2026 18:30
@nik-sta nik-sta closed this Jul 20, 2026
@nik-sta nik-sta reopened this Jul 20, 2026
@nik-sta
nik-sta changed the base branch from feat/22-safe-byte-cutoff to main July 20, 2026 18:31
@nik-sta
nik-sta merged commit c304264 into main Jul 20, 2026
@nik-sta
nik-sta deleted the feat/20-alphabet-256 branch July 20, 2026 18:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request upstream-parity Tracks feature parity with the original ai/nanoid library

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support custom alphabets with up to 256 symbols Support an optional prefix parameter in nanoid() for typed ids

1 participant