Identifier case
identifierCase sets the case of unquoted names: tables, columns, aliases, schemas.
Default: preserve, and we recommend leaving it there. A keyword is a word of the language,
but a name is an object that already exists in your database, spelled one way. Re-casing names
rewrites the query rather than its layout:
select 42 as TotalAmount -- duckdb returns a column called TotalAmount
select 42 as totalamount -- lower-cased, it returns one called totalamount
- It renames result columns on every engine that keeps the spelling you wrote: DuckDB, SQLite, Spark, Databricks, Trino and SQL Server. A dashboard reading the old name breaks.
- It reaches words that only look like names. A JSON key and a path into cloud storage are data, and data is compared exactly.
- Whether a name is compared exactly can depend on the database rather than on the SQL, which no formatter can see.
Other formatters agree: sql-formatter marks its identifierCase experimental and not recommended,
and pgFormatter leaves identifiers unchanged unless asked.
Set lower or upper where the engine folds every unquoted name anyway, so that the case in the
file is cosmetic. PostgreSQL folds to lower, Oracle and Snowflake to upper; there the setting makes
MyCol and mycol look like the one name they are. Quoted names are never re-cased on any dialect.
PostgreSQL, default and lower
SELECT MyCol FROM MyTable tSELECT MyCol FROM MyTable tSELECT MyCol FROM MyTable tSELECT mycol FROM mytable tWhere it is still unsafe, even where dfmt allows it. Each of these needs something the formatter cannot see in the text.
- A JSON field read with a dot. DuckDB's
j.UserNameis a key lookup ifjholds JSON and a struct field ifjholds a struct: the same syntax, told apart only by the column's type. Keys are compared exactly and struct fields are not, so on DuckDB a setidentifierCasecan lower a key and turn the value into NULL. Write the key quoted, or leave the setting atpreserve. - A Snowflake stage path.
copy into t from @MyStage/Data/Year=2024/is a path into cloud storage, whereData/anddata/are different prefixes. dfmt reads the path's words as names, so a setidentifierCasere-cases them. Its spacing is safe: the path is one lexical unit and stays whole. - A case-sensitive collation. SQL Server compares names by the database's collation, and a
database created with a
_CS_collation compares them exactly. Nothing in the file says which database the SQL will run against.
Where it is refused. When you do set it, dfmt applies it only where the engine compares every
kind of name without regard to case, and only to the letters the engine folds: PostgreSQL folds A
to Z and nothing else, so Ä stays Ä. It is ignored, with a note, on the engines that compare
some kind of name exactly as written: BigQuery (dataset and table names), MySQL, MariaDB,
SingleStore, Doris, StarRocks and PlanetScale (table names), ClickHouse (everything); and on an
engine whose rules nobody has measured: Athena, Fabric, Synapse and Dune. TiDB speaks MySQL's
protocol and shares its grammar, and yet T finds t there at every role, measured on the engine,
so it is allowed where its family is refused. Each dialect states its own answer rather than
inheriting a relative's.
A path into semi-structured data is not a name
In Snowflake, v:UserName reads the key UserName out of a VARIANT, and keys are compared exactly.
v:username returns NULL. The column v is a name; the keys after it are not.
select V:UserName, V:Address.City from tselect v:UserName, v:Address.City from t