fooBar, foo_bar, and Foo Bar are three different column names. Convert all three to snake case and they become the same name. A case conversion library can do its job correctly and still leave the database with a schema it cannot use.

The Inflector extension works on strings, but it also renames query columns and nested struct fields. Its DuckDB 2.0 update deals with the problems that appear at that second level: duplicate names, connection-specific acronym preferences, and NULL values that need to keep their place in a nested result.

Keep every column after renaming

The table form of inflect applies a naming convention to a query’s output:

SELECT column_name
FROM (DESCRIBE SELECT * FROM inflect('snake', (
SELECT 1 AS fooBar, 2 AS foo_bar, 3 AS "Foo Bar", 4 AS foo_bar_1
)));
result · 4 rows
column_name
foo_bar
foo_bar_1
foo_bar_2
foo_bar_1_1

Names are made unique in column order, using DuckDB’s case-insensitive comparison. A collision receives _1, then _2, and so on. The last name above starts as foo_bar_1, which has already been assigned to the second column, so it receives its own suffix.

The rule is deterministic, but the order matters. If downstream code depends on the generated names, keep the input column order stable or give important columns explicit names before passing the query to inflect.

Struct fields use the same rule, recursively:

SELECT inflect('snake', {
'fooBar': 1,
'foo_bar': 2,
'nested': {'aB': 3, 'a_b': 4}
}) AS renamed;
result
renamed
{'foo_bar': 1, 'foo_bar_1': 2, 'nested': {'a_b': 3, 'a_b_1': 4}}

Previously the struct path could produce duplicate field names, which are invalid in DuckDB. A name that converts to an empty string now retains its original spelling. Values pass through unchanged.

An acronym preference belongs to one connection

Suppose one application wants HTMLParser and another wants HtmlParser. Inflector already had an acronym setting, but its implementation stored the list in a process-wide Rust variable. Setting it from one connection changed the behavior of other connections, including those using another DuckDB database in the same process.

The setting now comes from the current connection:

SET inflector_acronyms = ['HTML', 'API'];
SELECT inflector_to_pascal_case('html_api_parser') AS name;
result
name
HTMLAPIParser
RESET inflector_acronyms;
SELECT inflector_to_pascal_case('html_api_parser') AS name;
result
name
HtmlApiParser

A second connection keeps its own list throughout. The same preference applies to scalar transformations, struct field names, and the table function’s output names.

Prepared scalar calls read the setting in effect when they execute. Schema-changing transformations resolve names when the query is bound, because DuckDB needs the output schema before execution. If an application previously relied on one connection configuring acronyms for the whole process, it now needs to set them on each connection that uses them.

Labels, slugs, and class names

Three new functions cover common naming tasks that take more than changing case:

SELECT inflector_humanize('employee_salary') AS label,
inflector_parameterize('Crème Brûlée: A Recipe!') AS slug,
inflector_classify('people') AS class_name;
result
labelslugclass_name
Employee salarycreme-brulee-a-recipePerson

inflector_humanize turns an identifier into a label and removes a trailing _id: author_id becomes Author. It also respects the acronym list, so html_parser_id becomes HTML parser when HTML is configured.

inflector_parameterize removes accents where supported, lowercases the text, and joins its words with hyphens. It does not promise an ASCII-only result: 東京 タワー 2026 becomes 東京-タワー-2026. It also does not assign unique slugs. Two different titles can produce the same slug, so uniqueness still belongs in the application or table constraint.

inflector_classify singularizes and converts to PascalCase. The 2.0 branch also includes the earlier corrections for singularizing people and children.

Preserve missing values

Renaming a NULL struct now leaves it NULL. A nested NULL field retains its validity rather than turning into a value during reconstruction. Scalar transformations follow NULL-in, NULL-out behavior, including a NULL format or strip_accents argument. The table function rejects NULL options with a specific error because it needs a naming rule to determine its schema.

The Rust boundary now passes strings with their lengths, preserving embedded NUL bytes, and catches Rust panics before they can unwind through C++. Invalid input and internal failures become ordinary errors instead of misleading allocation messages.

The November update includes two-connection tests, collision and helper cases, and NULL coverage. The extension page has the full naming reference. The main upgrade checks are the generated names after a collision and any code that expected acronym settings to leak between connections.