Inflector
String case transformation and English inflection inside SQL.
On this page
Technical Overview
Rails-style string shaping, without the Ruby
Inflector brings the string-shaping toolkit that Rails and ActiveSupport made famous into DuckDB SQL: convert between naming conventions, pluralize and singularize English words, and rewrite struct keys or whole result sets in one call — without dropping into Python or writing a UDF. The transforms are mechanical and deterministic, which is exactly what column-naming and ETL normalization want. What that mechanical, English-centric design does not handle is the part worth reading first.
Caveats — read these first
Inflector is intentionally a rule engine, not a linguist. The transforms are mechanical and English-only, which is the right trade-off for naming work but a trap if you expect semantic or multilingual behavior.
-
•
Pluralization is English-only: Singular/plural rules come from the upstream
cruetcrate, whose ruleset and irregular-word table are English. It handles real English irregulars well —person↔people,mouse↔mice,analysis↔analyses— but has no awareness of French, German, Spanish, or any other language. Non-English input silently produces mechanical English-rule output. For multilingual content, inflect upstream and store the result. -
•
Case transforms are mechanical, not semantic: The tokenizer treats acronyms as runs of capitals, so
inflector_to_snake_case('XMLHttpRequest')yieldsxml_http_request. That is correct for column-naming work; if you need acronym-preservation rules tuned to your domain, do that step in application code first. -
•
Pluralization isn't always reversible:
people→personround-trips fine, but English has true ambiguities —datais both singular and plural in modern usage, andspeciesandseriesare unchanged. Don't round-trip a value through plural-then-singular and assume identity. -
•
Single-character tokens are skipped: The case tokenizer requires a two-character minimum and ignores 1-character runs, which matters for inputs like
aBor single-letter prefixes — those tokens are dropped rather than preserved.
How it works
Inflector is a thin DuckDB binding over the cruet and convert_case Rust crates — the same Rails / ActiveSupport-Inflector heritage, exposed through DuckDB's function system. The interesting part is not the catalog of helpers (those are enumerated in the Function Reference below) but the dispatch model: how the same conceptual operation is reached three different ways depending on what you're transforming.
-
•
Upstream provenance: The actual rule engine — case tokenization, pluralization, the irregular-word tables, the ActiveSupport-style helpers — lives in
cruetandconvert_case. Inflector is a binding layer that surfaces those crates as DuckDB functions, so behavior tracks the upstream crates rather than being reimplemented. -
•
Scalar functions for a known target: When the target case is known at parse time, there's a dedicated scalar function per case, plus a matching
is_*predicate for routing a value based on its current shape. String in, string out — vectorized like any other DuckDB scalar. -
•
One polymorphic dispatcher across shapes:
inflecttakes the case as a runtime argument and is polymorphic on its payload: aVARCHARreturns a transformed string, aSTRUCTreturns the same struct with every key renamed (values untouched), and a parenthesized query becomes a table function that rewrites every column name. Same function, three shapes — the headline path for normalizing a whole result set in one call.
Inflector vs. RapidFuzz
Both extensions live in the Matching & casing group, but they answer different questions — and the line between them is clean.
-
•
Inflector: deterministic transformation: Reach for Inflector when you know exactly what shape you want —
snake_case,kebab-case, plural, a foreign-key name — and need to mechanically produce it from a single input. The output is fully determined by the input and the chosen rule. -
•
RapidFuzz: fuzzy comparison: Reach for
rapidfuzzwhen you have two strings and want a similarity score — record linkage, dedupe, spell-check. That's comparison, not transformation. The two compose well: normalize naming with Inflector, then fuzzy-match across the normalized values with RapidFuzz.
Deep Dive
Technical Details
The one-call rename
The single most useful pattern: take a CSV or JSON file with camelCase headers and snake-case the entire result set in one statement, no per-column boilerplate:
SELECT *FROM inflect('snake', (FROM read_csv('https://example.com/users.csv')));-- firstName,lastName,emailAddress → first_name,last_name,email_addressinflect is polymorphic on its second argument — pass it a VARCHAR, a STRUCT, or a parenthesized query. Same function name, three shapes:
-- 1. A single stringSELECT inflect('snake', 'helloWorld'); -- 'hello_world'-- 2. A struct — every key renamed, values untouchedSELECT inflect('snake', {'firstName':'John','lastName':'Doe'});-- {'first_name': 'John', 'last_name': 'Doe'}-- 3. A query result — every column header renamedSELECT * FROM inflect('kebab', (FROM read_json('events.json')));That’s the headline feature. The rest of the surface is Rails / ActiveSupport-style scalar helpers for individual values.
inflector_to_plural and inflector_to_singular wrap the cruet Rust crate, whose ruleset and irregular-word table are English. They handle real English irregulars well — person ↔ people, mouse ↔ mice, analysis ↔ analyses, quiz ↔ quizzes — but they have no awareness of French, German, Spanish, or any other language. Pass non-English input and you’ll get a mechanical English-rule output, silently. For multilingual content, do inflection upstream and store the result.
A handful of edge cases inside English are also worth knowing:
- Modern uncountables —
data,species,series— round-trip irregularly. - Mechanical case tokenization —
inflector_to_snake_case('XMLHttpRequest')yieldsxml_http_request. Right for column work; tune in app code if you need different acronym rules. - Single-character tokens are skipped. The tokenizer requires a 2-character minimum, so
aB⇒b-prefixed tokens are dropped.
Case formats supported
Every shape Rails / ActiveSupport ships, plus the obvious extras:
| Format | Example | To-function |
|---|---|---|
camelCase |
helloWorld |
inflector_to_camel_case |
PascalCase / ClassCase |
HelloWorld |
inflector_to_pascal_case / inflector_to_class_case |
snake_case |
hello_world |
inflector_to_snake_case |
kebab-case |
hello-world |
inflector_to_kebab_case |
Train-Case |
Hello-World |
inflector_to_train_case |
SCREAMING_SNAKE_CASE |
HELLO_WORLD |
inflector_to_screamingsnake_case |
Title Case |
Hello World |
inflector_to_title_case |
Sentence case |
Hello world |
inflector_to_sentence_case |
table_case (snake plural) |
user_accounts |
inflector_to_table_case |
Each has a matching inflector_is_* predicate — useful when you need to route a value based on its current shape rather than blindly transform.
Inflection helpers
Beyond pure case work:
- Pluralize / singularize —
inflector_to_plural,inflector_to_singular. - Ordinalize —
inflector_ordinalize(1→1st),inflector_deordinalize(1st→1). - Foreign keys —
inflector_to_foreign_key(Message→message_id). - Module paths —
inflector_demodulize(ActiveRecord::CoreExtensions::String→String),inflector_deconstantize(Net::HTTP→Net).
These are direct ports of the corresponding ActiveSupport::Inflector methods.
Inflector vs RapidFuzz
Both extensions live in the Matching & casing group, but they answer different questions:
- Use Inflector when you know exactly what shape you want (
snake_case,kebab-case, plural, foreign-key) and need to mechanically produce it. Single-input, deterministic output. - Use
rapidfuzzwhen you have two strings and want a similarity score — record linkage, dedupe, spell-check. Fuzzy comparison, not transformation. - Both can coexist — normalize naming with Inflector, then fuzzy-match across normalized values with RapidFuzz.
Source
Inflector is implemented in C++ with a Rust binding (cruet + convert_case) for the actual rule engine. Source at github.com/Query-farm/inflector.
Install
INSTALL inflector FROM community;
LOAD inflector;
Quick Start
Single-value case transforms
SELECT inflector_to_camel_case('hello_world'); -- helloWorld
SELECT inflector_to_snake_case('HelloWorld'); -- hello_world
SELECT inflector_to_kebab_case('helloWorld'); -- hello-world
Pluralize / singularize English words
SELECT inflector_to_plural('person'); -- people
SELECT inflector_to_singular('mice'); -- mouse
Rewrite every key in a struct / result set
SELECT inflect('snake', {'firstName':'John','lastName':'Doe'});
SELECT * FROM inflect('kebab', (FROM read_csv('data.csv')));
Reference
Extension Contents
Quick reference to all available functions and settings organized by category.
| Name | Type | Description |
|---|---|---|
|
Bulk
Apply a transform to every key in a struct or every column name in a query result, in a single call. The headline feature for normalizing API responses or migrating between naming conventions. |
||
| inflect() |
Apply a named transform ('camel', 'snake', 'pascal', 'kebab', etc.) to one of: a string, every key in a struct, or every column name in a query result.
|
|
|
Case Transformation
Convert strings between naming conventions: snake_case, camelCase, PascalCase, kebab-case, Train-Case, SCREAMING_SNAKE_CASE, Title Case, Sentence case. Also raw upper/lower transforms. |
||
| inflector_to_camel_case() | Convert to camelCase. | |
| inflector_to_class_case() | Convert to ClassCase (PascalCase singular). | |
| inflector_to_kebab_case() | Convert to kebab-case (hyphen-separated). | |
| inflector_to_lower_case() | Lowercase every character. | |
| inflector_to_pascal_case() | Convert to PascalCase. | |
| inflector_to_screamingsnake_case() | Convert to SCREAMING_SNAKE_CASE (caps with underscores). | |
| inflector_to_sentence_case() | Convert to Sentence case — first letter capital, rest lowercase, words space-separated. | |
| inflector_to_snake_case() | Convert to snake_case. | |
| inflector_to_title_case() | Convert to Title Case — every word capitalized, space-separated. | |
| inflector_to_train_case() | Convert to Train-Case (capitalized hyphenated). | |
| inflector_to_upper_case() | Uppercase every character. | |
|
Configuration
|
||
| inflector_acronyms | List of acronyms preserved as uppercase in case conversions (e.g., HTML, API) | |
|
Functions
|
||
| inflector_deconstantize() | Removes the rightmost segment from a constant expression | |
| inflector_demodulize() | Removes the module part from a fully qualified name | |
| inflector_deordinalize() | Removes the ordinal suffix from a string (1st -> 1) | |
| inflector_is_camel_case() | Returns true if the string is in camelCase format | |
| inflector_is_class_case() | Returns true if the string is in ClassCase (PascalCase) format | |
| inflector_is_foreign_key() | Returns true if the string is in foreign key format (ends with _id) | |
| inflector_is_kebab_case() | Returns true if the string is in kebab-case format | |
| inflector_is_pascal_case() | Returns true if the string is in PascalCase format | |
| inflector_is_screamingsnake_case() | Returns true if the string is in SCREAMING_SNAKE_CASE format | |
| inflector_is_sentence_case() | Returns true if the string is in Sentence case format | |
| inflector_is_snake_case() | Returns true if the string is in snake_case format | |
| inflector_is_table_case() | Returns true if the string is in table_case format (snake_case plural) | |
| inflector_is_title_case() | Returns true if the string is in Title Case format | |
| inflector_is_train_case() | Returns true if the string is in Train-Case format | |
| inflector_ordinalize() | Converts a number string to its ordinal form (1st, 2nd, 3rd, etc.) | |
| inflector_to_foreign_key() | Converts a class name to a foreign key column name | |
|
Inflection
English plural / singular conversion. Handles irregulars ( |
||
| inflector_to_plural() | Pluralize an English word — handles irregulars (person→people, mouse→mice). | |
| inflector_to_singular() | Singularize an English word. | |
|
Naming Helpers
Convenience builders for Rails-style naming — table_case, foreign-key generation, ClassCase, and module-path conversion. |
||
| inflector_to_table_case() | Rails-style 'table_case' — snake_case + plural. | |
No extension contents match that search.
API Reference
Function Documentation
Configuration
Settings
Configure the inflector extension behavior using these settings.
inflector_acronyms
List of acronyms preserved as uppercase in case conversions (e.g., HTML, API)
[]
Practical Examples
Cookbook
Real-world recipes and patterns for common use cases.
Quick reference
| Need | Pick |
|---|---|
| Convert one string between cases | inflector_to_<case> or inflect(case_name, value) |
| Rewrite every key in a struct | inflect('snake', struct_col) |
| Rewrite every column of a query | SELECT * FROM inflect('snake', (FROM ...)) |
| Pluralize / singularize an English word | inflector_to_plural / inflector_to_singular |
| Rails-style table name from class name | inflector_to_table_case |
| Class name → foreign-key column | inflector_to_foreign_key |
| Detect what case a string is in | inflector_is_* predicates |
| Number → ordinal label | inflector_ordinalize |
Single-string transforms
SELECT inflector_to_camel_case('hello_world'); -- 'helloWorld'SELECT inflector_to_snake_case('HelloWorld'); -- 'hello_world'SELECT inflector_to_pascal_case('hello_world'); -- 'HelloWorld'SELECT inflector_to_kebab_case('helloWorld'); -- 'hello-world'SELECT inflector_to_train_case('helloWorld'); -- 'Hello-World'SELECT inflector_to_screamingsnake_case('helloWorld'); -- 'HELLO_WORLD'SELECT inflector_to_title_case('hello_world'); -- 'Hello World'SELECT inflector_to_sentence_case('helloWorld'); -- 'Hello world'inflect(case_name, value) is the dispatching equivalent — same result, lets you parameterize the case at runtime:
SELECT inflect('camel', 'hello_world'); -- 'helloWorld'SELECT inflect(target_case, column_value) -- target_case from a config tableFROM ...;Pluralize / singularize (English only)
SELECT inflector_to_plural('person'); -- 'people'SELECT inflector_to_plural('child'); -- 'children'SELECT inflector_to_plural('quiz'); -- 'quizzes'SELECT inflector_to_singular('mice'); -- 'mouse'SELECT inflector_to_singular('analyses'); -- 'analysis'These wrap the cruet Rust crate’s English ruleset. Non-English input gets English-rule output, silently — handle multilingual text upstream.
Rewrite struct keys
SELECT inflect('snake', {'firstName':'John','lastName':'Doe'});-- {'first_name': 'John', 'last_name': 'Doe'}SELECT inflect('camel', {'first_name':'John','last_name':'Doe'});-- {'firstName': 'John', 'lastName': 'Doe'}Apply against a struct column to normalize a stored API payload in place:
SELECT id, inflect('snake', payload) AS payload_snakeFROM api_events;Rewrite every column of a query
The killer ETL pattern — normalize the headers of a remote file in one statement:
-- camelCase CSV → snake_case columnsSELECT * FROM inflect('snake', (FROM read_csv('https://example.com/users.csv')));-- Mixed-casing JSON → kebab-case columnsSELECT * FROM inflect('kebab', (FROM read_json('events.json')));Wrap with CREATE TABLE AS to land it in the warehouse with clean names:
CREATE TABLE users ASSELECT * FROM inflect('snake', (FROM read_csv('users.csv')));Build Rails-style derivations
The classic class ↔ table ↔ foreign-key triangle:
SELECT inflector_to_table_case('UserAccount'); -- 'user_accounts' (table)SELECT inflector_to_class_case('user_accounts'); -- 'UserAccount' (class)SELECT inflector_to_foreign_key('Message'); -- 'message_id' (FK column)Useful when emitting DDL or migration code from a model registry:
SELECT class_name, inflector_to_table_case(class_name) AS table_name, inflector_to_foreign_key(class_name) AS fk_columnFROM models;Detect-then-transform
Use inflector_is_* predicates to route values whose case you don’t trust on the way in:
SELECT raw, CASE WHEN inflector_is_snake_case(raw) THEN raw WHEN inflector_is_camel_case(raw) THEN inflector_to_snake_case(raw) WHEN inflector_is_pascal_case(raw) THEN inflector_to_snake_case(raw) WHEN inflector_is_kebab_case(raw) THEN replace(raw, '-', '_') ELSE inflector_to_snake_case(raw) END AS normalizedFROM mixed_input;Ordinalize numbers
SELECT inflector_ordinalize('1'); -- '1st'SELECT inflector_ordinalize('22'); -- '22nd'SELECT inflector_ordinalize('103'); -- '103rd'SELECT inflector_deordinalize('1st'); -- '1'Useful for report labels: 'Top ' || inflector_ordinalize(rank::VARCHAR) || ' result'.
Strip module / namespace prefixes
SELECT inflector_demodulize('ActiveRecord::CoreExtensions::String'); -- 'String'SELECT inflector_deconstantize('Net::HTTP'); -- 'Net'For working with stack traces or class names imported from systems that use Ruby- or C++-style namespaces.
Pair with RapidFuzz for normalize-then-match
If you’re deduping records that arrive in mixed cases, normalize first, then fuzzy-match — the rapidfuzz extension is the sibling tool:
SELECT a.id, b.id, rapidfuzz_ratio( inflector_to_snake_case(a.name), inflector_to_snake_case(b.name) ) AS simFROM left_records a, right_records bWHERE rapidfuzz_ratio( inflector_to_snake_case(a.name), inflector_to_snake_case(b.name) ) > 90;This is why both extensions share the Matching & casing group — they compose.
Platform Support
Compatibility
Extension availability may vary by platform and DuckDB version. Check below to ensure this extension supports your environment before installation.
Quick Facts
Platforms
- Linux x86_64 aarch64
- Linux (musl) Not available
- macOS Intel Apple Silicon
- Windows x86_64
- WASM eh mvp threads
Compiled binary sizes
| Platform | Architecture | Size |
|---|---|---|
| Linux | x86_64 | 4.94 MB |
| Linux | aarch64 | 4.52 MB |
| macOS | Intel | 2.89 MB |
| macOS | Apple Silicon | 2.55 MB |
| Windows | x86_64 | 8.01 MB |
| WASM | eh | 500.4 KB |
| WASM | mvp | 571.8 KB |
| WASM | threads | 336.1 KB |
Compressed download size from the Haybarn extension repository.
DuckDB & Haybarn
Release calendar- DuckDB v1.5.5 Haybarn 1.5.5-rc1 Supported