From d77d6dd94f3bdc67c687a8c2e51c2796524b09bd Mon Sep 17 00:00:00 2001 From: Christopher Pruijsen Date: Tue, 29 Sep 2026 04:24:33 +0100 Subject: [PATCH] Treat ClickHouse range() as a function when followed by a parenthesis keywordCase upper was rewriting range() to RANGE (), which ClickHouse rejects. Remap RANGE + OPEN_PAREN the same way SET(100) is remapped, and leave RANGE BETWEEN as a window-frame keyword phrase. --- .../clickhouse/clickhouse.formatter.ts | 12 ++++++++ test/clickhouse.test.ts | 28 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/src/languages/clickhouse/clickhouse.formatter.ts b/src/languages/clickhouse/clickhouse.formatter.ts index 71aa28b32d..ced67f298d 100644 --- a/src/languages/clickhouse/clickhouse.formatter.ts +++ b/src/languages/clickhouse/clickhouse.formatter.ts @@ -300,6 +300,9 @@ export const clickhouse: DialectOptions = { * for SELECT GRANTs * 2. Formats SET(100) as RESERVED_FUNCTION_NAME instead of RESERVED_KEYWORD * so it appears as a function rather than a statement. + * 3. Formats RANGE(...) as RESERVED_FUNCTION_NAME instead of RESERVED_KEYWORD + * so keywordCase does not rewrite the case-sensitive range() function. + * RANGE BETWEEN stays a reserved keyword phrase for window frames. */ function postProcess(tokens: Token[]): Token[] { return tokens.map((token, i) => { @@ -333,6 +336,15 @@ function postProcess(tokens: Token[]): Token[] { return { ...token, type: TokenType.RESERVED_FUNCTION_NAME }; } + // We should format `range(32)` as-is rather than `RANGE (32)` + if ( + token.type === TokenType.RESERVED_KEYWORD && + token.text === 'RANGE' && + nextToken.type === TokenType.OPEN_PAREN + ) { + return { ...token, type: TokenType.RESERVED_FUNCTION_NAME }; + } + return token; }); } diff --git a/test/clickhouse.test.ts b/test/clickhouse.test.ts index 225b0480f0..b2c668d46c 100644 --- a/test/clickhouse.test.ts +++ b/test/clickhouse.test.ts @@ -449,6 +449,34 @@ describe('ClickhouseFormatter', () => { value ASC; `); }); + + it('formats SELECT with window function and RANGE BETWEEN', () => { + expect( + format( + 'SELECT part_key, value, groupArray(value) OVER (ORDER BY seq RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) FROM wf_frame;', + { keywordCase: 'upper' } + ) + ).toBe(dedent` + SELECT + part_key, + value, + groupArray(value) OVER ( + ORDER BY + seq RANGE BETWEEN UNBOUNDED PRECEDING + AND CURRENT ROW + ) + FROM + wf_frame; + `); + }); + }); + + // Regression test for issue #980 + it('does not uppercase the case-sensitive range() function', () => { + expect(format('SELECT range(32);', { keywordCase: 'upper' })).toBe(dedent` + SELECT + range(32); + `); }); // https://clickhouse.com/docs/sql-reference/statements/create