Repository: sql-formatter-org/sql-formatter (ts)

Issue:
[FORMATTING] newline after FROM and NULL in IS DISTINCT FROM NULL
**Input data**

Which SQL and options did you provide as input?

```sql
SELECT table1.id FROM table1
LEFT OUTER JOIN table2 ON table2.table1_id = table1.id
WHERE NOT (COALESCE(interview_stage.* IS DISTINCT FROM NULL, FALSE))
```

**Expected Output**

// something like this that shows the `IS DISTINCT FROM NULL` part on a single line
```sql
SELECT
      table1.id
    FROM
      table1
      LEFT OUTER JOIN table2 ON table2.table1_id = table1.id
    WHERE
      NOT (
        COALESCE(
            table2.* IS DISTINCT FROM NULL,
            FALSE
        )
      )
```

**Actual Output**

```sql
SELECT
      table1.id
    FROM
      table1
      LEFT OUTER JOIN table2 ON table2.table1_id = table1.id
    WHERE
      NOT (
        COALESCE(
          table2.* IS DISTINCT
          FROM
            NULL,
            FALSE
        )
      )
```

**Usage**

- How are you calling / using the library? Using it in a test to show a nicely formatted expected output
- What SQL language(s) does this apply to? Postgresql
- Which SQL Formatter version are you using? 12.1.0


Pull request description:
Format IS [NOT] DISTINCT FROM operator
Fixes #564

Patch:
diff --git a/src/languages/bigquery/bigquery.formatter.ts b/src/languages/bigquery/bigquery.formatter.ts
index ab0c8ac2..bd69ae7f 100644
--- a/src/languages/bigquery/bigquery.formatter.ts
+++ b/src/languages/bigquery/bigquery.formatter.ts
@@ -150,6 +150,8 @@ const reservedPhrases = expandPhrases([
   'NOT DETERMINISTIC',
   // inside window definitions
   '{ROWS | RANGE} BETWEEN',
+  // comparison operator
+  'IS [NOT] DISTINCT FROM',
 ]);
 
 // https://cloud.google.com/bigquery/docs/reference/#standard-sql-reference
diff --git a/src/languages/postgresql/postgresql.formatter.ts b/src/languages/postgresql/postgresql.formatter.ts
index 55ac8ae1..5cce19a9 100644
--- a/src/languages/postgresql/postgresql.formatter.ts
+++ b/src/languages/postgresql/postgresql.formatter.ts
@@ -247,6 +247,8 @@ const reservedPhrases = expandPhrases([
   '{ROWS | RANGE | GROUPS} BETWEEN',
   // https://www.postgresql.org/docs/current/datatype-datetime.html
   '{TIMESTAMP | TIME} {WITH | WITHOUT} TIME ZONE',
+  // comparison operator
+  'IS [NOT] DISTINCT FROM',
 ]);
 
 // https://www.postgresql.org/docs/14/index.html
diff --git a/src/languages/trino/trino.formatter.ts b/src/languages/trino/trino.formatter.ts
index 6eca1b67..1a76dc82 100644
--- a/src/languages/trino/trino.formatter.ts
+++ b/src/languages/trino/trino.formatter.ts
@@ -120,7 +120,11 @@ const reservedJoins = expandPhrases([
   'NATURAL {LEFT | RIGHT | FULL} [OUTER] JOIN',
 ]);
 
-const reservedPhrases = expandPhrases(['{ROWS | RANGE | GROUPS} BETWEEN']);
+const reservedPhrases = expandPhrases([
+  '{ROWS | RANGE | GROUPS} BETWEEN',
+  // comparison operator
+  'IS [NOT] DISTINCT FROM',
+]);
 
 export const trino: DialectOptions = {
   tokenizerOptions: {


Test patch:
diff --git a/test/bigquery.test.ts b/test/bigquery.test.ts
index 1976d68d..857181df 100644
--- a/test/bigquery.test.ts
+++ b/test/bigquery.test.ts
@@ -23,6 +23,7 @@ import supportsTruncateTable from './features/truncateTable.js';
 import supportsMergeInto from './features/mergeInto.js';
 import supportsCreateView from './features/createView.js';
 import supportsAlterTable from './features/alterTable.js';
+import supportsIsDistinctFrom from './features/isDistinctFrom.js';
 
 describe('BigQueryFormatter', () => {
   const language = 'bigquery';
@@ -55,6 +56,7 @@ describe('BigQueryFormatter', () => {
     'INTERSECT DISTINCT',
   ]);
   supportsOperators(format, ['&', '|', '^', '~', '>>', '<<', '||']);
+  supportsIsDistinctFrom(format);
   supportsParams(format, { positional: true, named: ['@'], quoted: ['@``'] });
   supportsWindow(format);
   supportsLimiting(format, { limit: true, offset: true });
diff --git a/test/features/isDistinctFrom.ts b/test/features/isDistinctFrom.ts
new file mode 100644
index 00000000..c5a7bf9c
--- /dev/null
+++ b/test/features/isDistinctFrom.ts
@@ -0,0 +1,13 @@
+import dedent from 'dedent-js';
+import { FormatFn } from '../../src/sqlFormatter.js';
+
+export default function supportsIsDistinctFrom(format: FormatFn) {
+  // Regression test for #564
+  it('formats IS [NOT] DISTINCT FROM operator', () => {
+    expect(format('SELECT x IS DISTINCT FROM y, x IS NOT DISTINCT FROM y')).toBe(dedent`
+      SELECT
+        x IS DISTINCT FROM y,
+        x IS NOT DISTINCT FROM y
+    `);
+  });
+}
diff --git a/test/postgresql.test.ts b/test/postgresql.test.ts
index d6c998fa..5ebe5c14 100644
--- a/test/postgresql.test.ts
+++ b/test/postgresql.test.ts
@@ -26,6 +26,7 @@ import supportsUpdate from './features/update.js';
 import supportsTruncateTable from './features/truncateTable.js';
 import supportsCreateView from './features/createView.js';
 import supportsOnConflict from './features/onConflict.js';
+import supportsIsDistinctFrom from './features/isDistinctFrom.js';
 
 describe('PostgreSqlFormatter', () => {
   const language = 'postgresql';
@@ -138,6 +139,7 @@ describe('PostgreSqlFormatter', () => {
     '<<<->',
     '<->>>',
   ]);
+  supportsIsDistinctFrom(format);
   supportsJoin(format);
   supportsSetOperations(format);
   supportsReturning(format);
diff --git a/test/trino.test.ts b/test/trino.test.ts
index 8b316ad6..230fb1ea 100644
--- a/test/trino.test.ts
+++ b/test/trino.test.ts
@@ -23,6 +23,7 @@ import supportsUpdate from './features/update.js';
 import supportsTruncateTable from './features/truncateTable.js';
 import supportsCreateView from './features/createView.js';
 import supportsAlterTable from './features/alterTable.js';
+import supportsIsDistinctFrom from './features/isDistinctFrom.js';
 
 describe('TrinoFormatter', () => {
   const language = 'trino';
@@ -48,6 +49,7 @@ describe('TrinoFormatter', () => {
   supportsBetween(format);
   // Missing: '?' operator (for row patterns)
   supportsOperators(format, ['%', '->', '=>', '||', '|', '^', '$'], ['AND', 'OR']);
+  supportsIsDistinctFrom(format);
   supportsArrayLiterals(format);
   supportsArrayAndMapAccessors(format);
   supportsJoin(format);