Repository: sql-formatter-org/sql-formatter
Pull request #325 (2022-07-16 10:32:27)

Trino support (with additional changes)
Merged #297 in here, synced with changes in `master` and cleaned up a few things.


Patch:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 5e3abc1a..00fc9ab7 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -4,11 +4,7 @@
 
 Run `yarn` after checkout to install all dependencies.
 
-## Branch Guidelines
-
-New branches: Please branch off of the `develop` branch.
-
-### Naming
+## Branch Naming
 
 Please use one of the following prefixes: (ie. feature/new-feature)
 
@@ -42,6 +38,7 @@ For those who have admin access on the repo, the new release publish flow is as
 - Andrew
 - Benjamin Bellamy
 - bingou
+- Boris Verkhovskiy <boris.verk@gmail.com>
 - Damon Davison <ddavison@avalere.com>
 - Davut Can Abacigil <can@teamsql.io>
 - Erik Hirmo <erik.hirmo@roguewave.com>
diff --git a/README.md b/README.md
index 51a60262..9d9cd437 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, Spark, SQL Server Transact-SQL.
+GCP BigQuery, IBM DB2, Apache Hive, MariaDB, MySQL, Couchbase N1QL, Oracle PL/SQL, PostgreSQL, Amazon Redshift, 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,spark,sql,sqlite,tsql}] [-c CONFIG] [--version] [FILE]
+[-l {bigquery,db2,hive,mariadb,mysql,n1ql,plsql,postgresql,redshift,spark,sql,sqlite,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,spark,sql,sqlite,tsql}
+  -l, --language  {bigquery,db2,hive,mariadb,mysql,n1ql,plsql,postgresql,redshift,spark,sql,sqlite,trino,tsql}
                     SQL dialect (defaults to standard 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 6f8ad117..c15d741c 100644
--- a/docs/language.md
+++ b/docs/language.md
@@ -16,6 +16,7 @@ Specifies the SQL dialect to use.
 - `"redshift"` - [Amazon Redshift][]
 - `"spark"` - [Spark][]
 - `"sqlite"` - [SQLite][sqlite]
+- `"trino"` - [Trino][] / [Presto][]
 - `"tsql"` - [SQL Server Transact-SQL][tsql]
 
 The default `"sql"` dialect is meant for cases where you don't know which dialect of SQL you're about to format.
@@ -32,7 +33,9 @@ Better to always pick something more specific if possible.
 [couchbase n1ql]: http://www.couchbase.com/n1ql
 [oracle pl/sql]: http://www.oracle.com/technetwork/database/features/plsql/index.html
 [postgresql]: https://www.postgresql.org/
+[presto]: https://prestodb.io/docs/current/
 [amazon redshift]: https://docs.aws.amazon.com/redshift/latest/dg/cm_chap_SQLCommandRef.html
 [spark]: https://spark.apache.org/docs/latest/api/sql/index.html
 [sqlite]: https://sqlite.org/index.html
+[trino]: https://trino.io/docs/current/
 [tsql]: https://docs.microsoft.com/en-us/sql/sql-server/
diff --git a/package.json b/package.json
index 93b5aea3..167a9b65 100644
--- a/package.json
+++ b/package.json
@@ -22,7 +22,10 @@
     "pl/sql",
     "transact-sql",
     "db2",
-    "sqlite"
+    "sqlite",
+    "trino",
+    "presto",
+    "prestosql"
   ],
   "contributors": [
     "Adrien Pyke <adpyke@gmail.com>",
@@ -31,6 +34,7 @@
     "Andrew",
     "Benjamin Bellamy",
     "bingou",
+    "Boris Verkhovskiy <boris.verk@gmail.com>",
     "Damon Davison <ddavison@avalere.com>",
     "Davut Can Abacigil <can@teamsql.io>",
     "Erik Hirmo <erik.hirmo@roguewave.com>",
diff --git a/sql/arrays-and-maps.md b/sql/arrays-and-maps.md
index 6393a771..d29e8287 100644
--- a/sql/arrays-and-maps.md
+++ b/sql/arrays-and-maps.md
@@ -13,6 +13,7 @@ Array literals `ARRAY[1, 2, 3]`. Supported by:
 
 - [BigQuery][bigquery-literals].
 - [PostgreSQL][postgres-literals].
+- [Trino][]
 
 Map literals in JSON style `{"foo": 1, "bar": "John"}`. Supported by:
 
@@ -28,6 +29,7 @@ Supported by:
 - [Spark][]
 - [N1QL][]
 - [PostgreSQL][]
+- [Trino][]
 
 Array subscript operator `arr[OFFSET(5)]`. Supported by:
 
@@ -39,5 +41,6 @@ Array subscript operator `arr[OFFSET(5)]`. Supported by:
 [n1ql-literals]: https://docs.couchbase.com/server/current/n1ql/n1ql-language-reference/datatypes.html#arrays
 [n1ql]: https://docs.couchbase.com/server/current/n1ql/n1ql-language-reference/nestedops.html#field-selection
 [postgresql]: https://www.postgresql.org/docs/current/arrays.html#ARRAYS-ACCESSING
+[trino]: https://trino.io/docs/current/functions/array.html
 [bigquery-literals]: https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#array_literals
 [postgres-literals]: https://www.postgresql.org/docs/current/arrays.html
diff --git a/sql/create-table.md b/sql/create-table.md
index c0418912..e7dd3b2b 100644
--- a/sql/create-table.md
+++ b/sql/create-table.md
@@ -61,6 +61,10 @@ _No support for CREATE TABLE._
 
     CREATE TABLE
 
+[Trino][]:
+
+    CREATE TABLE [IF NOT EXISTS]
+
 [sql standard]: https://jakewheat.github.io/sql-overview/sql-2008-foundation-grammar.html#_11_3_table_definition
 [bigquery]: https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#create_table_statement
 [db2]: https://www.ibm.com/docs/en/db2/9.7?topic=statements-create-table
@@ -74,3 +78,4 @@ _No support for CREATE TABLE._
 [spark]: https://spark.apache.org/docs/latest/sql-ref-syntax-ddl-create-table.html
 [sqlite]: https://www.sqlite.org/lang_createtable.html
 [transact-sql]: https://docs.microsoft.com/en-us/sql/t-sql/statements/create-table-transact-sql?view=sql-server-ver15
+[trino]: https://trino.io/docs/current/sql/create-table.html
diff --git a/sql/create-view.md b/sql/create-view.md
index 1edc9f12..7d6ccfa1 100644
--- a/sql/create-view.md
+++ b/sql/create-view.md
@@ -64,6 +64,10 @@ _No support for CREATE VIEW._
 
     CREATE [OR ALTER] VIEW
 
+[Trino][]:
+
+    CREATE [OR REPLACE] VIEW
+
 [sql standard]: https://jakewheat.github.io/sql-overview/sql-2008-foundation-grammar.html#_11_22_view_definition
 [bigquery]: https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#create_view_statement
 [db2]: https://www.ibm.com/docs/en/db2/9.7?topic=statements-create-view
@@ -77,3 +81,4 @@ _No support for CREATE VIEW._
 [spark]: https://spark.apache.org/docs/latest/sql-ref-syntax-ddl-create-view.html
 [sqlite]: https://www.sqlite.org/lang_createview.html
 [transact-sql]: https://docs.microsoft.com/en-us/sql/t-sql/statements/create-view-transact-sql?view=sql-server-ver15
+[trino]: https://trino.io/docs/current/sql/create-view.html
diff --git a/sql/delete.md b/sql/delete.md
index 1441f6c7..603f84d3 100644
--- a/sql/delete.md
+++ b/sql/delete.md
@@ -96,6 +96,10 @@ _No support for DELETE_
       [WHERE condition | WHERE CURRENT OF [GLOBAL] cursor_name]
       [OPTION query_hints]
 
+[Trino][]:
+
+    DELETE FROM table_name [WHERE condition]
+
 [sql standard]: https://jakewheat.github.io/sql-overview/sql-2008-foundation-grammar.html#_14_8_delete_statement_searched
 [bigquery]: https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#delete_statement
 [db2]: https://www.ibm.com/docs/en/db2/9.7?topic=statements-delete
@@ -109,3 +113,4 @@ _No support for DELETE_
 [spark]: https://spark.apache.org/docs/latest/sql-ref-syntax.html#dml-statements
 [sqlite]: https://www.sqlite.org/lang_delete.html
 [transact-sql]: https://docs.microsoft.com/en-us/sql/t-sql/statements/delete-transact-sql?view=sql-server-ver16
+[trino]: https://trino.io/docs/current/sql/delete.html
diff --git a/sql/identifiers.md b/sql/identifiers.md
index 2b5f946a..e03a37d8 100644
--- a/sql/identifiers.md
+++ b/sql/identifiers.md
@@ -18,6 +18,7 @@ The differences from this are listed below:
 - [SQLite][sqlite-syntax-pdf]: _(no differences)_
 - [Transact-SQL][]: `@` and `#` are allowed as first chars plus `$` in the rest. Also unicode letters are allowed.
   Though the beginning `@` signifies a local variable or parameter and `#` a temporary table or procedure.
+- [Trino][]: `[a-zA-Z0-9_]+`, no first-letter restrictions
 
 ## Delimited identifiers
 
@@ -36,6 +37,7 @@ There is a considerable variation in implementations:
 - `` `..` `` [Spark][]
 - `".."`, `` `..` ``, `[..]` [SQLite][sqlite-keywords]
 - `".."`<sup>3</sup>, `[..]` [Transact-SQL][]
+- `".."`, `` `..` `` [Trino][]
 
 Notes:
 
@@ -56,3 +58,4 @@ Notes:
 [sqlite-keywords]: https://www.sqlite.org/lang_keywords.html
 [sqlite-syntax-pdf]: https://www.pearsonhighered.com/assets/samplechapter/0/6/7/2/067232685X.pdf
 [transact-sql]: https://docs.microsoft.com/en-us/sql/relational-databases/databases/database-identifiers?view=sql-server-ver15
+[trino]: https://github.com/trinodb/trino/blob/ca7dcaa873b9dd24185e9a69cecdd1dd8717694c/core/trino-parser/src/main/antlr4/io/trino/sql/parser/SqlBase.g4#L1175-L1189
diff --git a/sql/insert.md b/sql/insert.md
index e8a350d9..f5bbadf9 100644
--- a/sql/insert.md
+++ b/sql/insert.md
@@ -60,6 +60,10 @@ All dialects (except Hive) suppurt this syntax, plus a bunch of extra stuff:
 
     INSERT [TOP ( expression ) [PERCENT]] [INTO] table_name
 
+[Trino][]:
+
+    INSERT INTO table_name
+
 [sql standard]: https://jakewheat.github.io/sql-overview/sql-2008-foundation-grammar.html#insert-statement
 [bigquery]: https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#insert_statement
 [db2]: https://www.ibm.com/docs/en/db2/9.7?topic=statements-insert
@@ -73,3 +77,4 @@ All dialects (except Hive) suppurt this syntax, plus a bunch of extra stuff:
 [spark]: https://spark.apache.org/docs/latest/sql-ref-syntax-dml-insert-table.html
 [sqlite]: https://www.sqlite.org/lang_insert.html
 [transact-sql]: https://docs.microsoft.com/en-us/sql/t-sql/statements/insert-transact-sql?view=sql-server-ver16
+[trino]: https://trino.io/docs/current/sql/insert.html
diff --git a/sql/parameters.md b/sql/parameters.md
index ac80d6ed..ebdce40a 100644
--- a/sql/parameters.md
+++ b/sql/parameters.md
@@ -15,6 +15,7 @@ These come in the form of single question mark (`?`), supported by:
 - [N1QL][]
 - [SQLite][]
 - [Transact-SQL][]<sup>1</sup>
+- [Trino][]
 
 ## Numbered parameters
 
@@ -51,3 +52,4 @@ These come in the form of single question mark (`?`), supported by:
 [redshift]: https://docs.aws.amazon.com/redshift/latest/dg/r_PREPARE.html
 [sqlite]: https://sqlite.org/c3ref/bind_blob.html
 [transact-sql]: https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/configuring-parameters-and-parameter-data-types
+[trino]: https://trino.io/docs/current/sql/prepare.html
diff --git a/sql/select.md b/sql/select.md
index 7952adce..65ef2be5 100644
--- a/sql/select.md
+++ b/sql/select.md
@@ -66,6 +66,10 @@ All dialects support that, but also quite a bit extra stuff:
       [ALL | DISTINCT]
       [TOP ( expression ) [PERCENT] [WITH TIES]]
 
+[Trino][]:
+
+    SELECT [ALL | DISTINCT]
+
 [sql standard]: https://jakewheat.github.io/sql-overview/sql-2008-foundation-grammar.html#query-specification
 [bigquery]: https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax
 [db2]: https://www.ibm.com/docs/en/db2/9.7?topic=queries-subselect
@@ -79,3 +83,4 @@ All dialects support that, but also quite a bit extra stuff:
 [spark]: https://spark.apache.org/docs/latest/sql-ref-syntax-qry-select.html
 [sqlite]: https://www.sqlite.org/lang_select.html
 [transact-sql]: https://docs.microsoft.com/en-US/sql/t-sql/queries/select-transact-sql?view=sql-server-ver15
+[trino]: https://trino.io/docs/current/sql/select.html
diff --git a/sql/strings.md b/sql/strings.md
index 4cb4efb5..c07bd5fe 100644
--- a/sql/strings.md
+++ b/sql/strings.md
@@ -46,6 +46,10 @@ The real world implementations have lots of variation:
   - `'..'` (two single quotes `''` are used for escaping)
   - (`".."`<sup>3</sup>)
   - `N'..'` (`N".."`<sup>3</sup>) unicode strings
+- [Trino][]:
+  - `'..'` (two single quotes `''` are used for escaping)
+  - `U&'..'`, `u&'..'` string with unicode escapes (two single quotes `''` are used for escaping)
+  - `X'..'`, `x'..'` hex string
 
 ### Notes:
 
@@ -67,3 +71,4 @@ The real world implementations have lots of variation:
 [spark]: https://spark.apache.org/docs/latest/sql-ref-literals.html#string-literal
 [sqlite]: https://www.sqlite.org/lang_expr.html#literal_values_constants_
 [transact-sql]: https://docs.microsoft.com/en-us/sql/t-sql/data-types/constants-transact-sql?view=sql-server-ver15
+[trino]: https://github.com/trinodb/trino/blob/ca7dcaa873b9dd24185e9a69cecdd1dd8717694c/core/trino-parser/src/main/antlr4/io/trino/sql/parser/SqlBase.g4#L1146-L1159
diff --git a/sql/update.md b/sql/update.md
index c1f19f32..99759401 100644
--- a/sql/update.md
+++ b/sql/update.md
@@ -108,6 +108,12 @@ _No support for UPDATE_
       [WHERE condition | WHERE CURRENT OF [GLOBAL] cursor_name]
       [OPTION query_hints]
 
+[Trino][]:
+
+    UPDATE table_name
+      SET [(column = expression [, ... ])]
+      [WHERE condition]
+
 [sql standard]: https://jakewheat.github.io/sql-overview/sql-2008-foundation-grammar.html#_14_13_update_statement_searched
 [bigquery]: https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#update_statement
 [db2]: https://www.ibm.com/docs/en/db2/9.7?topic=statements-update
@@ -121,3 +127,4 @@ _No support for UPDATE_
 [spark]: https://spark.apache.org/docs/latest/sql-ref-syntax.html#dml-statements
 [sqlite]: https://www.sqlite.org/lang_update.html
 [transact-sql]: https://docs.microsoft.com/en-us/sql/t-sql/queries/update-transact-sql?view=sql-server-ver16
+[trino]: https://trino.io/docs/current/sql/update.html
diff --git a/sql/variables.md b/sql/variables.md
index 32582acf..527cb364 100644
--- a/sql/variables.md
+++ b/sql/variables.md
@@ -16,6 +16,7 @@
 - [Spark][]: `${name}` Like with Hive, these are substitution variables.
 - SQLite: _N/A_
 - [Transact-SQL][]: `@name` (using identifier syntax for name)
+- Trino: _N/A_
 
 [parameters]: ./parameters.md
 [bigquery]: https://cloud.google.com/bigquery/docs/reference/standard-sql/procedural-language
diff --git a/src/languages/sqlite/sqlite.keywords.ts b/src/languages/sqlite/sqlite.keywords.ts
index facd455d..9996efcb 100644
--- a/src/languages/sqlite/sqlite.keywords.ts
+++ b/src/languages/sqlite/sqlite.keywords.ts
@@ -1,7 +1,7 @@
 import { flatKeywordList } from '../../utils';
 
 export const keywords = flatKeywordList({
-  // https://www.sqlite.org/lang_keywords.html
+  // https://github.com/trinodb/trino/blob/432d2897bdef99388c1a47188743a061c4ac1f34/core/trino-parser/src/main/antlr4/io/trino/sql/parser/SqlBase.g4#L858-L1128
   all: [
     'ABORT',
     'ACTION',
diff --git a/src/languages/trino/trino.formatter.ts b/src/languages/trino/trino.formatter.ts
new file mode 100644
index 00000000..6e9b1bb9
--- /dev/null
+++ b/src/languages/trino/trino.formatter.ts
@@ -0,0 +1,147 @@
+import Formatter from 'src/formatter/Formatter';
+import Tokenizer from 'src/lexer/Tokenizer';
+import { functions } from './trino.functions';
+import { keywords } from './trino.keywords';
+
+// https://github.com/trinodb/trino/blob/432d2897bdef99388c1a47188743a061c4ac1f34/core/trino-parser/src/main/antlr4/io/trino/sql/parser/SqlBase.g4#L41
+const reservedCommands = [
+  // DDL
+  'ALTER SCHEMA',
+  'ALTER TABLE',
+  'RENAME TO',
+  'ADD COLUMN',
+  'RENAME COLUMN',
+  'DROP COLUMN',
+  'SET AUTHORIZATION',
+  'SET PROPERTIES',
+  'EXECUTE',
+  'ALTER MATERIALIZED VIEW',
+  'ALTER VIEW',
+  'CREATE SCHEMA',
+  'CREATE TABLE',
+  'CREATE VIEW',
+  'CREATE OR REPLACE VIEW',
+  'CREATE MATERIALIZED VIEW',
+  'CREATE OR REPLACE MATERIALIZED VIEW',
+  'CREATE ROLE',
+  'DROP SCHEMA',
+  'DROP TABLE',
+  'DROP COLUMN',
+  'DROP MATERIALIZED VIEW',
+  'DROP VIEW',
+  'DROP ROLE',
+  'TRUNCATE TABLE',
+  // DML
+  'INSERT INTO',
+  'MERGE INTO',
+  'VALUES',
+  'UPDATE',
+  'SET',
+  'DELETE FROM',
+  // Data Retrieval
+  'WITH',
+  'SELECT',
+  'FROM',
+  'WHERE',
+  'GROUP BY',
+  'HAVING',
+  'WINDOW',
+  'ORDER BY',
+  'OFFSET',
+  'LIMIT',
+  'FETCH',
+
+  'PARTITION BY',
+
+  // Auxiliary
+  'EXPLAIN',
+  'ANALYZE',
+  'EXPLAIN ANALYZE',
+  'EXPLAIN ANALYZE VERBOSE',
+  'USE',
+
+  'COMMENT ON TABLE',
+  'COMMENT ON COLUMN',
+  'DESCRIBE INPUT',
+  'DESCRIBE OUTPUT',
+
+  'REFRESH MATERIALIZED VIEW',
+  'RESET SESSION',
+  'SET SESSION',
+  'SET PATH',
+  'SET TIME ZONE',
+
+  'SHOW GRANTS',
+  'SHOW CREATE TABLE',
+  'SHOW CREATE SCHEMA',
+  'SHOW CREATE VIEW',
+  'SHOW CREATE MATERIALIZED VIEW',
+  'SHOW TABLES',
+  'SHOW SCHEMAS',
+  'SHOW CATALOGS',
+  'SHOW COLUMNS',
+  'SHOW STATS FOR',
+  'SHOW ROLES',
+  'SHOW CURRENT ROLES',
+  'SHOW ROLE GRANTS',
+  'SHOW FUNCTIONS',
+  'SHOW SESSION',
+];
+
+// https://github.com/trinodb/trino/blob/432d2897bdef99388c1a47188743a061c4ac1f34/core/trino-parser/src/main/antlr4/io/trino/sql/parser/SqlBase.g4#L231-L235
+// https://github.com/trinodb/trino/blob/432d2897bdef99388c1a47188743a061c4ac1f34/core/trino-parser/src/main/antlr4/io/trino/sql/parser/SqlBase.g4#L288-L291
+const reservedBinaryCommands = [
+  // set booleans
+  'INTERSECT',
+  'INTERSECT ALL',
+  'INTERSECT DISTINCT',
+  'UNION',
+  'UNION ALL',
+  'UNION DISTINCT',
+  'EXCEPT',
+  'EXCEPT ALL',
+  'EXCEPT DISTINCT',
+];
+
+// https://github.com/trinodb/trino/blob/432d2897bdef99388c1a47188743a061c4ac1f34/core/trino-parser/src/main/antlr4/io/trino/sql/parser/SqlBase.g4#L299-L313
+const reservedJoins = [
+  'JOIN',
+  'INNER JOIN',
+  'LEFT JOIN',
+  'LEFT OUTER JOIN',
+  'RIGHT JOIN',
+  'RIGHT OUTER JOIN',
+  'FULL JOIN',
+  'FULL OUTER JOIN',
+  'CROSS JOIN',
+  'NATURAL JOIN',
+  'NATURAL INNER JOIN',
+  'NATURAL LEFT JOIN',
+  'NATURAL LEFT OUTER JOIN',
+  'NATURAL RIGHT JOIN',
+  'NATURAL RIGHT OUTER JOIN',
+  'NATURAL FULL JOIN',
+  'NATURAL FULL OUTER JOIN',
+];
+
+export default class TrinoFormatter extends Formatter {
+  // https://trino.io/docs/current/functions/list.html#id1
+  static operators = ['||', '->'];
+
+  tokenizer() {
+    return new Tokenizer({
+      reservedCommands,
+      reservedBinaryCommands,
+      reservedJoins,
+      reservedDependentClauses: ['WHEN', 'ELSE'],
+      reservedKeywords: keywords,
+      reservedFunctionNames: functions,
+      openParens: ['(', '['],
+      closeParens: [')', ']'],
+      stringTypes: [{ quote: "''", prefixes: ['U&', 'X'] }],
+      identTypes: ['""', '``'],
+      positionalParams: true,
+      operators: TrinoFormatter.operators,
+    });
+  }
+}
diff --git a/src/languages/trino/trino.functions.ts b/src/languages/trino/trino.functions.ts
new file mode 100644
index 00000000..8ff1b0bc
--- /dev/null
+++ b/src/languages/trino/trino.functions.ts
@@ -0,0 +1,431 @@
+import { flatKeywordList } from '../../utils';
+
+export const functions = flatKeywordList({
+  // https://github.com/trinodb/trino/tree/432d2897bdef99388c1a47188743a061c4ac1f34/docs/src/main/sphinx/functions
+  // rg '^\.\. function::' ./docs/src/main/sphinx/functions | cut -d' ' -f 3 | cut -d '(' -f 1 | sort | uniq
+  // rg '\* ' ./docs/src/main/sphinx/functions/list-by-topic.rst | grep    '\* :func:' | cut -d'`' -f 2
+  // rg '\* ' ./docs/src/main/sphinx/functions/list-by-topic.rst | grep -v '\* :func:'
+  // grep -e '^- ' ./docs/src/main/sphinx/functions/list.rst | grep  -e '^- :func:' | cut -d'`' -f2
+  // grep -e '^- ' ./docs/src/main/sphinx/functions/list.rst | grep -ve '^- :func:'
+  all: [
+    'ABS',
+    'ACOS',
+    'ALL_MATCH',
+    'ANY_MATCH',
+    'APPROX_DISTINCT',
+    'APPROX_MOST_FREQUENT',
+    'APPROX_PERCENTILE',
+    'APPROX_SET',
+    'ARBITRARY',
+    'ARRAYS_OVERLAP',
+    'ARRAY_AGG',
+    'ARRAY_DISTINCT',
+    'ARRAY_EXCEPT',
+    'ARRAY_INTERSECT',
+    'ARRAY_JOIN',
+    'ARRAY_MAX',
+    'ARRAY_MIN',
+    'ARRAY_POSITION',
+    'ARRAY_REMOVE',
+    'ARRAY_SORT',
+    'ARRAY_UNION',
+    'ASIN',
+    'ATAN',
+    'ATAN2',
+    'AT_TIMEZONE',
+    'AVG',
+    'BAR',
+    'BETA_CDF',
+    'BING_TILE',
+    'BING_TILES_AROUND',
+    'BING_TILE_AT',
+    'BING_TILE_COORDINATES',
+    'BING_TILE_POLYGON',
+    'BING_TILE_QUADKEY',
+    'BING_TILE_ZOOM_LEVEL',
+    'BITWISE_AND',
+    'BITWISE_AND_AGG',
+    'BITWISE_LEFT_SHIFT',
+    'BITWISE_NOT',
+    'BITWISE_OR',
+    'BITWISE_OR_AGG',
+    'BITWISE_RIGHT_SHIFT',
+    'BITWISE_RIGHT_SHIFT_ARITHMETIC',
+    'BITWISE_XOR',
+    'BIT_COUNT',
+    'BOOL_AND',
+    'BOOL_OR',
+    'CARDINALITY',
+    'CAST',
+    'CBRT',
+    'CEIL',
+    'CEILING',
+    'CHAR2HEXINT',
+    'CHECKSUM',
+    'CHR',
+    'CLASSIFY',
+    'COALESCE',
+    'CODEPOINT',
+    'COLOR',
+    'COMBINATIONS',
+    'CONCAT',
+    'CONCAT_WS',
+    'CONTAINS',
+    'CONTAINS_SEQUENCE',
+    'CONVEX_HULL_AGG',
+    'CORR',
+    'COS',
+    'COSH',
+    'COSINE_SIMILARITY',
+    'COUNT',
+    'COUNT_IF',
+    'COVAR_POP',
+    'COVAR_SAMP',
+    'CRC32',
+    'CUME_DIST',
+    'CURRENT_CATALOG',
+    'CURRENT_DATE',
+    'CURRENT_GROUPS',
+    'CURRENT_SCHEMA',
+    'CURRENT_TIME',
+    'CURRENT_TIMESTAMP',
+    'CURRENT_TIMEZONE',
+    'CURRENT_USER',
+    'DATE',
+    'DATE_ADD',
+    'DATE_DIFF',
+    'DATE_FORMAT',
+    'DATE_PARSE',
+    'DATE_TRUNC',
+    'DAY',
+    'DAY_OF_MONTH',
+    'DAY_OF_WEEK',
+    'DAY_OF_YEAR',
+    'DEGREES',
+    'DENSE_RANK',
+    'DOW',
+    'DOY',
+    'E',
+    'ELEMENT_AT',
+    'EMPTY_APPROX_SET',
+    'EVALUATE_CLASSIFIER_PREDICTIONS',
+    'EVERY',
+    'EXP',
+    'EXTRACT',
+    'FEATURES',
+    'FILTER',
+    'FIRST_VALUE',
+    'FLATTEN',
+    'FLOOR',
+    'FORMAT',
+    'FORMAT_DATETIME',
+    'FORMAT_NUMBER',
+    'FROM_BASE',
+    'FROM_BASE32',
+    'FROM_BASE64',
+    'FROM_BASE64URL',
+    'FROM_BIG_ENDIAN_32',
+    'FROM_BIG_ENDIAN_64',
+    'FROM_ENCODED_POLYLINE',
+    'FROM_GEOJSON_GEOMETRY',
+    'FROM_HEX',
+    'FROM_IEEE754_32',
+    'FROM_IEEE754_64',
+    'FROM_ISO8601_DATE',
+    'FROM_ISO8601_TIMESTAMP',
+    'FROM_ISO8601_TIMESTAMP_NANOS',
+    'FROM_UNIXTIME',
+    'FROM_UNIXTIME_NANOS',
+    'FROM_UTF8',
+    'GEOMETRIC_MEAN',
+    'GEOMETRY_FROM_HADOOP_SHAPE',
+    'GEOMETRY_INVALID_REASON',
+    'GEOMETRY_NEAREST_POINTS',
+    'GEOMETRY_TO_BING_TILES',
+    'GEOMETRY_UNION',
+    'GEOMETRY_UNION_AGG',
+    'GREATEST',
+    'GREAT_CIRCLE_DISTANCE',
+    'HAMMING_DISTANCE',
+    'HASH_COUNTS',
+    'HISTOGRAM',
+    'HMAC_MD5',
+    'HMAC_SHA1',
+    'HMAC_SHA256',
+    'HMAC_SHA512',
+    'HOUR',
+    'HUMAN_READABLE_SECONDS',
+    'IF',
+    'INDEX',
+    'INFINITY',
+    'INTERSECTION_CARDINALITY',
+    'INVERSE_BETA_CDF',
+    'INVERSE_NORMAL_CDF',
+    'IS_FINITE',
+    'IS_INFINITE',
+    'IS_JSON_SCALAR',
+    'IS_NAN',
+    'JACCARD_INDEX',
+    'JSON_ARRAY_CONTAINS',
+    'JSON_ARRAY_GET',
+    'JSON_ARRAY_LENGTH',
+    'JSON_EXISTS',
+    'JSON_EXTRACT',
+    'JSON_EXTRACT_SCALAR',
+    'JSON_FORMAT',
+    'JSON_PARSE',
+    'JSON_QUERY',
+    'JSON_SIZE',
+    'JSON_VALUE',
+    'KURTOSIS',
+    'LAG',
+    'LAST_DAY_OF_MONTH',
+    'LAST_VALUE',
+    'LEAD',
+    'LEARN_CLASSIFIER',
+    'LEARN_LIBSVM_CLASSIFIER',
+    'LEARN_LIBSVM_REGRESSOR',
+    'LEARN_REGRESSOR',
+    'LEAST',
+    'LENGTH',
+    'LEVENSHTEIN_DISTANCE',
+    'LINE_INTERPOLATE_POINT',
+    'LINE_INTERPOLATE_POINTS',
+    'LINE_LOCATE_POINT',
+    'LISTAGG',
+    'LN',
+    'LOCALTIME',
+    'LOCALTIMESTAMP',
+    'LOG',
+    'LOG10',
+    'LOG2',
+    'LOWER',
+    'LPAD',
+    'LTRIM',
+    'LUHN_CHECK',
+    'MAKE_SET_DIGEST',
+    'MAP',
+    'MAP_AGG',
+    'MAP_CONCAT',
+    'MAP_ENTRIES',
+    'MAP_FILTER',
+    'MAP_FROM_ENTRIES',
+    'MAP_KEYS',
+    'MAP_UNION',
+    'MAP_VALUES',
+    'MAP_ZIP_WITH',
+    'MAX',
+    'MAX_BY',
+    'MD5',
+    'MERGE',
+    'MERGE_SET_DIGEST',
+    'MILLISECOND',
+    'MIN',
+    'MINUTE',
+    'MIN_BY',
+    'MOD',
+    'MONTH',
+    'MULTIMAP_AGG',
+    'MULTIMAP_FROM_ENTRIES',
+    'MURMUR3',
+    'NAN',
+    'NGRAMS',
+    'NONE_MATCH',
+    'NORMALIZE',
+    'NORMAL_CDF',
+    'NOW',
+    'NTH_VALUE',
+    'NTILE',
+    'NULLIF',
+    'NUMERIC_HISTOGRAM',
+    'OBJECTID',
+    'OBJECTID_TIMESTAMP',
+    'PARSE_DATA_SIZE',
+    'PARSE_DATETIME',
+    'PARSE_DURATION',
+    'PERCENT_RANK',
+    'PI',
+    'POSITION',
+    'POW',
+    'POWER',
+    'QDIGEST_AGG',
+    'QUARTER',
+    'RADIANS',
+    'RAND',
+    'RANDOM',
+    'RANK',
+    'REDUCE',
+    'REDUCE_AGG',
+    'REGEXP_COUNT',
+    'REGEXP_EXTRACT',
+    'REGEXP_EXTRACT_ALL',
+    'REGEXP_LIKE',
+    'REGEXP_POSITION',
+    'REGEXP_REPLACE',
+    'REGEXP_SPLIT',
+    'REGRESS',
+    'REGR_INTERCEPT',
+    'REGR_SLOPE',
+    'RENDER',
+    'REPEAT',
+    'REPLACE',
+    'REVERSE',
+    'RGB',
+    'ROUND',
+    'ROW_NUMBER',
+    'RPAD',
+    'RTRIM',
+    'SECOND',
+    'SEQUENCE',
+    'SHA1',
+    'SHA256',
+    'SHA512',
+    'SHUFFLE',
+    'SIGN',
+    'SIMPLIFY_GEOMETRY',
+    'SIN',
+    'SKEWNESS',
+    'SLICE',
+    'SOUNDEX',
+    'SPATIAL_PARTITIONING',
+    'SPATIAL_PARTITIONS',
+    'SPLIT',
+    'SPLIT_PART',
+    'SPLIT_TO_MAP',
+    'SPLIT_TO_MULTIMAP',
+    'SPOOKY_HASH_V2_32',
+    'SPOOKY_HASH_V2_64',
+    'SQRT',
+    'STARTS_WITH',
+    'STDDEV',
+    'STDDEV_POP',
+    'STDDEV_SAMP',
+    'STRPOS',
+    'ST_AREA',
+    'ST_ASBINARY',
+    'ST_ASTEXT',
+    'ST_BOUNDARY',
+    'ST_BUFFER',
+    'ST_CENTROID',
+    'ST_CONTAINS',
+    'ST_CONVEXHULL',
+    'ST_COORDDIM',
+    'ST_CROSSES',
+    'ST_DIFFERENCE',
+    'ST_DIMENSION',
+    'ST_DISJOINT',
+    'ST_DISTANCE',
+    'ST_ENDPOINT',
+    'ST_ENVELOPE',
+    'ST_ENVELOPEASPTS',
+    'ST_EQUALS',
+    'ST_EXTERIORRING',
+    'ST_GEOMETRIES',
+    'ST_GEOMETRYFROMTEXT',
+    'ST_GEOMETRYN',
+    'ST_GEOMETRYTYPE',
+    'ST_GEOMFROMBINARY',
+    'ST_INTERIORRINGN',
+    'ST_INTERIORRINGS',
+    'ST_INTERSECTION',
+    'ST_INTERSECTS',
+    'ST_ISCLOSED',
+    'ST_ISEMPTY',
+    'ST_ISRING',
+    'ST_ISSIMPLE',
+    'ST_ISVALID',
+    'ST_LENGTH',
+    'ST_LINEFROMTEXT',
+    'ST_LINESTRING',
+    'ST_MULTIPOINT',
+    'ST_NUMGEOMETRIES',
+    'ST_NUMINTERIORRING',
+    'ST_NUMPOINTS',
+    'ST_OVERLAPS',
+    'ST_POINT',
+    'ST_POINTN',
+    'ST_POINTS',
+    'ST_POLYGON',
+    'ST_RELATE',
+    'ST_STARTPOINT',
+    'ST_SYMDIFFERENCE',
+    'ST_TOUCHES',
+    'ST_UNION',
+    'ST_WITHIN',
+    'ST_X',
+    'ST_XMAX',
+    'ST_XMIN',
+    'ST_Y',
+    'ST_YMAX',
+    'ST_YMIN',
+    'SUBSTR',
+    'SUBSTRING',
+    'SUM',
+    'TAN',
+    'TANH',
+    'TDIGEST_AGG',
+    'TIMESTAMP_OBJECTID',
+    'TIMEZONE_HOUR',
+    'TIMEZONE_MINUTE',
+    'TO_BASE',
+    'TO_BASE32',
+    'TO_BASE64',
+    'TO_BASE64URL',
+    'TO_BIG_ENDIAN_32',
+    'TO_BIG_ENDIAN_64',
+    'TO_CHAR',
+    'TO_DATE',
+    'TO_ENCODED_POLYLINE',
+    'TO_GEOJSON_GEOMETRY',
+    'TO_GEOMETRY',
+    'TO_HEX',
+    'TO_IEEE754_32',
+    'TO_IEEE754_64',
+    'TO_ISO8601',
+    'TO_MILLISECONDS',
+    'TO_SPHERICAL_GEOGRAPHY',
+    'TO_TIMESTAMP',
+    'TO_UNIXTIME',
+    'TO_UTF8',
+    'TRANSFORM',
+    'TRANSFORM_KEYS',
+    'TRANSFORM_VALUES',
+    'TRANSLATE',
+    'TRIM',
+    'TRIM_ARRAY',
+    'TRUNCATE',
+    'TRY',
+    'TRY_CAST',
+    'TYPEOF',
+    'UPPER',
+    'URL_DECODE',
+    'URL_ENCODE',
+    'URL_EXTRACT_FRAGMENT',
+    'URL_EXTRACT_HOST',
+    'URL_EXTRACT_PARAMETER',
+    'URL_EXTRACT_PATH',
+    'URL_EXTRACT_PORT',
+    'URL_EXTRACT_PROTOCOL',
+    'URL_EXTRACT_QUERY',
+    'UUID',
+    'VALUES_AT_QUANTILES',
+    'VALUE_AT_QUANTILE',
+    'VARIANCE',
+    'VAR_POP',
+    'VAR_SAMP',
+    'VERSION',
+    'WEEK',
+    'WEEK_OF_YEAR',
+    'WIDTH_BUCKET',
+    'WILSON_INTERVAL_LOWER',
+    'WILSON_INTERVAL_UPPER',
+    'WITH_TIMEZONE',
+    'WORD_STEM',
+    'XXHASH64',
+    'YEAR',
+    'YEAR_OF_WEEK',
+    'YOW',
+    'ZIP',
+    'ZIP_WITH',
+  ],
+});
diff --git a/src/languages/trino/trino.keywords.ts b/src/languages/trino/trino.keywords.ts
new file mode 100644
index 00000000..4dd08adb
--- /dev/null
+++ b/src/languages/trino/trino.keywords.ts
@@ -0,0 +1,281 @@
+import { flatKeywordList } from '../../utils';
+
+export const keywords = flatKeywordList({
+  // https://www.sqlite.org/lang_keywords.html
+  all: [
+    'ABSENT',
+    'ADD',
+    'ADMIN',
+    'AFTER',
+    'ALL',
+    'ALTER',
+    'ANALYZE',
+    // 'AND',
+    'ANY',
+    'ARRAY',
+    'AS',
+    'ASC',
+    'AT',
+    'AUTHORIZATION',
+    'BERNOULLI',
+    'BETWEEN',
+    'BOTH',
+    'BY',
+    'CALL',
+    'CASCADE',
+    'CASE',
+    'CATALOGS',
+    'COLUMN',
+    'COLUMNS',
+    'COMMENT',
+    'COMMIT',
+    'COMMITTED',
+    'CONDITIONAL',
+    'CONSTRAINT',
+    'COPARTITION',
+    'CREATE',
+    'CROSS',
+    'CUBE',
+    'CURRENT',
+    'CURRENT_PATH',
+    'CURRENT_ROLE',
+    'DATA',
+    'DEALLOCATE',
+    'DEFAULT',
+    'DEFINE',
+    'DEFINER',
+    'DELETE',
+    'DENY',
+    'DESC',
+    'DESCRIBE',
+    'DESCRIPTOR',
+    'DISTINCT',
+    'DISTRIBUTED',
+    'DOUBLE',
+    'DROP',
+    'ELSE',
+    'EMPTY',
+    'ENCODING',
+    'END',
+    'ERROR',
+    'ESCAPE',
+    'EXCEPT',
+    'EXCLUDING',
+    'EXECUTE',
+    'EXISTS',
+    'EXPLAIN',
+    'FALSE',
+    'FETCH',
+    'FINAL',
+    'FIRST',
+    'FOLLOWING',
+    'FOR',
+    'FROM',
+    'FULL',
+    'FUNCTIONS',
+    'GRANT',
+    'GRANTED',
+    'GRANTS',
+    'GRAPHVIZ',
+    'GROUP',
+    'GROUPING',
+    'GROUPS',
+    'HAVING',
+    'IGNORE',
+    'IN',
+    'INCLUDING',
+    'INITIAL',
+    'INNER',
+    'INPUT',
+    'INSERT',
+    'INTERSECT',
+    'INTERVAL',
+    'INTO',
+    'INVOKER',
+    'IO',
+    'IS',
+    'ISOLATION',
+    'JOIN',
+    'JSON',
+    'JSON_ARRAY',
+    'JSON_OBJECT',
+    'KEEP',
+    'KEY',
+    'KEYS',
+    'LAST',
+    'LATERAL',
+    'LEADING',
+    'LEFT',
+    'LEVEL',
+    'LIKE',
+    'LIMIT',
+    'LOCAL',
+    'LOGICAL',
+    'MATCH',
+    'MATCHED',
+    'MATCHES',
+    'MATCH_RECOGNIZE',
+    'MATERIALIZED',
+    'MEASURES',
+    'NATURAL',
+    'NEXT',
+    'NFC',
+    'NFD',
+    'NFKC',
+    'NFKD',
+    'NO',
+    'NONE',
+    'NOT',
+    'NULL',
+    'NULLS',
+    'OBJECT',
+    'OF',
+    'OFFSET',
+    'OMIT',
+    'ON',
+    'ONE',
+    'ONLY',
+    'OPTION',
+    // 'OR',
+    'ORDER',
+    'ORDINALITY',
+    'OUTER',
+    'OUTPUT',
+    'OVER',
+    'OVERFLOW',
+    'PARTITION',
+    'PARTITIONS',
+    'PASSING',
+    'PAST',
+    'PATH',
+    'PATTERN',
+    'PER',
+    'PERMUTE',
+    'PRECEDING',
+    'PRECISION',
+    'PREPARE',
+    'PRIVILEGES',
+    'PROPERTIES',
+    'PRUNE',
+    'QUOTES',
+    'RANGE',
+    'READ',
+    'RECURSIVE',
+    'REFRESH',
+    'RENAME',
+    'REPEATABLE',
+    'RESET',
+    'RESPECT',
+    'RESTRICT',
+    'RETURNING',
+    'REVOKE',
+    'RIGHT',
+    'ROLE',
+    'ROLES',
+    'ROLLBACK',
+    'ROLLUP',
+    'ROW',
+    'ROWS',
+    'RUNNING',
+    'SCALAR',
+    'SCHEMA',
+    'SCHEMAS',
+    'SECURITY',
+    'SEEK',
+    'SELECT',
+    'SERIALIZABLE',
+    'SESSION',
+    'SET',
+    'SETS',
+    'SHOW',
+    'SKIP',
+    'SOME',
+    'START',
+    'STATS',
+    'STRING',
+    'SUBSET',
+    'SYSTEM',
+    'TABLE',
+    'TABLES',
+    'TABLESAMPLE',
+    'TEXT',
+    'THEN',
+    'TIES',
+    'TIME',
+    'TIMESTAMP',
+    'TO',
+    'TRAILING',
+    'TRANSACTION',
+    'TRUE',
+    'TYPE',
+    'UESCAPE',
+    'UNBOUNDED',
+    'UNCOMMITTED',
+    'UNCONDITIONAL',
+    'UNION',
+    'UNIQUE',
+    'UNKNOWN',
+    'UNMATCHED',
+    'UNNEST',
+    'UPDATE',
+    'USE',
+    'USER',
+    'USING',
+    'UTF16',
+    'UTF32',
+    'UTF8',
+    'VALIDATE',
+    'VALUE',
+    'VALUES',
+    'VERBOSE',
+    'VIEW',
+    'WHEN',
+    'WHERE',
+    'WINDOW',
+    'WITH',
+    'WITHIN',
+    'WITHOUT',
+    'WORK',
+    'WRAPPER',
+    'WRITE',
+    'ZONE',
+    // https://github.com/trinodb/trino/blob/432d2897bdef99388c1a47188743a061c4ac1f34/core/trino-main/src/main/java/io/trino/metadata/TypeRegistry.java#L131-L168
+    // or https://trino.io/docs/current/language/types.html
+    'BIGINT',
+    'INT',
+    'INTEGER',
+    'SMALLINT',
+    'TINYINT',
+    'BOOLEAN',
+    'DATE',
+    'DECIMAL',
+    'REAL',
+    'DOUBLE',
+    'HYPERLOGLOG',
+    'QDIGEST',
+    'TDIGEST',
+    'P4HYPERLOGLOG',
+    'INTERVAL',
+    'TIMESTAMP',
+    'TIME',
+    'VARBINARY',
+    'VARCHAR',
+    'CHAR',
+    'ROW',
+    'ARRAY',
+    'MAP',
+    'JSON',
+    'JSON2016',
+    'IPADDRESS',
+    'GEOMETRY',
+    'UUID',
+    'SETDIGEST',
+    'JONIREGEXP',
+    'RE2JREGEXP',
+    'LIKEPATTERN',
+    'COLOR',
+    'CODEPOINTS',
+    'FUNCTION',
+    'JSONPATH',
+  ],
+});
diff --git a/src/sqlFormatter.ts b/src/sqlFormatter.ts
index 3d362010..2090642c 100644
--- a/src/sqlFormatter.ts
+++ b/src/sqlFormatter.ts
@@ -10,6 +10,7 @@ import RedshiftFormatter from 'src/languages/redshift/redshift.formatter';
 import SparkFormatter from 'src/languages/spark/spark.formatter';
 import SqliteFormatter from 'src/languages/sqlite/sqlite.formatter';
 import SqlFormatter from 'src/languages/sql/sql.formatter';
+import TrinoFormatter from 'src/languages/trino/trino.formatter';
 import TSqlFormatter from 'src/languages/tsql/tsql.formatter';
 
 import type { FormatOptions } from './types';
@@ -28,6 +29,7 @@ export const formatters = {
   spark: SparkFormatter,
   sql: SqlFormatter,
   sqlite: SqliteFormatter,
+  trino: TrinoFormatter,
   tsql: TSqlFormatter,
 };
 export type SqlLanguage = keyof typeof formatters;
diff --git a/static/index.html b/static/index.html
index 288ae85f..a642b829 100644
--- a/static/index.html
+++ b/static/index.html
@@ -69,6 +69,7 @@
               <option value="spark">Spark</option>
               <option value="sqlite">SQLite</option>
               <option value="tsql">Transact-SQL</option>
+              <option value="trino">Trino</option>
             </select>
           </article>
           <article class="config">


Test patch:
diff --git a/test/behavesLikeSqlFormatter.ts b/test/behavesLikeSqlFormatter.ts
index c758bf49..905a4094 100644
--- a/test/behavesLikeSqlFormatter.ts
+++ b/test/behavesLikeSqlFormatter.ts
@@ -286,13 +286,13 @@ export default function behavesLikeSqlFormatter(format: FormatFn) {
   });
 
   it('formats top-level and newline multi-word reserved words with inconsistent spacing', () => {
-    const result = format('SELECT * FROM foo LEFT \t   \n JOIN bar ORDER \n BY blah');
+    const result = format('SELECT * FROM foo LEFT \t   \n JOIN mycol ORDER \n BY blah');
     expect(result).toBe(dedent`
       SELECT
         *
       FROM
         foo
-        LEFT JOIN bar
+        LEFT JOIN mycol
       ORDER BY
         blah
     `);
diff --git a/test/features/join.ts b/test/features/join.ts
index 9e22fad7..8cd77fda 100644
--- a/test/features/join.ts
+++ b/test/features/join.ts
@@ -59,7 +59,7 @@ export default function supportsJoin(format: FormatFn, { without, additionally }
 
   it('properly uppercases JOIN, ON and USING', () => {
     const result = format(
-      `select * from customers join foo on foo.id = customers.id join bar using (id);`,
+      `select * from customers join foo on foo.id = customers.id join foobar using (id);`,
       { keywordCase: 'upper' }
     );
     expect(result).toBe(dedent`
@@ -68,7 +68,7 @@ export default function supportsJoin(format: FormatFn, { without, additionally }
       FROM
         customers
         JOIN foo ON foo.id = customers.id
-        JOIN bar USING (id);
+        JOIN foobar USING (id);
     `);
   });
 }
diff --git a/test/options/keywordCase.ts b/test/options/keywordCase.ts
index a0257b03..5637f295 100644
--- a/test/options/keywordCase.ts
+++ b/test/options/keywordCase.ts
@@ -18,7 +18,7 @@ export default function supportsKeywordCase(format: FormatFn) {
   });
 
   it('converts keywords to uppercase', () => {
-    const result = format('select distinct * frOM foo left JOIN bar WHERe cola > 1 and colb = 3', {
+    const result = format('select distinct * frOM foo left JOIN mycol WHERe cola > 1 and colb = 3', {
       keywordCase: 'upper',
     });
     expect(result).toBe(dedent`
@@ -26,7 +26,7 @@ export default function supportsKeywordCase(format: FormatFn) {
         DISTINCT *
       FROM
         foo
-        LEFT JOIN bar
+        LEFT JOIN mycol
       WHERE
         cola > 1
         AND colb = 3
diff --git a/test/trino.test.ts b/test/trino.test.ts
new file mode 100644
index 00000000..ec985865
--- /dev/null
+++ b/test/trino.test.ts
@@ -0,0 +1,83 @@
+import dedent from 'dedent-js';
+
+import { format as originalFormat, FormatFn } from 'src/sqlFormatter';
+import TrinoFormatter from 'src/languages/trino/trino.formatter';
+import behavesLikeSqlFormatter from './behavesLikeSqlFormatter';
+
+import supportsArrayLiterals from './features/arrayLiterals';
+import supportsBetween from './features/between';
+import supportsCreateTable from './features/createTable';
+import supportsDeleteFrom from './features/deleteFrom';
+import supportsJoin from './features/join';
+import supportsOperators from './features/operators';
+import supportsStrings from './features/strings';
+import supportsArrayAndMapAccessors from './features/arrayAndMapAccessors';
+import supportsComments from './features/comments';
+import supportsIdentifiers from './features/identifiers';
+import supportsParams from './options/param';
+
+describe('TrinoFormatter', () => {
+  const language = 'trino';
+  const format: FormatFn = (query, cfg = {}) => originalFormat(query, { ...cfg, language });
+
+  behavesLikeSqlFormatter(format);
+  supportsComments(format);
+  supportsCreateTable(format);
+  supportsDeleteFrom(format);
+  supportsStrings(format, ["''", "X''"]);
+  supportsIdentifiers(format, ['""', '``']);
+  supportsBetween(format);
+  supportsOperators(format, TrinoFormatter.operators, ['AND', 'OR']);
+  supportsArrayLiterals(format);
+  supportsArrayAndMapAccessors(format);
+  supportsJoin(format, {
+    additionally: [
+      'NATURAL INNER JOIN',
+      'NATURAL LEFT JOIN',
+      'NATURAL LEFT OUTER JOIN',
+      'NATURAL RIGHT JOIN',
+      'NATURAL RIGHT OUTER JOIN',
+      'NATURAL FULL JOIN',
+      'NATURAL FULL OUTER JOIN',
+    ],
+  });
+  supportsParams(format, { positional: true });
+
+  it('formats SET SESSION', () => {
+    const result = format('SET SESSION foo = 444;');
+    expect(result).toBe(dedent`
+      SET SESSION
+        foo = 444;
+    `);
+  });
+
+  it('formats basic ALTER TABLE statements', () => {
+    const result = format(`
+      ALTER TABLE people RENAME TO persons;
+      ALTER TABLE persons ADD COLUMN location_id INT;
+      ALTER TABLE persons RENAME COLUMN location_id TO loc_id;
+      ALTER TABLE persons DROP COLUMN loc_id;
+    `);
+    expect(result).toBe(dedent`
+      ALTER TABLE
+        people
+      RENAME TO
+        persons;
+
+      ALTER TABLE
+        persons
+      ADD COLUMN
+        location_id INT;
+
+      ALTER TABLE
+        persons
+      RENAME COLUMN
+        location_id TO loc_id;
+
+      ALTER TABLE
+        persons
+      DROP COLUMN
+        loc_id;
+    `);
+  });
+});