Skip to content

Commit 9ab44fb

Browse files
authored
Expose parseColumnType function (#316)
1 parent d930aa5 commit 9ab44fb

21 files changed

+2110
-11
lines changed

.github/workflows/scorecard.yml

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,19 @@ on:
1212
schedule:
1313
- cron: '43 12 * * 6'
1414
push:
15-
branches: [ "main" ]
15+
branches:
16+
- main
17+
paths-ignore:
18+
- '**/*.md'
19+
- 'LICENSE'
20+
- 'benchmarks/**'
21+
- 'examples/**'
22+
pull_request:
23+
paths-ignore:
24+
- '**/*.md'
25+
- 'LICENSE'
26+
- 'benchmarks/**'
27+
- 'examples/**'
1628
workflow_dispatch:
1729

1830
# Declare default permissions as read only.
@@ -32,12 +44,12 @@ jobs:
3244
# actions: read
3345

3446
steps:
35-
- name: "Checkout code"
47+
- name: 'Checkout code'
3648
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
3749
with:
3850
persist-credentials: false
3951

40-
- name: "Run analysis"
52+
- name: 'Run analysis'
4153
uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1
4254
with:
4355
results_file: results.sarif
@@ -59,7 +71,7 @@ jobs:
5971

6072
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
6173
# format to the repository Actions tab.
62-
- name: "Upload artifact"
74+
- name: 'Upload artifact'
6375
uses: actions/upload-artifact@97a0fba1372883ab732affbe8f94b823f91727db # v3.pre.node20
6476
with:
6577
name: SARIF file
@@ -68,7 +80,7 @@ jobs:
6880

6981
# Upload the results to GitHub's code scanning dashboard (optional).
7082
# Commenting out will disable upload of results to your repo's Code Scanning dashboard
71-
- name: "Upload to code-scanning"
83+
- name: 'Upload to code-scanning'
7284
uses: github/codeql-action/upload-sarif@v3
7385
with:
7486
sarif_file: results.sarif

CHANGELOG.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,59 @@
1+
# 1.7.0 (Common, Node.js, Web)
2+
3+
- (Experimental) Exposed the `parseColumnType` function that takes a string representation of a ClickHouse type (e.g., `FixedString(16)`, `Nullable(Int32)`, etc.) and returns an AST-like object that represents the type. For example:
4+
5+
```ts
6+
for (const type of [
7+
'Int32',
8+
'Array(Nullable(String))',
9+
`Map(Int32, DateTime64(9, 'UTC'))`,
10+
]) {
11+
console.log(`##### Source ClickHouse type: ${type}`)
12+
console.log(parseColumnType(type))
13+
}
14+
```
15+
16+
The above code will output:
17+
18+
```
19+
##### Source ClickHouse type: Int32
20+
{ type: 'Simple', columnType: 'Int32', sourceType: 'Int32' }
21+
##### Source ClickHouse type: Array(Nullable(String))
22+
{
23+
type: 'Array',
24+
value: {
25+
type: 'Nullable',
26+
sourceType: 'Nullable(String)',
27+
value: { type: 'Simple', columnType: 'String', sourceType: 'String' }
28+
},
29+
dimensions: 1,
30+
sourceType: 'Array(Nullable(String))'
31+
}
32+
##### Source ClickHouse type: Map(Int32, DateTime64(9, 'UTC'))
33+
{
34+
type: 'Map',
35+
key: { type: 'Simple', columnType: 'Int32', sourceType: 'Int32' },
36+
value: {
37+
type: 'DateTime64',
38+
timezone: 'UTC',
39+
precision: 9,
40+
sourceType: "DateTime64(9, 'UTC')"
41+
},
42+
sourceType: "Map(Int32, DateTime64(9, 'UTC'))"
43+
}
44+
```
45+
46+
While the original intention was to use this function internally for `Native`/`RowBinaryWithNamesAndTypes` data formats headers parsing, it can be useful for other purposes as well (e.g., interfaces generation, or custom JSON serializers).
47+
48+
NB: currently unsupported source types to parse:
49+
50+
- Geo
51+
- (Simple)AggregateFunction
52+
- Nested
53+
- Old/new experimental JSON
54+
- Dynamic
55+
- Variant
56+
157
# 1.6.0 (Common, Node.js, Web)
258

359
## New features

packages/client-common/__tests__/integration/abort_request.test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ describe('abort request', () => {
77
beforeEach(() => {
88
client = createTestClient()
99
})
10-
1110
afterEach(async () => {
1211
await client.close()
1312
})

packages/client-common/__tests__/integration/error_parsing.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@ describe('ClickHouse server errors parsing', () => {
1414
// Possible error messages here:
1515
// (since 24.3+, Cloud SMT): Unknown expression identifier 'number' in scope SELECT number AS FR
1616
// (since 23.8+, Cloud RMT): Missing columns: 'number' while processing query: 'SELECT number AS FR', required columns: 'number'
17+
// (since 24.9+): Unknown expression identifier `number` in scope SELECT number AS FR
1718
const errorMessagePattern =
1819
`((?:Missing columns: 'number' while processing query: 'SELECT number AS FR', required columns: 'number')|` +
19-
`(?:Unknown expression identifier 'number' in scope SELECT number AS FR))`
20+
`(?:Unknown expression identifier ('|\`)number('|\`) in scope SELECT number AS FR))`
2021
await expectAsync(
2122
client.query({
2223
query: 'SELECT number FR',
@@ -37,7 +38,7 @@ describe('ClickHouse server errors parsing', () => {
3738
const dbName = getTestDatabaseName()
3839
const errorMessagePattern =
3940
`((?:^Table ${dbName}.unknown_table does not exist.*)|` +
40-
`(?:Unknown table expression identifier 'unknown_table' in scope))`
41+
`(?:Unknown table expression identifier ('|\`)unknown_table('|\`) in scope))`
4142
await expectAsync(
4243
client.query({
4344
query: 'SELECT * FROM unknown_table',
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { parseFixedStringType } from '../../src/parse'
2+
3+
describe('Columns types parser', () => {
4+
describe('FixedString', () => {
5+
it('should parse FixedString', async () => {
6+
const args: [string, number][] = [
7+
['FixedString(1)', 1],
8+
['FixedString(42)', 42],
9+
['FixedString(100)', 100],
10+
['FixedString(32768)', 32768],
11+
]
12+
args.forEach(([columnType, sizeBytes]) => {
13+
const result = parseFixedStringType({
14+
columnType,
15+
sourceType: columnType,
16+
})
17+
expect(result)
18+
.withContext(
19+
`Expected ${columnType} to be parsed as a FixedString with size ${sizeBytes}`,
20+
)
21+
.toEqual({ type: 'FixedString', sizeBytes, sourceType: columnType })
22+
})
23+
})
24+
25+
it('should throw on invalid FixedString type', async () => {
26+
const args: [string][] = [
27+
['FixedString'],
28+
['FixedString('],
29+
['FixedString()'],
30+
['String'],
31+
]
32+
args.forEach(([columnType]) => {
33+
expect(() =>
34+
parseFixedStringType({ columnType, sourceType: columnType }),
35+
)
36+
.withContext(`Expected ${columnType} to throw`)
37+
.toThrowError('Invalid FixedString type')
38+
})
39+
})
40+
41+
it('should throw on invalid FixedString size', async () => {
42+
const args: [string][] = [
43+
['FixedString(0)'],
44+
['FixedString(x)'],
45+
[`FixedString(')`],
46+
]
47+
args.forEach(([columnType]) => {
48+
expect(() =>
49+
parseFixedStringType({ columnType, sourceType: columnType }),
50+
)
51+
.withContext(`Expected ${columnType} to throw`)
52+
.toThrowError('Invalid FixedString size in bytes')
53+
})
54+
})
55+
})
56+
})

0 commit comments

Comments
 (0)