Skip to content

DuckDB support #857

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Apr 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Erik Forsström <erik@orbiapp.io>
Gajus Kuizinas <gajus@gajus.com>
George Leslie-Waksman <waksman@gmail.com>
Grant Forsythe <grantwforsythe@gmail.com>
Hugh Cameron <hescameron@gmail.com>
htaketani <h.taketani@gmail.com>
Ian Campbell <icampbell@immuta.com>
ivan baktsheev
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
It started as a port of a [PHP Library][], but has since considerably diverged.

It supports various SQL dialects:
GCP BigQuery, IBM DB2, Apache Hive, MariaDB, MySQL, TiDB, Couchbase N1QL, Oracle PL/SQL, PostgreSQL, Amazon Redshift, SingleStoreDB, Snowflake, Spark, SQL Server Transact-SQL, Trino (and Presto).
GCP BigQuery, IBM DB2, DuckDB, Apache Hive, MariaDB, MySQL, TiDB, Couchbase N1QL, Oracle PL/SQL, PostgreSQL, Amazon Redshift, SingleStoreDB, Snowflake, Spark, SQL Server Transact-SQL, Trino (and Presto).
See [language option docs](docs/language.md) for more details.

It does not support:
Expand Down
2 changes: 2 additions & 0 deletions docs/dialect.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ The following dialects can be imported from `"sql-formatter"` module:
- `bigquery` - [GCP BigQuery][]
- `db2` - [IBM DB2][]
- `db2i` - [IBM DB2i][] (experimental)
- `duckdb` - [DuckDB][]
- `hive` - [Apache Hive][]
- `mariadb` - [MariaDB][]
- `mysql` - [MySQL][]
Expand Down Expand Up @@ -73,6 +74,7 @@ You likely only want to use this if your other alternative is to fork SQL Format
[gcp bigquery]: https://cloud.google.com/bigquery
[ibm db2]: https://www.ibm.com/analytics/us/en/technology/db2/
[ibm db2i]: https://www.ibm.com/docs/en/i/7.5?topic=overview-db2-i
[duckdb]: https://duckdb.org/
[apache hive]: https://hive.apache.org/
[mariadb]: https://mariadb.com/
[mysql]: https://www.mysql.com/
Expand Down
2 changes: 2 additions & 0 deletions docs/language.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const result = format('SELECT * FROM tbl', { language: 'sqlite' });
- `"bigquery"` - [GCP BigQuery][]
- `"db2"` - [IBM DB2][]
- `"db2i"` - [IBM DB2i][] (experimental)
- `"duckdb"` - [DuckDB][]
- `"hive"` - [Apache Hive][]
- `"mariadb"` - [MariaDB][]
- `"mysql"` - [MySQL][]
Expand Down Expand Up @@ -50,6 +51,7 @@ See docs for [dialect][] option.
[gcp bigquery]: https://cloud.google.com/bigquery
[ibm db2]: https://www.ibm.com/analytics/us/en/technology/db2/
[ibm db2i]: https://www.ibm.com/docs/en/i/7.5?topic=overview-db2-i
[duckdb]: https://duckdb.org/
[apache hive]: https://hive.apache.org/
[mariadb]: https://mariadb.com/
[mysql]: https://www.mysql.com/
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
"trino",
"presto",
"prestosql",
"snowflake"
"snowflake",
"duckdb"
],
"files": [
"dist",
Expand Down
1 change: 1 addition & 0 deletions src/allDialects.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export { bigquery } from './languages/bigquery/bigquery.formatter.js';
export { db2 } from './languages/db2/db2.formatter.js';
export { db2i } from './languages/db2i/db2i.formatter.js';
export { duckdb } from './languages/duckdb/duckdb.formatter.js';
export { hive } from './languages/hive/hive.formatter.js';
export { mariadb } from './languages/mariadb/mariadb.formatter.js';
export { mysql } from './languages/mysql/mysql.formatter.js';
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export { ConfigError } from './validateConfig.js';
export { bigquery } from './languages/bigquery/bigquery.formatter.js';
export { db2 } from './languages/db2/db2.formatter.js';
export { db2i } from './languages/db2i/db2i.formatter.js';
export { duckdb } from './languages/duckdb/duckdb.formatter.js';
export { hive } from './languages/hive/hive.formatter.js';
export { mariadb } from './languages/mariadb/mariadb.formatter.js';
export { mysql } from './languages/mysql/mysql.formatter.js';
Expand Down
216 changes: 216 additions & 0 deletions src/languages/duckdb/duckdb.formatter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
import { DialectOptions } from '../../dialect.js';
import { expandPhrases } from '../../expandPhrases.js';
import { functions } from './duckdb.functions.js';
import { dataTypes, keywords } from './duckdb.keywords.js';

const reservedSelect = expandPhrases(['SELECT [ALL | DISTINCT]']);

const reservedClauses = expandPhrases([
// queries
'WITH [RECURSIVE]',
'FROM',
'WHERE',
'GROUP BY [ALL]',
'HAVING',
'WINDOW',
'PARTITION BY',
'ORDER BY [ALL]',
'LIMIT',
'OFFSET',
// 'USING' (conflicts with 'USING' in JOIN)
'USING SAMPLE',
'QUALIFY',
// Data manipulation
// - insert:
'INSERT [OR REPLACE] INTO',
'VALUES',
'DEFAULT VALUES',
// - update:
'SET',
// other:
'RETURNING',
]);

const standardOnelineClauses = expandPhrases([
'CREATE [OR REPLACE] [TEMPORARY | TEMP] TABLE [IF NOT EXISTS]',
]);

const tabularOnelineClauses = expandPhrases([
// TABLE
// - update:
'UPDATE',
// - insert:
'ON CONFLICT',
// - delete:
'DELETE FROM',
// - drop table:
'DROP TABLE [IF EXISTS]',
// - truncate
'TRUNCATE',
// - alter table:
'ALTER TABLE',
'ADD [COLUMN] [IF NOT EXISTS]',
'ADD PRIMARY KEY',
'DROP [COLUMN] [IF EXISTS]',
'ALTER [COLUMN]',
'RENAME [COLUMN]',
'RENAME TO',
'SET [DATA] TYPE', // for alter column
'{SET | DROP} DEFAULT', // for alter column
'{SET | DROP} NOT NULL', // for alter column

// MACRO / FUNCTION
'CREATE [OR REPLACE] [TEMPORARY | TEMP] {MACRO | FUNCTION}',
'DROP MACRO [TABLE] [IF EXISTS]',
'DROP FUNCTION [IF EXISTS]',
// INDEX
'CREATE [UNIQUE] INDEX [IF NOT EXISTS]',
'DROP INDEX [IF EXISTS]',
// SCHEMA
'CREATE [OR REPLACE] SCHEMA [IF NOT EXISTS]',
'DROP SCHEMA [IF EXISTS]',
// SECRET
'CREATE [OR REPLACE] [PERSISTENT | TEMPORARY] SECRET [IF NOT EXISTS]',
'DROP [PERSISTENT | TEMPORARY] SECRET [IF EXISTS]',
// SEQUENCE
'CREATE [OR REPLACE] [TEMPORARY | TEMP] SEQUENCE',
'DROP SEQUENCE [IF EXISTS]',
// VIEW
'CREATE [OR REPLACE] [TEMPORARY | TEMP] VIEW [IF NOT EXISTS]',
'DROP VIEW [IF EXISTS]',
'ALTER VIEW',
// TYPE
'CREATE TYPE',
'DROP TYPE [IF EXISTS]',

// other
'ANALYZE',
'ATTACH [DATABASE] [IF NOT EXISTS]',
'DETACH [DATABASE] [IF EXISTS]',
'CALL',
'[FORCE] CHECKPOINT',
'COMMENT ON [TABLE | COLUMN | VIEW | INDEX | SEQUENCE | TYPE | MACRO | MACRO TABLE]',
'COPY [FROM DATABASE]',
'DESCRIBE',
'EXPORT DATABASE',
'IMPORT DATABASE',
'INSTALL',
'LOAD',
'PIVOT',
'PIVOT_WIDER',
'UNPIVOT',
'EXPLAIN [ANALYZE]',
// plain SET conflicts with SET clause in UPDATE
'SET {LOCAL | SESSION | GLOBAL}',
'RESET [LOCAL | SESSION | GLOBAL]',
'{SET | RESET} VARIABLE',
'SUMMARIZE',
'BEGIN TRANSACTION',
'ROLLBACK',
'COMMIT',
'ABORT',
'USE',
'VACUUM [ANALYZE]',
// prepared statements
'PREPARE',
'EXECUTE',
'DEALLOCATE [PREPARE]',
]);

const reservedSetOperations = expandPhrases([
'UNION [ALL | BY NAME]',
'EXCEPT [ALL]',
'INTERSECT [ALL]',
]);

const reservedJoins = expandPhrases([
'JOIN',
'{LEFT | RIGHT | FULL} [OUTER] JOIN',
'{INNER | CROSS} JOIN',
'{NATURAL | ASOF} [INNER] JOIN',
'{NATURAL | ASOF} {LEFT | RIGHT | FULL} [OUTER] JOIN',
'POSITIONAL JOIN',
'ANTI JOIN',
'SEMI JOIN',
]);

const reservedPhrases = expandPhrases([
'{ROWS | RANGE | GROUPS} BETWEEN',
'SIMILAR TO',
'IS [NOT] DISTINCT FROM',
'TIMESTAMP WITH TIME ZONE',
]);

export const duckdb: DialectOptions = {
name: 'duckdb',
tokenizerOptions: {
reservedSelect,
reservedClauses: [...reservedClauses, ...standardOnelineClauses, ...tabularOnelineClauses],
reservedSetOperations,
reservedJoins,
reservedPhrases,
supportsXor: true,
reservedKeywords: keywords,
reservedDataTypes: dataTypes,
reservedFunctionNames: functions,
nestedBlockComments: true,
extraParens: ['[]', '{}'],
stringTypes: [
'$$',
"''-qq",
{ quote: "''-qq-bs", prefixes: ['E'], requirePrefix: true },
{ quote: "''-raw", prefixes: ['B', 'X'], requirePrefix: true },
],
identTypes: [`""-qq`],
identChars: { rest: '$' },
// TODO: named params $foo currently conflict with $$-quoted strings
paramTypes: { positional: true, numbered: ['$'], quoted: ['$'] },
operators: [
// Arithmetic:
'//',
'%',
'**',
'^',
'!',
// Bitwise:
'&',
'|',
'~',
'<<',
'>>',
// Cast:
'::',
// Comparison:
'==',
// Lambda:
'->',
// key-value separator:
':',
// Named function params:
':=',
'=>',
// Pattern matching:
'~~',
'!~~',
'~~*',
'!~~*',
'~~~',
// Regular expressions:
'~',
'!~',
'~*',
'!~*',
// String:
'^@',
'||',
// INET extension:
'>>=',
'<<=',
],
},
formatOptions: {
alwaysDenseOperators: ['::'],
onelineClauses: [...standardOnelineClauses, ...tabularOnelineClauses],
tabularOnelineClauses,
},
};
Loading