Repository: sql-formatter-org/sql-formatter
Pull request #454 (2022-09-22 06:44:55)

Add support for Snowflake Language
This is first draft to solve #366 . It does not work yet, mainly because I had some troubles with the `QuoteTypes`.

## Review
Please review, have a look at the following things:

1. `TODO` comments show lines where I'm totally lost
2. I'm very unsure about the `reservedComments`, the `functions` and the `keywords`.
3.  Did I put the correct things in there, or do they belong somewhere else?
4. Please check the places where I used the `[thing] {a|b|c} ...` syntax, if I used them correctly.
5. Did I miss any tests?
6. Any other places that have to be changed?

## Questions
I want to support accessing elements in unstructured data.
This can be done via square brackets `[,]` or via colons `:` , like [here](https://docs.snowflake.com/en/user-guide/querying-semistructured.html#bracket-notation):
```sql
select src:salesperson.name from car_sales

select src['salesperson']['name'] from car_sales
```
How should I do this? I added `:` as an operator, is this right? How about the brackets?

Thanks for this Repo!

nene: Thanks for the effort.

#### Regarding your main question about colon and square-bracket syntax.

- For brackets, adding `extraParens: ['[]']` should do the trick. See for example in `n1ql.formatter.ts`
- With colon, things are a bit trickier. You should indeed add it as an operator. However, currently the default is to format colon with a space after (as in `name: value`). To fix this, a language-specific condition should be added to `ExpressionFormatter.formatOperator()` - there already is a similar thing for `@` operator in PL/SQL.

#### In general

It would be really helpful if you could also update our [wiki](https://github.com/sql-formatter-org/sql-formatter/wiki) with information about Snowflake SQL syntax. Otherwise I'll have to do all this research on my own before I can merge this PR.
primeapple: I will do all the stuff you mentioned.
Please go other the `TODO` comments in the changes. I can't run tests because I don't understand the Quote types yet... 
nene: Regarding quote types, see: https://github.com/sql-formatter-org/sql-formatter/blob/master/src/lexer/regexFactory.ts#L99

The `-qq` and `-bs` suffixes at the end of quote types define what sort of escaping (if any) is used inside the quotes.
nene: Some comments about wiki updates:

- [Parameters page](https://github.com/sql-formatter-org/sql-formatter/wiki/parameters) says that Snowflake supports `$1` parameters and links to [select docs](https://docs.snowflake.com/en/sql-reference/sql/select.html#parameters). I'm pretty sure these are something else than parameters one would use in prepared statements. I think these things are more similar to variables - they also share a very similar syntax. It's probably better to reference this syntax from [Variables page](https://github.com/sql-formatter-org/sql-formatter/wiki/variables). Probably the simplest path for supporting this syntax is also to treat them the same as variables - at lest that would be my first gut-instinct-guess.

- [Comments page](https://github.com/sql-formatter-org/sql-formatter/wiki/comments) mentions support for `# comments` but says these aren't actually supported. Syntax highlighting really shouldn't be used as a guide for what is and isn't supported. So probably best to be removed. Too bad you haven't found any official documentation for the comment syntax. Found one [Knowledge Base page](https://community.snowflake.com/s/article/how-to-comment-or-uncomment-multiple-lines-in-the-sql-worksheet) that at least mentions the `//` syntax - we could link to that.
primeapple: I updated all the wiki pages for snowflake (also updated your suggestions).
One problem is the [Window-Clause](https://github.com/sql-formatter-org/sql-formatter/wiki/WINDOW-clause). Snowflake seems to not have support for a `WINDOW` clause in it's [Query Syntax)(https://docs.snowflake.com/en/sql-reference/constructs.html). There is [QUALIFY](https://docs.snowflake.com/en/sql-reference/constructs/qualify.html) but I'm unsure if this is the same?  
However, there is support for [Window Functions](https://docs.snowflake.com/en/sql-reference/functions-analytic.html) but also here I'm unsure if it is the same as in the other languages.  
Could you have a look into this?

I will rewriting the code now.

Patch:
diff --git a/README.md b/README.md
index 52b0714b..6aac39f7 100644
--- a/README.md
+++ b/README.md
@@ -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, Couchbase N1QL, Oracle PL/SQL, PostgreSQL, Amazon Redshift, SingleStoreDB, Spark, SQL Server Transact-SQL, Trino/Presto.
+GCP BigQuery, IBM DB2, Apache Hive, MariaDB, MySQL, Couchbase N1QL, Oracle PL/SQL, PostgreSQL, Amazon Redshift, SingleStoreDB, Snowflake, Spark, SQL Server Transact-SQL, Trino/Presto.
 See [language option docs](docs/language.md) for more details.
 
 It does not support:
@@ -95,7 +95,7 @@ sql-formatter -h
 
 ```
 usage: sql-formatter [-h] [-o OUTPUT] \
-[-l {bigquery,db2,hive,mariadb,mysql,n1ql,plsql,postgresql,redshift,singlestoredb,spark,sql,sqlite,transactsql,trino,tsql}] [-c CONFIG] [--version] [FILE]
+[-l {bigquery,db2,hive,mariadb,mysql,n1ql,plsql,postgresql,redshift,singlestoredb,snowflake,spark,sql,sqlite,transactsql,trino,tsql}] [-c CONFIG] [--version] [FILE]
 
 SQL Formatter
 
@@ -106,7 +106,7 @@ optional arguments:
   -h, --help      show this help message and exit
   -o, --output    OUTPUT
                     File to write SQL output (defaults to stdout)
-  -l, --language  {bigquery,db2,hive,mariadb,mysql,n1ql,plsql,postgresql,redshift,singlestoredb,spark,sql,sqlite,trino,tsql}
+  -l, --language  {bigquery,db2,hive,mariadb,mysql,n1ql,plsql,postgresql,redshift,singlestoredb,snowflake,spark,sql,sqlite,trino,tsql}
                     SQL dialect (defaults to basic sql)
   -c, --config    CONFIG
                     Path to config json file (will use default configs if unspecified)
diff --git a/docs/language.md b/docs/language.md
index 97c5926d..8e9c7327 100644
--- a/docs/language.md
+++ b/docs/language.md
@@ -15,6 +15,7 @@ Specifies the SQL dialect to use.
 - `"postgresql"` - [PostgreSQL][]
 - `"redshift"` - [Amazon Redshift][]
 - `"singlestoredb"` - [SingleStoreDB][]
+- `"snowflake"` - [Snowflake][]
 - `"spark"` - [Spark][]
 - `"sqlite"` - [SQLite][sqlite]
 - `"transactsql"` or `"tsql"` - [SQL Server Transact-SQL][tsql]
@@ -61,6 +62,7 @@ You likely only want to use this if your other alternative is to fork SQL Format
 [presto]: https://prestodb.io/docs/current/
 [amazon redshift]: https://docs.aws.amazon.com/redshift/latest/dg/cm_chap_SQLCommandRef.html
 [singlestoredb]: https://docs.singlestore.com/managed-service/en/reference.html
+[snowflake]: https://docs.snowflake.com/en/index.html
 [spark]: https://spark.apache.org/docs/latest/api/sql/index.html
 [sqlite]: https://sqlite.org/index.html
 [trino]: https://trino.io/docs/current/
diff --git a/docs/params.md b/docs/params.md
index 4c8e3055..2da66db1 100644
--- a/docs/params.md
+++ b/docs/params.md
@@ -117,6 +117,7 @@ The placeholder types available by default depend on SQL dialect used:
 - plsql - `:1`, `:name`
 - postgresql - `$1`
 - redshift - `$1`
+- snowflake - _no support_
 - sqlite - `?`, `?1`, `:name`, `@name`, `$name`
 - spark - _no support_
 - tsql - `@name`, `@"name"`, `@[name]`
diff --git a/package.json b/package.json
index ccd34774..63910ce0 100644
--- a/package.json
+++ b/package.json
@@ -37,7 +37,8 @@
     "sqlite",
     "trino",
     "presto",
-    "prestosql"
+    "prestosql",
+    "snowflake"
   ],
   "contributors": [
     "Adrien Pyke <adpyke@gmail.com>",
diff --git a/src/languages/snowflake/snowflake.formatter.ts b/src/languages/snowflake/snowflake.formatter.ts
new file mode 100644
index 00000000..45b2d618
--- /dev/null
+++ b/src/languages/snowflake/snowflake.formatter.ts
@@ -0,0 +1,357 @@
+import { expandPhrases } from 'src/expandPhrases';
+import { DialectFormatOptions } from 'src/formatter/ExpressionFormatter';
+import Formatter from 'src/formatter/Formatter';
+import Tokenizer from 'src/lexer/Tokenizer';
+import { functions } from './snowflake.functions';
+import { keywords } from './snowflake.keywords';
+
+const reservedSelect = expandPhrases(['SELECT [ALL | DISTINCT]']);
+
+// https://docs.snowflake.com/en/sql-reference/sql-all.html
+//
+// 1. run in console on this page: $x('//tbody/tr/*[1]//a/span/text()').map(x => x.nodeValue)
+// 2. delete all lines that contain a sting like '(.*)', they are already covered in the list
+// 3. delete all lines that contain a sting like '<.*>', they are already covered in the list
+// 4. delete all lines that contain '…', they are part of a regex statement that can't be covered here
+// 5. Manually add 'COPY INTO'
+//
+// Steps 1-4 can be combined by the following script in the developer console:
+// $x('//tbody/tr/*[1]//a/span/text()').map(x => x.nodeValue) // Step 1
+//   filter(x => !x.match(/\(.*\)/) && !x.match(/…/) && !x.match(/<.*>/)) // Step 2-4
+const reservedCommands = expandPhrases([
+  // queries
+  'WITH [RECURSIVE]',
+  'FROM',
+  'WHERE',
+  'GROUP BY',
+  'HAVING',
+  'PARTITION BY',
+  'ORDER BY',
+  'LIMIT',
+  'OFFSET',
+  'FETCH [FIRST | NEXT]',
+  // Data manipulation
+  // - insert:
+  'INSERT [OVERWRITE] [ALL INTO | INTO | ALL | FIRST]',
+  '{THEN | ELSE} INTO',
+  'VALUES',
+  // - update:
+  'UPDATE',
+  'SET',
+  // - delete:
+  'DELETE FROM',
+  // - truncate:
+  'TRUNCATE [TABLE] [IF EXISTS]',
+  // Data definition
+  // - view
+  'CREATE [OR REPLACE] [SECURE] [RECURSIVE] VIEW [IF NOT EXISTS]',
+  // - create/drop/merge table
+  'CREATE [OR REPLACE] [VOLATILE] TABLE [IF NOT EXISTS]',
+  'CREATE [OR REPLACE] [LOCAL | GLOBAL] {TEMP|TEMPORARY} TABLE [IF NOT EXISTS]',
+  'CLUSTER BY',
+  '[WITH] {MASKING POLICY | TAG | ROW ACCESS POLICY}',
+  'COPY GRANTS',
+  'USING TEMPLATE',
+  'MERGE INTO',
+  'WHEN MATCHED [AND]',
+  'THEN {UPDATE SET | DELETE}',
+  'WHEN NOT MATCHED THEN INSERT',
+  'DROP TABLE [IF EXISTS]',
+  // - alter table:
+  'ALTER TABLE [IF EXISTS]',
+  'RENAME TO',
+  'SWAP WITH',
+  '{SET | UNSET} [TAG | DATA_RETENTION_TIME_IN_DAYS | MAX_DATA_EXTENSION_TIME_IN_DAYS | CHANGE_TRACKING | DEFAULT_DDL_COLLATION | COMMENT]',
+  '{ADD | DROP} ROW ACCESS POLICY',
+  'DROP ALL ROW ACCESS POLICIES',
+  '[SUSPEND | RESUME] RECLUSTER',
+  'DROP CLUSTERING KEY',
+  'ADD [COLUMN]',
+  'RENAME COLUMN',
+  '{ALTER | MODIFY} [COLUMN]',
+  'DROP [COLUMN]',
+  '{SET | DROP} DEFAULT', // for alter column
+  'SET NOT NULL', // for alter column
+  'DROP NOT NULL', // for alter column
+  '[SET DATA] TYPE', // for alter column
+  '[UNSET] COMMENT', // for alter column
+  '{SET | UNSET} {MASKING POLICY | TAG}', // for alter column
+  '[FOREIGN KEY] REFERENCES', // for alter column
+  '{ADD | RENAME | ALTER | MODIFY | DROP} CONSTRAINT', // for alter column
+  '{ADD | DROP} SEARCH OPTIMIZATION', // for alter column
+
+  // other
+  // https://docs.snowflake.com/en/sql-reference/sql-all.html
+  //
+  // 1. run in console on this page: $x('//tbody/tr/*[1]//a/span/text()').map(x => x.nodeValue)
+  // 2. delete all lines that contain a sting like '(.*)', they are already covered in the list
+  // 3. delete all lines that contain a sting like '<.*>', they are already covered in the list
+  // 4. delete all lines that contain '…', they are part of a regex statement that can't be covered here
+  // 5. Manually add 'COPY INTO'
+  // 6. Remove all lines that are already in the above definitions:
+  //   - ALTER TABLE
+  //   - COMMENT
+  //   - CREATE TABLE
+  //   - CREATE VIEW
+  //   - DROP TABLE
+  //   - TRUNCATE TABLE
+  //   - SELECT
+  //   - UPDATE
+  //   - SET
+  //
+  // Steps 1-4 can be combined by the following script in the developer console:
+  // $x('//tbody/tr/*[1]//a/span/text()').map(x => x.nodeValue) // Step 1
+  //   filter(x => !x.match(/\(.*\)/) && !x.match(/…/) && !x.match(/<.*>/)) // Step 2-4
+  'ALTER ACCOUNT',
+  'ALTER API INTEGRATION',
+  'ALTER CONNECTION',
+  'ALTER DATABASE',
+  'ALTER EXTERNAL TABLE',
+  'ALTER FAILOVER GROUP',
+  'ALTER FILE FORMAT',
+  'ALTER FUNCTION',
+  'ALTER INTEGRATION',
+  'ALTER MASKING POLICY',
+  'ALTER MATERIALIZED VIEW',
+  'ALTER NETWORK POLICY',
+  'ALTER NOTIFICATION INTEGRATION',
+  'ALTER PIPE',
+  'ALTER PROCEDURE',
+  'ALTER REPLICATION GROUP',
+  'ALTER RESOURCE MONITOR',
+  'ALTER ROLE',
+  'ALTER ROW ACCESS POLICY',
+  'ALTER SCHEMA',
+  'ALTER SECURITY INTEGRATION',
+  'ALTER SEQUENCE',
+  'ALTER SESSION',
+  'ALTER SESSION POLICY',
+  'ALTER SHARE',
+  'ALTER STAGE',
+  'ALTER STORAGE INTEGRATION',
+  'ALTER STREAM',
+  'ALTER TAG',
+  'ALTER TASK',
+  'ALTER USER',
+  'ALTER VIEW',
+  'ALTER WAREHOUSE',
+  'BEGIN',
+  'CALL',
+  'COMMIT',
+  'COPY INTO',
+  'CREATE ACCOUNT',
+  'CREATE API INTEGRATION',
+  'CREATE CONNECTION',
+  'CREATE DATABASE',
+  'CREATE EXTERNAL FUNCTION',
+  'CREATE EXTERNAL TABLE',
+  'CREATE FAILOVER GROUP',
+  'CREATE FILE FORMAT',
+  'CREATE FUNCTION',
+  'CREATE INTEGRATION',
+  'CREATE MANAGED ACCOUNT',
+  'CREATE MASKING POLICY',
+  'CREATE MATERIALIZED VIEW',
+  'CREATE NETWORK POLICY',
+  'CREATE NOTIFICATION INTEGRATION',
+  'CREATE PIPE',
+  'CREATE PROCEDURE',
+  'CREATE REPLICATION GROUP',
+  'CREATE RESOURCE MONITOR',
+  'CREATE ROLE',
+  'CREATE ROW ACCESS POLICY',
+  'CREATE SCHEMA',
+  'CREATE SECURITY INTEGRATION',
+  'CREATE SEQUENCE',
+  'CREATE SESSION POLICY',
+  'CREATE SHARE',
+  'CREATE STAGE',
+  'CREATE STORAGE INTEGRATION',
+  'CREATE STREAM',
+  'CREATE TAG',
+  'CREATE TASK',
+  'CREATE USER',
+  'CREATE WAREHOUSE',
+  'DELETE',
+  'DESCRIBE DATABASE',
+  'DESCRIBE EXTERNAL TABLE',
+  'DESCRIBE FILE FORMAT',
+  'DESCRIBE FUNCTION',
+  'DESCRIBE INTEGRATION',
+  'DESCRIBE MASKING POLICY',
+  'DESCRIBE MATERIALIZED VIEW',
+  'DESCRIBE NETWORK POLICY',
+  'DESCRIBE PIPE',
+  'DESCRIBE PROCEDURE',
+  'DESCRIBE RESULT',
+  'DESCRIBE ROW ACCESS POLICY',
+  'DESCRIBE SCHEMA',
+  'DESCRIBE SEQUENCE',
+  'DESCRIBE SESSION POLICY',
+  'DESCRIBE SHARE',
+  'DESCRIBE STAGE',
+  'DESCRIBE STREAM',
+  'DESCRIBE TABLE',
+  'DESCRIBE TASK',
+  'DESCRIBE TRANSACTION',
+  'DESCRIBE USER',
+  'DESCRIBE VIEW',
+  'DESCRIBE WAREHOUSE',
+  'DROP CONNECTION',
+  'DROP DATABASE',
+  'DROP EXTERNAL TABLE',
+  'DROP FAILOVER GROUP',
+  'DROP FILE FORMAT',
+  'DROP FUNCTION',
+  'DROP INTEGRATION',
+  'DROP MANAGED ACCOUNT',
+  'DROP MASKING POLICY',
+  'DROP MATERIALIZED VIEW',
+  'DROP NETWORK POLICY',
+  'DROP PIPE',
+  'DROP PROCEDURE',
+  'DROP REPLICATION GROUP',
+  'DROP RESOURCE MONITOR',
+  'DROP ROLE',
+  'DROP ROW ACCESS POLICY',
+  'DROP SCHEMA',
+  'DROP SEQUENCE',
+  'DROP SESSION POLICY',
+  'DROP SHARE',
+  'DROP STAGE',
+  'DROP STREAM',
+  'DROP TAG',
+  'DROP TASK',
+  'DROP USER',
+  'DROP VIEW',
+  'DROP WAREHOUSE',
+  'EXECUTE IMMEDIATE',
+  'EXECUTE TASK',
+  'EXPLAIN',
+  'GET',
+  'GRANT OWNERSHIP',
+  'GRANT ROLE',
+  'INSERT',
+  'LIST',
+  'MERGE',
+  'PUT',
+  'REMOVE',
+  'REVOKE ROLE',
+  'ROLLBACK',
+  'SHOW COLUMNS',
+  'SHOW CONNECTIONS',
+  'SHOW DATABASES',
+  'SHOW DATABASES IN FAILOVER GROUP',
+  'SHOW DATABASES IN REPLICATION GROUP',
+  'SHOW DELEGATED AUTHORIZATIONS',
+  'SHOW EXTERNAL FUNCTIONS',
+  'SHOW EXTERNAL TABLES',
+  'SHOW FAILOVER GROUPS',
+  'SHOW FILE FORMATS',
+  'SHOW FUNCTIONS',
+  'SHOW GLOBAL ACCOUNTS',
+  'SHOW GRANTS',
+  'SHOW INTEGRATIONS',
+  'SHOW LOCKS',
+  'SHOW MANAGED ACCOUNTS',
+  'SHOW MASKING POLICIES',
+  'SHOW MATERIALIZED VIEWS',
+  'SHOW NETWORK POLICIES',
+  'SHOW OBJECTS',
+  'SHOW ORGANIZATION ACCOUNTS',
+  'SHOW PARAMETERS',
+  'SHOW PIPES',
+  'SHOW PRIMARY KEYS',
+  'SHOW PROCEDURES',
+  'SHOW REGIONS',
+  'SHOW REPLICATION ACCOUNTS',
+  'SHOW REPLICATION DATABASES',
+  'SHOW REPLICATION GROUPS',
+  'SHOW RESOURCE MONITORS',
+  'SHOW ROLES',
+  'SHOW ROW ACCESS POLICIES',
+  'SHOW SCHEMAS',
+  'SHOW SEQUENCES',
+  'SHOW SESSION POLICIES',
+  'SHOW SHARES',
+  'SHOW SHARES IN FAILOVER GROUP',
+  'SHOW SHARES IN REPLICATION GROUP',
+  'SHOW STAGES',
+  'SHOW STREAMS',
+  'SHOW TABLES',
+  'SHOW TAGS',
+  'SHOW TASKS',
+  'SHOW TRANSACTIONS',
+  'SHOW USER FUNCTIONS',
+  'SHOW USERS',
+  'SHOW VARIABLES',
+  'SHOW VIEWS',
+  'SHOW WAREHOUSES',
+  'TRUNCATE MATERIALIZED VIEW',
+  'UNDROP DATABASE',
+  'UNDROP SCHEMA',
+  'UNDROP TABLE',
+  'UNDROP TAG',
+  'UNSET',
+  'USE DATABASE',
+  'USE ROLE',
+  'USE SCHEMA',
+  'USE SECONDARY ROLES',
+  'USE WAREHOUSE',
+]);
+
+const reservedSetOperations = expandPhrases(['UNION [ALL]', 'MINUS', 'EXCEPT', 'INTERSECT']);
+
+const reservedJoins = expandPhrases([
+  '[INNER] JOIN',
+  '[NATURAL] {LEFT | RIGHT | FULL} [OUTER] JOIN',
+  '{CROSS | NATURAL} JOIN',
+]);
+
+const reservedPhrases = expandPhrases([
+  '{ROWS | RANGE} BETWEEN',
+  'MATCH {FULL | SIMPLE | PARTIAL}',
+  'ON {UPDATE | DELETE} {CASCADE | SET NULL | SET DEFAULT | RESTRICT | NO ACTION}',
+  'INITIALLY {DEFERRED | IMMEDIATE}',
+]);
+
+export default class SnowflakeFormatter extends Formatter {
+  tokenizer() {
+    return new Tokenizer({
+      reservedCommands,
+      reservedSelect,
+      reservedSetOperations,
+      reservedJoins,
+      reservedPhrases,
+      reservedKeywords: keywords,
+      reservedFunctionNames: functions,
+      stringTypes: ['$$', `''-qq-bs`],
+      identTypes: ['""-qq'],
+      variableTypes: [
+        // for accessing columns at certain positons in the table
+        { regex: '[$][1-9]\\d*' },
+        // identifier style syntax
+        { regex: '[$][_a-zA-Z][_a-zA-Z0-9$]*' },
+      ],
+      extraParens: ['[]'],
+      identChars: { rest: '$' },
+      lineCommentTypes: ['--', '//'],
+      operators: [
+        // Modulo
+        '%',
+        // Type cast
+        '::',
+        // String concat
+        '||',
+        // Get Path
+        ':',
+        // Generators: https://docs.snowflake.com/en/sql-reference/functions/generator.html#generator
+        '=>',
+      ],
+    });
+  }
+
+  formatOptions(): DialectFormatOptions {
+    return { alwaysDenseOperators: [':', '::'] };
+  }
+}
diff --git a/src/languages/snowflake/snowflake.functions.ts b/src/languages/snowflake/snowflake.functions.ts
new file mode 100644
index 00000000..3616d4d8
--- /dev/null
+++ b/src/languages/snowflake/snowflake.functions.ts
@@ -0,0 +1,610 @@
+import { flatKeywordList } from '../../utils';
+
+export const functions = flatKeywordList({
+  // https://docs.snowflake.com/en/sql-reference-functions.html
+  //
+  // https://docs.snowflake.com/en/sql-reference/functions-all.html
+  // 1. run in console on this page: $x('//tbody/tr/*[1]//a/span/text()').map(x => x.nodeValue)
+  // 2. split all lines that contain ',' or '/' into multiple lines
+  // 3. remove all '— Deprecated' parts from the strings
+  // 4. delete all strings that end with '<object_type>', they are already covered in the list
+  // 5. remove all strings that contain '[', they are operators not functions
+  // 6. fix all values that contain '*'
+  // 7. delete operatos ':', '::', '||'
+  //
+  // Steps 1-5 can be combined by the following script in the developer console:
+  // $x('//tbody/tr/*[1]//a/span/text()').map(x => x.nodeValue) // Step 1
+  //   .map(x => x.split(x.includes(',') ? ',' : '/')).flat().map(x => x.trim()) // Step 2
+  //   .map(x => x.replace('— Deprecated', '')) // Step 3
+  //   .filter(x => !x.endsWith('<object_type>')) // Step 4
+  //   .filter(x => !x.includes('[')) // Step 5
+  all: [
+    'ABS',
+    'ACOS',
+    'ACOSH',
+    'ADD_MONTHS',
+    'ALL_USER_NAMES',
+    'ANY_VALUE',
+    'APPROX_COUNT_DISTINCT',
+    'APPROX_PERCENTILE',
+    'APPROX_PERCENTILE_ACCUMULATE',
+    'APPROX_PERCENTILE_COMBINE',
+    'APPROX_PERCENTILE_ESTIMATE',
+    'APPROX_TOP_K',
+    'APPROX_TOP_K_ACCUMULATE',
+    'APPROX_TOP_K_COMBINE',
+    'APPROX_TOP_K_ESTIMATE',
+    'APPROXIMATE_JACCARD_INDEX',
+    'APPROXIMATE_SIMILARITY',
+    'ARRAY_AGG',
+    'ARRAY_APPEND',
+    'ARRAY_CAT',
+    'ARRAY_COMPACT',
+    'ARRAY_CONSTRUCT',
+    'ARRAY_CONSTRUCT_COMPACT',
+    'ARRAY_CONTAINS',
+    'ARRAY_INSERT',
+    'ARRAY_INTERSECTION',
+    'ARRAY_POSITION',
+    'ARRAY_PREPEND',
+    'ARRAY_SIZE',
+    'ARRAY_SLICE',
+    'ARRAY_TO_STRING',
+    'ARRAY_UNION_AGG',
+    'ARRAY_UNIQUE_AGG',
+    'ARRAYS_OVERLAP',
+    'AS_ARRAY',
+    'AS_BINARY',
+    'AS_BOOLEAN',
+    'AS_CHAR',
+    'AS_VARCHAR',
+    'AS_DATE',
+    'AS_DECIMAL',
+    'AS_NUMBER',
+    'AS_DOUBLE',
+    'AS_REAL',
+    'AS_INTEGER',
+    'AS_OBJECT',
+    'AS_TIME',
+    'AS_TIMESTAMP_LTZ',
+    'AS_TIMESTAMP_NTZ',
+    'AS_TIMESTAMP_TZ',
+    'ASCII',
+    'ASIN',
+    'ASINH',
+    'ATAN',
+    'ATAN2',
+    'ATANH',
+    'AUTO_REFRESH_REGISTRATION_HISTORY',
+    'AUTOMATIC_CLUSTERING_HISTORY',
+    'AVG',
+    'BASE64_DECODE_BINARY',
+    'BASE64_DECODE_STRING',
+    'BASE64_ENCODE',
+    'BIT_LENGTH',
+    'BITAND',
+    'BITAND_AGG',
+    'BITMAP_BIT_POSITION',
+    'BITMAP_BUCKET_NUMBER',
+    'BITMAP_CONSTRUCT_AGG',
+    'BITMAP_COUNT',
+    'BITMAP_OR_AGG',
+    'BITNOT',
+    'BITOR',
+    'BITOR_AGG',
+    'BITSHIFTLEFT',
+    'BITSHIFTRIGHT',
+    'BITXOR',
+    'BITXOR_AGG',
+    'BOOLAND',
+    'BOOLAND_AGG',
+    'BOOLNOT',
+    'BOOLOR',
+    'BOOLOR_AGG',
+    'BOOLXOR',
+    'BOOLXOR_AGG',
+    'BUILD_SCOPED_FILE_URL',
+    'BUILD_STAGE_FILE_URL',
+    'CASE',
+    'CAST',
+    'CBRT',
+    'CEIL',
+    'CHARINDEX',
+    'CHECK_JSON',
+    'CHECK_XML',
+    'CHR',
+    'CHAR',
+    'COALESCE',
+    'COLLATE',
+    'COLLATION',
+    'COMPLETE_TASK_GRAPHS',
+    'COMPRESS',
+    'CONCAT',
+    'CONCAT_WS',
+    'CONDITIONAL_CHANGE_EVENT',
+    'CONDITIONAL_TRUE_EVENT',
+    'CONTAINS',
+    'CONVERT_TIMEZONE',
+    'COPY_HISTORY',
+    'CORR',
+    'COS',
+    'COSH',
+    'COT',
+    'COUNT',
+    'COUNT_IF',
+    'COVAR_POP',
+    'COVAR_SAMP',
+    'CUME_DIST',
+    'CURRENT_ACCOUNT',
+    'CURRENT_AVAILABLE_ROLES',
+    'CURRENT_CLIENT',
+    'CURRENT_DATABASE',
+    'CURRENT_DATE',
+    'CURRENT_IP_ADDRESS',
+    'CURRENT_REGION',
+    'CURRENT_ROLE',
+    'CURRENT_SCHEMA',
+    'CURRENT_SCHEMAS',
+    'CURRENT_SECONDARY_ROLES',
+    'CURRENT_SESSION',
+    'CURRENT_STATEMENT',
+    'CURRENT_TASK_GRAPHS',
+    'CURRENT_TIME',
+    'CURRENT_TIMESTAMP',
+    'CURRENT_TRANSACTION',
+    'CURRENT_USER',
+    'CURRENT_VERSION',
+    'CURRENT_WAREHOUSE',
+    'DATA_TRANSFER_HISTORY',
+    'DATABASE_REFRESH_HISTORY',
+    'DATABASE_REFRESH_PROGRESS',
+    'DATABASE_REFRESH_PROGRESS_BY_JOB',
+    'DATABASE_STORAGE_USAGE_HISTORY',
+    'DATE_FROM_PARTS',
+    'DATE_PART',
+    'DATE_TRUNC',
+    'DATEADD',
+    'DATEDIFF',
+    'DAYNAME',
+    'DECODE',
+    'DECOMPRESS_BINARY',
+    'DECOMPRESS_STRING',
+    'DECRYPT',
+    'DECRYPT_RAW',
+    'DEGREES',
+    'DENSE_RANK',
+    'DIV0',
+    'EDITDISTANCE',
+    'ENCRYPT',
+    'ENCRYPT_RAW',
+    'ENDSWITH',
+    'EQUAL_NULL',
+    'EXP',
+    'EXPLAIN_JSON',
+    'EXTERNAL_FUNCTIONS_HISTORY',
+    'EXTERNAL_TABLE_FILES',
+    'EXTERNAL_TABLE_FILE_REGISTRATION_HISTORY',
+    'EXTRACT',
+    'EXTRACT_SEMANTIC_CATEGORIES',
+    'FACTORIAL',
+    'FIRST_VALUE',
+    'FLATTEN',
+    'FLOOR',
+    'GENERATE_COLUMN_DESCRIPTION',
+    'GENERATOR',
+    'GET',
+    'GET_ABSOLUTE_PATH',
+    'GET_DDL',
+    'GET_IGNORE_CASE',
+    'GET_OBJECT_REFERENCES',
+    'GET_PATH',
+    'GET_PRESIGNED_URL',
+    'GET_RELATIVE_PATH',
+    'GET_STAGE_LOCATION',
+    'GETBIT',
+    'GREATEST',
+    'GROUPING',
+    'GROUPING_ID',
+    'HASH',
+    'HASH_AGG',
+    'HAVERSINE',
+    'HEX_DECODE_BINARY',
+    'HEX_DECODE_STRING',
+    'HEX_ENCODE',
+    'HLL',
+    'HLL_ACCUMULATE',
+    'HLL_COMBINE',
+    'HLL_ESTIMATE',
+    'HLL_EXPORT',
+    'HLL_IMPORT',
+    'HOUR',
+    'MINUTE',
+    'SECOND',
+    'IFF',
+    'IFNULL',
+    'ILIKE',
+    'ILIKE ANY',
+    'INFER_SCHEMA',
+    'INITCAP',
+    'INSERT',
+    'INVOKER_ROLE',
+    'INVOKER_SHARE',
+    'IS_ARRAY',
+    'IS_BINARY',
+    'IS_BOOLEAN',
+    'IS_CHAR',
+    'IS_VARCHAR',
+    'IS_DATE',
+    'IS_DATE_VALUE',
+    'IS_DECIMAL',
+    'IS_DOUBLE',
+    'IS_REAL',
+    'IS_GRANTED_TO_INVOKER_ROLE',
+    'IS_INTEGER',
+    'IS_NULL_VALUE',
+    'IS_OBJECT',
+    'IS_ROLE_IN_SESSION',
+    'IS_TIME',
+    'IS_TIMESTAMP_LTZ',
+    'IS_TIMESTAMP_NTZ',
+    'IS_TIMESTAMP_TZ',
+    'JAROWINKLER_SIMILARITY',
+    'JSON_EXTRACT_PATH_TEXT',
+    'KURTOSIS',
+    'LAG',
+    'LAST_DAY',
+    'LAST_QUERY_ID',
+    'LAST_TRANSACTION',
+    'LAST_VALUE',
+    'LEAD',
+    'LEAST',
+    'LEFT',
+    'LENGTH',
+    'LEN',
+    'LIKE',
+    'LIKE ALL',
+    'LIKE ANY',
+    'LISTAGG',
+    'LN',
+    'LOCALTIME',
+    'LOCALTIMESTAMP',
+    'LOG',
+    'LOGIN_HISTORY',
+    'LOGIN_HISTORY_BY_USER',
+    'LOWER',
+    'LPAD',
+    'LTRIM',
+    'MATERIALIZED_VIEW_REFRESH_HISTORY',
+    'MD5',
+    'MD5_HEX',
+    'MD5_BINARY',
+    'MD5_NUMBER — Obsoleted',
+    'MD5_NUMBER_LOWER64',
+    'MD5_NUMBER_UPPER64',
+    'MEDIAN',
+    'MIN',
+    'MAX',
+    'MINHASH',
+    'MINHASH_COMBINE',
+    'MOD',
+    'MODE',
+    'MONTHNAME',
+    'MONTHS_BETWEEN',
+    'NEXT_DAY',
+    'NORMAL',
+    'NTH_VALUE',
+    'NTILE',
+    'NULLIF',
+    'NULLIFZERO',
+    'NVL',
+    'NVL2',
+    'OBJECT_AGG',
+    'OBJECT_CONSTRUCT',
+    'OBJECT_CONSTRUCT_KEEP_NULL',
+    'OBJECT_DELETE',
+    'OBJECT_INSERT',
+    'OBJECT_KEYS',
+    'OBJECT_PICK',
+    'OCTET_LENGTH',
+    'PARSE_IP',
+    'PARSE_JSON',
+    'PARSE_URL',
+    'PARSE_XML',
+    'PERCENT_RANK',
+    'PERCENTILE_CONT',
+    'PERCENTILE_DISC',
+    'PI',
+    'PIPE_USAGE_HISTORY',
+    'POLICY_CONTEXT',
+    'POLICY_REFERENCES',
+    'POSITION',
+    'POW',
+    'POWER',
+    'PREVIOUS_DAY',
+    'QUERY_ACCELERATION_HISTORY',
+    'QUERY_HISTORY',
+    'QUERY_HISTORY_BY_SESSION',
+    'QUERY_HISTORY_BY_USER',
+    'QUERY_HISTORY_BY_WAREHOUSE',
+    'RADIANS',
+    'RANDOM',
+    'RANDSTR',
+    'RANK',
+    'RATIO_TO_REPORT',
+    'REGEXP',
+    'REGEXP_COUNT',
+    'REGEXP_INSTR',
+    'REGEXP_LIKE',
+    'REGEXP_REPLACE',
+    'REGEXP_SUBSTR',
+    'REGEXP_SUBSTR_ALL',
+    'REGR_AVGX',
+    'REGR_AVGY',
+    'REGR_COUNT',
+    'REGR_INTERCEPT',
+    'REGR_R2',
+    'REGR_SLOPE',
+    'REGR_SXX',
+    'REGR_SXY',
+    'REGR_SYY',
+    'REGR_VALX',
+    'REGR_VALY',
+    'REPEAT',
+    'REPLACE',
+    'REPLICATION_GROUP_REFRESH_HISTORY',
+    'REPLICATION_GROUP_REFRESH_PROGRESS',
+    'REPLICATION_GROUP_REFRESH_PROGRESS_BY_JOB',
+    'REPLICATION_GROUP_USAGE_HISTORY',
+    'REPLICATION_USAGE_HISTORY',
+    'REST_EVENT_HISTORY',
+    'RESULT_SCAN',
+    'REVERSE',
+    'RIGHT',
+    'RLIKE',
+    'ROUND',
+    'ROW_NUMBER',
+    'RPAD',
+    'RTRIM',
+    'RTRIMMED_LENGTH',
+    'SEARCH_OPTIMIZATION_HISTORY',
+    'SEQ1',
+    'SEQ2',
+    'SEQ4',
+    'SEQ8',
+    'SERVERLESS_TASK_HISTORY',
+    'SHA1',
+    'SHA1_HEX',
+    'SHA1_BINARY',
+    'SHA2',
+    'SHA2_HEX',
+    'SHA2_BINARY',
+    'SIGN',
+    'SIN',
+    'SINH',
+    'SKEW',
+    'SOUNDEX',
+    'SPACE',
+    'SPLIT',
+    'SPLIT_PART',
+    'SPLIT_TO_TABLE',
+    'SQRT',
+    'SQUARE',
+    'ST_AREA',
+    'ST_ASEWKB',
+    'ST_ASEWKT',
+    'ST_ASGEOJSON',
+    'ST_ASWKB',
+    'ST_ASBINARY',
+    'ST_ASWKT',
+    'ST_ASTEXT',
+    'ST_AZIMUTH',
+    'ST_CENTROID',
+    'ST_COLLECT',
+    'ST_CONTAINS',
+    'ST_COVEREDBY',
+    'ST_COVERS',
+    'ST_DIFFERENCE',
+    'ST_DIMENSION',
+    'ST_DISJOINT',
+    'ST_DISTANCE',
+    'ST_DWITHIN',
+    'ST_ENDPOINT',
+    'ST_ENVELOPE',
+    'ST_GEOGFROMGEOHASH',
+    'ST_GEOGPOINTFROMGEOHASH',
+    'ST_GEOGRAPHYFROMWKB',
+    'ST_GEOGRAPHYFROMWKT',
+    'ST_GEOHASH',
+    'ST_GEOMETRYFROMWKB',
+    'ST_GEOMETRYFROMWKT',
+    'ST_HAUSDORFFDISTANCE',
+    'ST_INTERSECTION',
+    'ST_INTERSECTS',
+    'ST_LENGTH',
+    'ST_MAKEGEOMPOINT',
+    'ST_GEOM_POINT',
+    'ST_MAKELINE',
+    'ST_MAKEPOINT',
+    'ST_POINT',
+    'ST_MAKEPOLYGON',
+    'ST_POLYGON',
+    'ST_NPOINTS',
+    'ST_NUMPOINTS',
+    'ST_PERIMETER',
+    'ST_POINTN',
+    'ST_SETSRID',
+    'ST_SIMPLIFY',
+    'ST_SRID',
+    'ST_STARTPOINT',
+    'ST_SYMDIFFERENCE',
+    'ST_UNION',
+    'ST_WITHIN',
+    'ST_X',
+    'ST_XMAX',
+    'ST_XMIN',
+    'ST_Y',
+    'ST_YMAX',
+    'ST_YMIN',
+    'STAGE_DIRECTORY_FILE_REGISTRATION_HISTORY',
+    'STAGE_STORAGE_USAGE_HISTORY',
+    'STARTSWITH',
+    'STDDEV',
+    'STDDEV_POP',
+    'STDDEV_SAMP',
+    'STRIP_NULL_VALUE',
+    'STRTOK',
+    'STRTOK_SPLIT_TO_TABLE',
+    'STRTOK_TO_ARRAY',
+    'SUBSTR',
+    'SUBSTRING',
+    'SUM',
+    'SYSDATE',
+    'SYSTEM$ABORT_SESSION',
+    'SYSTEM$ABORT_TRANSACTION',
+    'SYSTEM$AUTHORIZE_PRIVATELINK',
+    'SYSTEM$AUTHORIZE_STAGE_PRIVATELINK_ACCESS',
+    'SYSTEM$BEHAVIOR_CHANGE_BUNDLE_STATUS',
+    'SYSTEM$CANCEL_ALL_QUERIES',
+    'SYSTEM$CANCEL_QUERY',
+    'SYSTEM$CLUSTERING_DEPTH',
+    'SYSTEM$CLUSTERING_INFORMATION',
+    'SYSTEM$CLUSTERING_RATIO ',
+    'SYSTEM$CURRENT_USER_TASK_NAME',
+    'SYSTEM$DATABASE_REFRESH_HISTORY ',
+    'SYSTEM$DATABASE_REFRESH_PROGRESS',
+    'SYSTEM$DATABASE_REFRESH_PROGRESS_BY_JOB ',
+    'SYSTEM$DISABLE_BEHAVIOR_CHANGE_BUNDLE',
+    'SYSTEM$DISABLE_DATABASE_REPLICATION',
+    'SYSTEM$ENABLE_BEHAVIOR_CHANGE_BUNDLE',
+    'SYSTEM$ESTIMATE_QUERY_ACCELERATION',
+    'SYSTEM$ESTIMATE_SEARCH_OPTIMIZATION_COSTS',
+    'SYSTEM$EXPLAIN_JSON_TO_TEXT',
+    'SYSTEM$EXPLAIN_PLAN_JSON',
+    'SYSTEM$EXTERNAL_TABLE_PIPE_STATUS',
+    'SYSTEM$GENERATE_SAML_CSR',
+    'SYSTEM$GENERATE_SCIM_ACCESS_TOKEN',
+    'SYSTEM$GET_AWS_SNS_IAM_POLICY',
+    'SYSTEM$GET_PREDECESSOR_RETURN_VALUE',
+    'SYSTEM$GET_PRIVATELINK',
+    'SYSTEM$GET_PRIVATELINK_AUTHORIZED_ENDPOINTS',
+    'SYSTEM$GET_PRIVATELINK_CONFIG',
+    'SYSTEM$GET_SNOWFLAKE_PLATFORM_INFO',
+    'SYSTEM$GET_TAG',
+    'SYSTEM$GET_TAG_ALLOWED_VALUES',
+    'SYSTEM$GET_TAG_ON_CURRENT_COLUMN',
+    'SYSTEM$GET_TAG_ON_CURRENT_TABLE',
+    'SYSTEM$GLOBAL_ACCOUNT_SET_PARAMETER',
+    'SYSTEM$LAST_CHANGE_COMMIT_TIME',
+    'SYSTEM$LINK_ACCOUNT_OBJECTS_BY_NAME',
+    'SYSTEM$MIGRATE_SAML_IDP_REGISTRATION',
+    'SYSTEM$PIPE_FORCE_RESUME',
+    'SYSTEM$PIPE_STATUS',
+    'SYSTEM$REVOKE_PRIVATELINK',
+    'SYSTEM$REVOKE_STAGE_PRIVATELINK_ACCESS',
+    'SYSTEM$SET_RETURN_VALUE',
+    'SYSTEM$SHOW_OAUTH_CLIENT_SECRETS',
+    'SYSTEM$STREAM_GET_TABLE_TIMESTAMP',
+    'SYSTEM$STREAM_HAS_DATA',
+    'SYSTEM$TASK_DEPENDENTS_ENABLE',
+    'SYSTEM$TYPEOF',
+    'SYSTEM$USER_TASK_CANCEL_ONGOING_EXECUTIONS',
+    'SYSTEM$VERIFY_EXTERNAL_OAUTH_TOKEN',
+    'SYSTEM$WAIT',
+    'SYSTEM$WHITELIST',
+    'SYSTEM$WHITELIST_PRIVATELINK',
+    'TAG_REFERENCES',
+    'TAG_REFERENCES_ALL_COLUMNS',
+    'TAG_REFERENCES_WITH_LINEAGE',
+    'TAN',
+    'TANH',
+    'TASK_DEPENDENTS',
+    'TASK_HISTORY',
+    'TIME_FROM_PARTS',
+    'TIME_SLICE',
+    'TIMEADD',
+    'TIMEDIFF',
+    'TIMESTAMP_FROM_PARTS',
+    'TIMESTAMPADD',
+    'TIMESTAMPDIFF',
+    'TO_ARRAY',
+    'TO_BINARY',
+    'TO_BOOLEAN',
+    'TO_CHAR',
+    'TO_VARCHAR',
+    'TO_DATE',
+    'DATE',
+    'TO_DECIMAL',
+    'TO_NUMBER',
+    'TO_NUMERIC',
+    'TO_DOUBLE',
+    'TO_GEOGRAPHY',
+    'TO_GEOMETRY',
+    'TO_JSON',
+    'TO_OBJECT',
+    'TO_TIME',
+    'TIME',
+    'TO_TIMESTAMP',
+    'TO_TIMESTAMP_LTZ',
+    'TO_TIMESTAMP_NTZ',
+    'TO_TIMESTAMP_TZ',
+    'TO_VARIANT',
+    'TO_XML',
+    'TRANSLATE',
+    'TRIM',
+    'TRUNCATE',
+    'TRUNC',
+    'TRUNC',
+    'TRY_BASE64_DECODE_BINARY',
+    'TRY_BASE64_DECODE_STRING',
+    'TRY_CAST',
+    'TRY_HEX_DECODE_BINARY',
+    'TRY_HEX_DECODE_STRING',
+    'TRY_PARSE_JSON',
+    'TRY_TO_BINARY',
+    'TRY_TO_BOOLEAN',
+    'TRY_TO_DATE',
+    'TRY_TO_DECIMAL',
+    'TRY_TO_NUMBER',
+    'TRY_TO_NUMERIC',
+    'TRY_TO_DOUBLE',
+    'TRY_TO_GEOGRAPHY',
+    'TRY_TO_GEOMETRY',
+    'TRY_TO_TIME',
+    'TRY_TO_TIMESTAMP',
+    'TRY_TO_TIMESTAMP_LTZ',
+    'TRY_TO_TIMESTAMP_NTZ',
+    'TRY_TO_TIMESTAMP_TZ',
+    'TYPEOF',
+    'UNICODE',
+    'UNIFORM',
+    'UPPER',
+    'UUID_STRING',
+    'VALIDATE',
+    'VALIDATE_PIPE_LOAD',
+    'VAR_POP',
+    'VAR_SAMP',
+    'VARIANCE',
+    'VARIANCE_SAMP',
+    'VARIANCE_POP',
+    'WAREHOUSE_LOAD_HISTORY',
+    'WAREHOUSE_METERING_HISTORY',
+    'WIDTH_BUCKET',
+    'XMLGET',
+    'YEAR',
+    'YEAROFWEEK',
+    'YEAROFWEEKISO',
+    'DAY',
+    'DAYOFMONTH',
+    'DAYOFWEEK',
+    'DAYOFWEEKISO',
+    'DAYOFYEAR',
+    'WEEK',
+    'WEEK',
+    'WEEKOFYEAR',
+    'WEEKISO',
+    'MONTH',
+    'QUARTER',
+    'ZEROIFNULL',
+    'ZIPF',
+  ],
+});
diff --git a/src/languages/snowflake/snowflake.keywords.ts b/src/languages/snowflake/snowflake.keywords.ts
new file mode 100644
index 00000000..cf1e6aad
--- /dev/null
+++ b/src/languages/snowflake/snowflake.keywords.ts
@@ -0,0 +1,100 @@
+import { flatKeywordList } from '../../utils';
+
+export const keywords = flatKeywordList({
+  // https://docs.snowflake.com/en/sql-reference/reserved-keywords.html
+  //
+  // run in console on this page: $x('//tbody/tr/*[1]/p/text()').map(x => x.nodeValue)
+  all: [
+    'ACCOUNT',
+    'ALL',
+    'ALTER',
+    'AND',
+    'ANY',
+    'AS',
+    'BETWEEN',
+    'BY',
+    'CASE',
+    'CAST',
+    'CHECK',
+    'COLUMN',
+    'CONNECT',
+    'CONNECTION',
+    'CONSTRAINT',
+    'CREATE',
+    'CROSS',
+    'CURRENT',
+    'CURRENT_DATE',
+    'CURRENT_TIME',
+    'CURRENT_TIMESTAMP',
+    'CURRENT_USER',
+    'DATABASE',
+    'DELETE',
+    'DISTINCT',
+    'DROP',
+    'ELSE',
+    'EXISTS',
+    'FALSE',
+    'FOLLOWING',
+    'FOR',
+    'FROM',
+    'FULL',
+    'GRANT',
+    'GROUP',
+    'GSCLUSTER',
+    'HAVING',
+    'ILIKE',
+    'IN',
+    'INCREMENT',
+    'INNER',
+    'INSERT',
+    'INTERSECT',
+    'INTO',
+    'IS',
+    'ISSUE',
+    'JOIN',
+    'LATERAL',
+    'LEFT',
+    'LIKE',
+    'LOCALTIME',
+    'LOCALTIMESTAMP',
+    'MINUS',
+    'NATURAL',
+    'NOT',
+    'NULL',
+    'OF',
+    'ON',
+    'OR',
+    'ORDER',
+    'ORGANIZATION',
+    'QUALIFY',
+    'REGEXP',
+    'REVOKE',
+    'RIGHT',
+    'RLIKE',
+    'ROW',
+    'ROWS',
+    'SAMPLE',
+    'SCHEMA',
+    'SELECT',
+    'SET',
+    'SOME',
+    'START',
+    'TABLE',
+    'TABLESAMPLE',
+    'THEN',
+    'TO',
+    'TRIGGER',
+    'TRUE',
+    'TRY_CAST',
+    'UNION',
+    'UNIQUE',
+    'UPDATE',
+    'USING',
+    'VALUES',
+    'VIEW',
+    'WHEN',
+    'WHENEVER',
+    'WHERE',
+    'WITH',
+  ],
+});
diff --git a/src/sqlFormatter.ts b/src/sqlFormatter.ts
index 9dc4208e..45b04098 100644
--- a/src/sqlFormatter.ts
+++ b/src/sqlFormatter.ts
@@ -13,6 +13,7 @@ import SqlFormatter from 'src/languages/sql/sql.formatter';
 import TrinoFormatter from 'src/languages/trino/trino.formatter';
 import TransactSqlFormatter from 'src/languages/transactsql/transactsql.formatter';
 import SingleStoreDbFormatter from './languages/singlestoredb/singlestoredb.formatter';
+import SnowflakeFormatter from './languages/snowflake/snowflake.formatter';
 
 import { FormatOptions } from './FormatOptions';
 import { ParamItems } from './formatter/Params';
@@ -28,6 +29,7 @@ export const formatters = {
   postgresql: PostgreSqlFormatter,
   redshift: RedshiftFormatter,
   singlestoredb: SingleStoreDbFormatter,
+  snowflake: SnowflakeFormatter,
   spark: SparkFormatter,
   sql: SqlFormatter,
   sqlite: SqliteFormatter,
diff --git a/static/index.html b/static/index.html
index d6060377..1e6cab55 100644
--- a/static/index.html
+++ b/static/index.html
@@ -67,6 +67,7 @@
               <option value="plsql">PL/SQL</option>
               <option value="postgresql">PostgreSQL</option>
               <option value="singlestoredb">SingleStoreDB</option>
+              <option value="snowflake">Snowflake</option>
               <option value="spark">Spark</option>
               <option value="sqlite">SQLite</option>
               <option value="tsql">Transact-SQL</option>


Test patch:
diff --git a/test/features/comments.ts b/test/features/comments.ts
index ce93f842..a9e65aff 100644
--- a/test/features/comments.ts
+++ b/test/features/comments.ts
@@ -4,6 +4,7 @@ import { FormatFn } from 'src/sqlFormatter';
 
 interface CommentsConfig {
   hashComments?: boolean;
+  doubleSlashComments?: boolean;
   nestedBlockComments?: boolean;
 }
 
@@ -219,6 +220,18 @@ export default function supportsComments(format: FormatFn, opts: CommentsConfig
     });
   }
 
+  if (opts.doubleSlashComments) {
+    it('supports // line comment', () => {
+      const result = format('SELECT alpha // commment\nFROM beta');
+      expect(result).toBe(dedent`
+        SELECT
+          alpha // commment
+        FROM
+          beta
+      `);
+    });
+  }
+
   if (opts.nestedBlockComments) {
     it('supports nested block comments', () => {
       const result = format('SELECT alpha /* /* commment */ */ FROM beta');
diff --git a/test/features/constraints.ts b/test/features/constraints.ts
index 4eba80f0..8ddaf87d 100644
--- a/test/features/constraints.ts
+++ b/test/features/constraints.ts
@@ -2,21 +2,25 @@ import dedent from 'dedent-js';
 
 import { FormatFn } from 'src/sqlFormatter';
 
-export default function supportsConstraints(format: FormatFn) {
-  it('treats ON UPDATE & ON DELETE as distinct keywords from ON', () => {
-    expect(
-      format(`
-      CREATE TABLE foo (
-        update_time datetime ON UPDATE CURRENT_TIMESTAMP,
-        other_table_id int NOT NULL ON DELETE CASCADE
-      );
-    `)
-    ).toBe(dedent`
-      CREATE TABLE
-        foo (
-          update_time datetime ON UPDATE CURRENT_TIMESTAMP,
-          other_table_id int NOT NULL ON DELETE CASCADE
+const standardActions = ['CURRENT_TIMESTAMP'];
+
+export default function supportsConstraints(format: FormatFn, actions: string[] = standardActions) {
+  actions.forEach(action => {
+    it(`treats ON UPDATE & ON DELETE ${action} as distinct keywords from ON`, () => {
+      expect(
+        format(`
+        CREATE TABLE foo (
+          update_time datetime ON UPDATE ${action},
+          delete_time datetime ON DELETE ${action},
         );
-    `);
+      `)
+      ).toBe(dedent`
+        CREATE TABLE
+          foo (
+            update_time datetime ON UPDATE ${action},
+            delete_time datetime ON DELETE ${action},
+          );
+      `);
+    });
   });
 }
diff --git a/test/features/createView.ts b/test/features/createView.ts
index fce33bae..6b16eee0 100644
--- a/test/features/createView.ts
+++ b/test/features/createView.ts
@@ -5,11 +5,12 @@ import { FormatFn } from 'src/sqlFormatter';
 interface CreateViewConfig {
   orReplace?: boolean;
   materialized?: boolean;
+  ifNotExists?: boolean;
 }
 
 export default function supportsCreateView(
   format: FormatFn,
-  { orReplace, materialized }: CreateViewConfig = {}
+  { orReplace, materialized, ifNotExists }: CreateViewConfig = {}
 ) {
   it('formats CREATE VIEW', () => {
     expect(format('CREATE VIEW my_view AS SELECT id, fname, lname FROM tbl;')).toBe(dedent`
@@ -56,4 +57,13 @@ export default function supportsCreateView(
       `);
     });
   }
+
+  if (ifNotExists) {
+    it('formats short CREATE TABLE IF NOT EXISTS', () => {
+      expect(format('CREATE TABLE IF NOT EXISTS tbl (a INT PRIMARY KEY, b TEXT);')).toBe(dedent`
+        CREATE TABLE IF NOT EXISTS
+          tbl (a INT PRIMARY KEY, b TEXT);
+      `);
+    });
+  }
 }
diff --git a/test/features/insertInto.ts b/test/features/insertInto.ts
index 11a8c610..e5edd834 100644
--- a/test/features/insertInto.ts
+++ b/test/features/insertInto.ts
@@ -4,11 +4,12 @@ import { FormatFn } from 'src/sqlFormatter';
 
 interface InsertIntoConfig {
   withoutInto?: boolean;
+  withOverwrite?: boolean;
 }
 
 export default function supportsInsertInto(
   format: FormatFn,
-  { withoutInto }: InsertIntoConfig = {}
+  { withoutInto, withOverwrite }: InsertIntoConfig = {}
 ) {
   it('formats simple INSERT INTO', () => {
     const result = format(
@@ -35,4 +36,18 @@ export default function supportsInsertInto(
       `);
     });
   }
+
+  if (withOverwrite) {
+    it('formats INSERT with OVERWRITE', () => {
+      const result = format(
+        "INSERT OVERWRITE INTO Customers (ID, MoneyBalance, Address, City) VALUES (12,-123.4, 'Skagen 2111','Stv');"
+      );
+      expect(result).toBe(dedent`
+        INSERT OVERWRITE INTO
+          Customers (ID, MoneyBalance, Address, City)
+        VALUES
+          (12, -123.4, 'Skagen 2111', 'Stv');
+      `);
+    });
+  }
 }
diff --git a/test/features/strings.ts b/test/features/strings.ts
index 286f118b..3a459dca 100644
--- a/test/features/strings.ts
+++ b/test/features/strings.ts
@@ -12,6 +12,7 @@ type StringType =
   | "''-bs" // with backslash escaping
   | "U&''" // with repeated-quote escaping
   | "N''" // with escaping style depending on whether also ''-qq or ''-bs was specified
+  | '$$' // no escaping
   | "X''" // no escaping
   | 'X""' // no escaping
   | "B''" // no escaping
@@ -112,6 +113,15 @@ export default function supportsStrings(format: FormatFn, stringTypes: StringTyp
     });
   }
 
+  if (stringTypes.includes('$$')) {
+    it('supports quotes and dollar signs inside $$ string', () => {
+      expect(format(`$$foo' JOIN"$bar$$`)).toBe(`$$foo' JOIN"$bar$$`);
+    });
+
+    it('detects consecutive $$ strings as separate ones', () => {
+      expect(format('$$foo$$$$bar$$')).toBe('$$foo$$ $$bar$$');
+    });
+  }
   if (stringTypes.includes("N''")) {
     it('supports T-SQL unicode strings', () => {
       expect(format("N'foo JOIN bar'")).toBe("N'foo JOIN bar'");
diff --git a/test/features/truncateTable.ts b/test/features/truncateTable.ts
index 3346097c..09fa9c49 100644
--- a/test/features/truncateTable.ts
+++ b/test/features/truncateTable.ts
@@ -3,12 +3,13 @@ import dedent from 'dedent-js';
 import { FormatFn } from 'src/sqlFormatter';
 
 interface TruncateTableConfig {
+  ifExists?: boolean;
   withoutTable?: boolean;
 }
 
 export default function supportsTruncateTable(
   format: FormatFn,
-  { withoutTable }: TruncateTableConfig = {}
+  { ifExists, withoutTable }: TruncateTableConfig = {}
 ) {
   it('formats TRUNCATE TABLE statement', () => {
     const result = format('TRUNCATE TABLE Customers;');
@@ -18,6 +19,16 @@ export default function supportsTruncateTable(
     `);
   });
 
+  if (ifExists) {
+    it('formats TRUNCATE TABLE IF EXISTS statments', () => {
+      const result = format('TRUNCATE TABLE IF EXISTS Customers;');
+      expect(result).toBe(dedent`
+        TRUNCATE TABLE IF EXISTS
+          Customers;
+      `);
+    });
+  }
+
   if (withoutTable) {
     it('formats TRUNCATE statement (without TABLE)', () => {
       const result = format('TRUNCATE Customers;');
diff --git a/test/snowflake.test.ts b/test/snowflake.test.ts
new file mode 100644
index 00000000..1b2a9d35
--- /dev/null
+++ b/test/snowflake.test.ts
@@ -0,0 +1,83 @@
+import dedent from 'dedent-js';
+
+import { format as originalFormat, FormatFn } from 'src/sqlFormatter';
+
+import behavesLikeSqlFormatter from './behavesLikeSqlFormatter';
+import supportsAlterTable from './features/alterTable';
+import supportsBetween from './features/between';
+import supportsCreateTable from './features/createTable';
+import supportsDropTable from './features/dropTable';
+import supportsJoin from './features/join';
+import supportsOperators from './features/operators';
+import supportsStrings from './features/strings';
+import supportsDeleteFrom from './features/deleteFrom';
+import supportsComments from './features/comments';
+import supportsIdentifiers from './features/identifiers';
+import supportsArrayAndMapAccessors from './features/arrayAndMapAccessors';
+import supportsSetOperations from './features/setOperations';
+import supportsLimiting from './features/limiting';
+import supportsInsertInto from './features/insertInto';
+import supportsUpdate from './features/update';
+import supportsTruncateTable from './features/truncateTable';
+import supportsCreateView from './features/createView';
+import supportsConstraints from './features/constraints';
+
+describe('SnowflakeFormatter', () => {
+  const language = 'snowflake';
+  const format: FormatFn = (query, cfg = {}) => originalFormat(query, { ...cfg, language });
+
+  behavesLikeSqlFormatter(format);
+  supportsComments(format, { doubleSlashComments: true });
+  supportsCreateView(format, { orReplace: true, ifNotExists: true });
+  supportsCreateTable(format, { orReplace: true, ifNotExists: true });
+  supportsDropTable(format, { ifExists: true });
+  supportsArrayAndMapAccessors(format);
+  supportsAlterTable(format, {
+    addColumn: true,
+    dropColumn: true,
+    renameTo: true,
+    renameColumn: true,
+  });
+  supportsDeleteFrom(format);
+  supportsInsertInto(format, { withOverwrite: true });
+  supportsUpdate(format);
+  supportsTruncateTable(format, { withoutTable: true, ifExists: true });
+  supportsStrings(format, ['$$', "''-bs", "''-qq"]);
+  supportsIdentifiers(format, [`""-qq`]);
+  supportsBetween(format);
+  // ':' and '::' are tested later, since they should always be dense
+  supportsOperators(format, ['%', '||', '=>']);
+  supportsJoin(format, { without: ['NATURAL INNER JOIN'] });
+  supportsSetOperations(format, ['UNION', 'UNION ALL', 'MINUS', 'EXCEPT', 'INTERSECT']);
+  supportsLimiting(format, { limit: true, offset: true, fetchFirst: true, fetchNext: true });
+  supportsConstraints(format, ['CASCADE', 'SET NULL', 'SET DEFAULT', 'RESTRICT', 'NO ACTION']);
+
+  it('allows $ character as part of unquoted identifiers', () => {
+    expect(format('SELECT foo$')).toBe(dedent`
+      SELECT
+        foo$
+    `);
+  });
+
+  describe(`formats traversal of semi structured data`, () => {
+    it(`formats ':' path-operator without spaces`, () => {
+      expect(format(`SELECT foo : bar`)).toBe(dedent`
+        SELECT
+          foo:bar
+      `);
+    });
+    it(`formats ':' path-operator followed by dots without spaces`, () => {
+      expect(format(`SELECT foo : bar . baz`)).toBe(dedent`
+        SELECT
+          foo:bar.baz
+      `);
+    });
+  });
+
+  it('formats type-cast operator without spaces', () => {
+    expect(format('SELECT 2 :: numeric AS foo;')).toBe(dedent`
+      SELECT
+        2::numeric AS foo;
+    `);
+  });
+});