Repository: AfterShip/clickhouse-sql-parser
Pull request #74 (2024-07-15 09:53:48)

Extend the index parameters to support the float type
extend the index parameters to support the float type and add parsing index tests for `ALTER TABLE ... ADD INDEX` and `CREATE TABLE ... INDEX`

index types ref: https://clickhouse.com/docs/en/optimize/skipping-indexes#skip-index-types

- minmax
- set
- bloom_filter
- tokenbf_v1
- ngrambf_v1

coveralls: ## Pull Request Test Coverage Report for [Build 9937319416](https://coveralls.io/builds/68641016)


### Details

* **7** of **7**   **(100.0%)**  changed or added relevant lines in **3** files are covered.
* No unchanged relevant lines lost coverage.
* Overall coverage increased (+**0.07%**) to **58.064%**

---



|  Totals | [![Coverage Status](https://coveralls.io/builds/68641016/badge)](https://coveralls.io/builds/68641016) |
| :-- | --: |
| Change from base [Build 9866858946](https://coveralls.io/builds/68554969): |  0.07% |
| Covered Lines: | 5897 |
| Relevant Lines: | 10156 |

---
##### 💛  - [Coveralls](https://coveralls.io)


Patch:
diff --git a/parser/ast.go b/parser/ast.go
index fbec71c..4f14432 100644
--- a/parser/ast.go
+++ b/parser/ast.go
@@ -1274,6 +1274,11 @@ func (a *TableIndex) String(level int) string {
 	builder.WriteString("INDEX")
 	builder.WriteByte(' ')
 	builder.WriteString(a.Name.String(0))
+	// a.ColumnExpr = *Ident --- e.g. INDEX idx column TYPE ...
+	// a.ColumnExpr = *ParamExprList --- e.g. INDEX idx(column) TYPE ...
+	if _, ok := a.ColumnExpr.(*Ident); ok {
+		builder.WriteByte(' ')
+	}
 	builder.WriteString(a.ColumnExpr.String(level))
 	builder.WriteByte(' ')
 	builder.WriteString("TYPE")
diff --git a/parser/parser_column.go b/parser/parser_column.go
index d1b801d..f0ec19f 100644
--- a/parser/parser_column.go
+++ b/parser/parser_column.go
@@ -672,7 +672,7 @@ func (p *Parser) parseColumnType(_ Pos) (Expr, error) { // nolint:funlen
 			}
 			// like Datetime('Asia/Dubai')
 			return p.parseColumnTypeWithParams(ident, p.Pos())
-		case p.matchTokenKind(TokenInt):
+		case p.matchTokenKind(TokenInt), p.matchTokenKind(TokenFloat):
 			// fixed size
 			return p.parseColumnTypeWithParams(ident, p.Pos())
 		default:
diff --git a/parser/parser_common.go b/parser/parser_common.go
index 6fbaf41..cf486d7 100644
--- a/parser/parser_common.go
+++ b/parser/parser_common.go
@@ -234,7 +234,7 @@ func (p *Parser) parseString(pos Pos) (*StringLiteral, error) {
 
 func (p *Parser) parseLiteral(pos Pos) (Literal, error) {
 	switch {
-	case p.matchTokenKind(TokenInt):
+	case p.matchTokenKind(TokenInt), p.matchTokenKind(TokenFloat):
 		return p.parseNumber(pos)
 	case p.matchTokenKind(TokenString):
 		return p.parseString(pos)


Test patch:
diff --git a/parser/parser_test.go b/parser/parser_test.go
index f59e67f..af5da10 100644
--- a/parser/parser_test.go
+++ b/parser/parser_test.go
@@ -66,7 +66,6 @@ func TestParser_ParseStatements(t *testing.T) {
 					goldie.WithDiffEngine(goldie.ColoredDiff),
 					goldie.WithFixtureDir(outputDir))
 				g.Assert(t, entry.Name(), outputBytes)
-
 			})
 		}
 	}
diff --git a/parser/testdata/ddl/alter_table_add_index.sql b/parser/testdata/ddl/alter_table_add_index.sql
index 81a62a6..65e0ca3 100644
--- a/parser/testdata/ddl/alter_table_add_index.sql
+++ b/parser/testdata/ddl/alter_table_add_index.sql
@@ -1,1 +1,5 @@
 ALTER TABLE test.events_local ON CLUSTER 'default_cluster' ADD INDEX my_index(f0) TYPE minmax GRANULARITY 1024;
+ALTER TABLE test.events_local ON CLUSTER 'default_cluster' ADD INDEX api_id_idx api_id TYPE set(100) GRANULARITY 2;
+ALTER TABLE test.events_local ON CLUSTER 'default_cluster' ADD INDEX arr_idx arr TYPE bloom_filter(0.01) GRANULARITY 3;
+ALTER TABLE test.events_local ON CLUSTER 'default_cluster' ADD INDEX content_idx content TYPE tokenbf_v1(30720, 2, 0) GRANULARITY 1;
+ALTER TABLE test.events_local ON CLUSTER 'default_cluster' ADD INDEX output_idx output TYPE ngrambf_v1(3, 10000, 2, 1) GRANULARITY 2;
diff --git a/parser/testdata/ddl/create_table_with_index.sql b/parser/testdata/ddl/create_table_with_index.sql
new file mode 100644
index 0000000..fa1d2da
--- /dev/null
+++ b/parser/testdata/ddl/create_table_with_index.sql
@@ -0,0 +1,18 @@
+CREATE TABLE IF NOT EXISTS test_local
+(
+ `id` UInt64 CODEC(Delta, ZSTD(1)),
+ `api_id` UInt64 CODEC(ZSTD(1)),
+ `arr` Array(Int64),
+ `content` String CODEC(ZSTD(1)),
+ `output` String,
+ INDEX id_idx id TYPE minmax GRANULARITY 10,
+ INDEX api_id_idx api_id TYPE set(100) GRANULARITY 2,
+ INDEX arr_idx arr TYPE bloom_filter(0.01) GRANULARITY 3,
+ INDEX content_idx content TYPE tokenbf_v1(30720, 2, 0) GRANULARITY 1,
+ INDEX output_idx output TYPE ngrambf_v1(3, 10000, 2, 1) GRANULARITY 2
+)
+ENGINE = ReplicatedMergeTree('/root/test_local', '{replica}')
+PARTITION BY toStartOfHour(`timestamp`)
+ORDER BY (toUnixTimestamp64Nano(`timestamp`), `api_id`)
+TTL toStartOfHour(`timestamp`) + INTERVAL 7 DAY,toStartOfHour(`timestamp`) + INTERVAL 2 DAY
+SETTINGS execute_merges_on_single_replica_time_threshold=1200, index_granularity=16384, max_bytes_to_merge_at_max_space_in_pool=64424509440, storage_policy='main', ttl_only_drop_parts=1;
diff --git a/parser/testdata/ddl/format/alter_table_add_index.sql b/parser/testdata/ddl/format/alter_table_add_index.sql
index 7648deb..d4ef67e 100644
--- a/parser/testdata/ddl/format/alter_table_add_index.sql
+++ b/parser/testdata/ddl/format/alter_table_add_index.sql
@@ -1,8 +1,24 @@
 -- Origin SQL:
 ALTER TABLE test.events_local ON CLUSTER 'default_cluster' ADD INDEX my_index(f0) TYPE minmax GRANULARITY 1024;
+ALTER TABLE test.events_local ON CLUSTER 'default_cluster' ADD INDEX api_id_idx api_id TYPE set(100) GRANULARITY 2;
+ALTER TABLE test.events_local ON CLUSTER 'default_cluster' ADD INDEX arr_idx arr TYPE bloom_filter(0.01) GRANULARITY 3;
+ALTER TABLE test.events_local ON CLUSTER 'default_cluster' ADD INDEX content_idx content TYPE tokenbf_v1(30720, 2, 0) GRANULARITY 1;
+ALTER TABLE test.events_local ON CLUSTER 'default_cluster' ADD INDEX output_idx output TYPE ngrambf_v1(3, 10000, 2, 1) GRANULARITY 2;
 
 
 -- Format SQL:
 ALTER TABLE test.events_local
 ON CLUSTER 'default_cluster'
 ADD INDEX my_index(f0) TYPE minmax GRANULARITY 1024;
+ALTER TABLE test.events_local
+ON CLUSTER 'default_cluster'
+ADD INDEX api_id_idx api_id TYPE set(100) GRANULARITY 2;
+ALTER TABLE test.events_local
+ON CLUSTER 'default_cluster'
+ADD INDEX arr_idx arr TYPE bloom_filter(0.01) GRANULARITY 3;
+ALTER TABLE test.events_local
+ON CLUSTER 'default_cluster'
+ADD INDEX content_idx content TYPE tokenbf_v1(30720,2,0) GRANULARITY 1;
+ALTER TABLE test.events_local
+ON CLUSTER 'default_cluster'
+ADD INDEX output_idx output TYPE ngrambf_v1(3,10000,2,1) GRANULARITY 2;
diff --git a/parser/testdata/ddl/format/create_table_with_index.sql b/parser/testdata/ddl/format/create_table_with_index.sql
new file mode 100644
index 0000000..5cf2f23
--- /dev/null
+++ b/parser/testdata/ddl/format/create_table_with_index.sql
@@ -0,0 +1,40 @@
+-- Origin SQL:
+CREATE TABLE IF NOT EXISTS test_local
+(
+ `id` UInt64 CODEC(Delta, ZSTD(1)),
+ `api_id` UInt64 CODEC(ZSTD(1)),
+ `arr` Array(Int64),
+ `content` String CODEC(ZSTD(1)),
+ `output` String,
+ INDEX id_idx id TYPE minmax GRANULARITY 10,
+ INDEX api_id_idx api_id TYPE set(100) GRANULARITY 2,
+ INDEX arr_idx arr TYPE bloom_filter(0.01) GRANULARITY 3,
+ INDEX content_idx content TYPE tokenbf_v1(30720, 2, 0) GRANULARITY 1,
+ INDEX output_idx output TYPE ngrambf_v1(3, 10000, 2, 1) GRANULARITY 2
+)
+ENGINE = ReplicatedMergeTree('/root/test_local', '{replica}')
+PARTITION BY toStartOfHour(`timestamp`)
+ORDER BY (toUnixTimestamp64Nano(`timestamp`), `api_id`)
+TTL toStartOfHour(`timestamp`) + INTERVAL 7 DAY,toStartOfHour(`timestamp`) + INTERVAL 2 DAY
+SETTINGS execute_merges_on_single_replica_time_threshold=1200, index_granularity=16384, max_bytes_to_merge_at_max_space_in_pool=64424509440, storage_policy='main', ttl_only_drop_parts=1;
+
+
+-- Format SQL:
+CREATE TABLE IF NOT EXISTS test_local
+(
+  `id` UInt64 CODEC(Delta, ZSTD(1)),
+  `api_id` UInt64 CODEC(ZSTD(1)),
+  `arr` Array(Int64),
+  `content` String CODEC(ZSTD(1)),
+  `output` String,
+  INDEX id_idx id TYPE minmax GRANULARITY 10,
+  INDEX api_id_idx api_id TYPE set(100) GRANULARITY 2,
+  INDEX arr_idx arr TYPE bloom_filter(0.01) GRANULARITY 3,
+  INDEX content_idx content TYPE tokenbf_v1(30720,2,0) GRANULARITY 1,
+  INDEX output_idx output TYPE ngrambf_v1(3,10000,2,1) GRANULARITY 2
+)
+ENGINE = ReplicatedMergeTree('/root/test_local', '{replica}')
+PARTITION BY toStartOfHour(`timestamp`)
+TTL toStartOfHour(`timestamp`) + INTERVAL 7 DAY,toStartOfHour(`timestamp`) + INTERVAL 2 DAY
+SETTINGS execute_merges_on_single_replica_time_threshold=1200, index_granularity=16384, max_bytes_to_merge_at_max_space_in_pool=64424509440, storage_policy='main', ttl_only_drop_parts=1
+ORDER BY (toUnixTimestamp64Nano(`timestamp`), `api_id`);
diff --git a/parser/testdata/ddl/output/alter_table_add_index.sql.golden.json b/parser/testdata/ddl/output/alter_table_add_index.sql.golden.json
index 8bc25ef..d4b7c55 100644
--- a/parser/testdata/ddl/output/alter_table_add_index.sql.golden.json
+++ b/parser/testdata/ddl/output/alter_table_add_index.sql.golden.json
@@ -76,5 +76,339 @@
         "After": null
       }
     ]
+  },
+  {
+    "AlterPos": 112,
+    "StatementEnd": 226,
+    "TableIdentifier": {
+      "Database": {
+        "Name": "test",
+        "QuoteType": 1,
+        "NamePos": 124,
+        "NameEnd": 128
+      },
+      "Table": {
+        "Name": "events_local",
+        "QuoteType": 1,
+        "NamePos": 129,
+        "NameEnd": 141
+      }
+    },
+    "OnCluster": {
+      "OnPos": 142,
+      "Expr": {
+        "LiteralPos": 154,
+        "LiteralEnd": 169,
+        "Literal": "default_cluster"
+      }
+    },
+    "AlterExprs": [
+      {
+        "AddPos": 171,
+        "StatementEnd": 226,
+        "Index": {
+          "IndexPos": 175,
+          "Name": {
+            "Ident": {
+              "Name": "api_id_idx",
+              "QuoteType": 1,
+              "NamePos": 181,
+              "NameEnd": 191
+            },
+            "DotIdent": null
+          },
+          "ColumnExpr": {
+            "Name": "api_id",
+            "QuoteType": 1,
+            "NamePos": 192,
+            "NameEnd": 198
+          },
+          "ColumnType": {
+            "LeftParenPos": 208,
+            "RightParenPos": 211,
+            "Name": {
+              "Name": "set",
+              "QuoteType": 1,
+              "NamePos": 204,
+              "NameEnd": 207
+            },
+            "Params": [
+              {
+                "NumPos": 208,
+                "NumEnd": 211,
+                "Literal": "100",
+                "Base": 10
+              }
+            ]
+          },
+          "Granularity": {
+            "NumPos": 225,
+            "NumEnd": 226,
+            "Literal": "2",
+            "Base": 10
+          }
+        },
+        "IfNotExists": false,
+        "After": null
+      }
+    ]
+  },
+  {
+    "AlterPos": 228,
+    "StatementEnd": 346,
+    "TableIdentifier": {
+      "Database": {
+        "Name": "test",
+        "QuoteType": 1,
+        "NamePos": 240,
+        "NameEnd": 244
+      },
+      "Table": {
+        "Name": "events_local",
+        "QuoteType": 1,
+        "NamePos": 245,
+        "NameEnd": 257
+      }
+    },
+    "OnCluster": {
+      "OnPos": 258,
+      "Expr": {
+        "LiteralPos": 270,
+        "LiteralEnd": 285,
+        "Literal": "default_cluster"
+      }
+    },
+    "AlterExprs": [
+      {
+        "AddPos": 287,
+        "StatementEnd": 346,
+        "Index": {
+          "IndexPos": 291,
+          "Name": {
+            "Ident": {
+              "Name": "arr_idx",
+              "QuoteType": 1,
+              "NamePos": 297,
+              "NameEnd": 304
+            },
+            "DotIdent": null
+          },
+          "ColumnExpr": {
+            "Name": "arr",
+            "QuoteType": 1,
+            "NamePos": 305,
+            "NameEnd": 308
+          },
+          "ColumnType": {
+            "LeftParenPos": 327,
+            "RightParenPos": 331,
+            "Name": {
+              "Name": "bloom_filter",
+              "QuoteType": 1,
+              "NamePos": 314,
+              "NameEnd": 326
+            },
+            "Params": [
+              {
+                "NumPos": 327,
+                "NumEnd": 331,
+                "Literal": "0.01",
+                "Base": 10
+              }
+            ]
+          },
+          "Granularity": {
+            "NumPos": 345,
+            "NumEnd": 346,
+            "Literal": "3",
+            "Base": 10
+          }
+        },
+        "IfNotExists": false,
+        "After": null
+      }
+    ]
+  },
+  {
+    "AlterPos": 348,
+    "StatementEnd": 479,
+    "TableIdentifier": {
+      "Database": {
+        "Name": "test",
+        "QuoteType": 1,
+        "NamePos": 360,
+        "NameEnd": 364
+      },
+      "Table": {
+        "Name": "events_local",
+        "QuoteType": 1,
+        "NamePos": 365,
+        "NameEnd": 377
+      }
+    },
+    "OnCluster": {
+      "OnPos": 378,
+      "Expr": {
+        "LiteralPos": 390,
+        "LiteralEnd": 405,
+        "Literal": "default_cluster"
+      }
+    },
+    "AlterExprs": [
+      {
+        "AddPos": 407,
+        "StatementEnd": 479,
+        "Index": {
+          "IndexPos": 411,
+          "Name": {
+            "Ident": {
+              "Name": "content_idx",
+              "QuoteType": 1,
+              "NamePos": 417,
+              "NameEnd": 428
+            },
+            "DotIdent": null
+          },
+          "ColumnExpr": {
+            "Name": "content",
+            "QuoteType": 1,
+            "NamePos": 429,
+            "NameEnd": 436
+          },
+          "ColumnType": {
+            "LeftParenPos": 453,
+            "RightParenPos": 464,
+            "Name": {
+              "Name": "tokenbf_v1",
+              "QuoteType": 1,
+              "NamePos": 442,
+              "NameEnd": 452
+            },
+            "Params": [
+              {
+                "NumPos": 453,
+                "NumEnd": 458,
+                "Literal": "30720",
+                "Base": 10
+              },
+              {
+                "NumPos": 460,
+                "NumEnd": 461,
+                "Literal": "2",
+                "Base": 10
+              },
+              {
+                "NumPos": 463,
+                "NumEnd": 464,
+                "Literal": "0",
+                "Base": 10
+              }
+            ]
+          },
+          "Granularity": {
+            "NumPos": 478,
+            "NumEnd": 479,
+            "Literal": "1",
+            "Base": 10
+          }
+        },
+        "IfNotExists": false,
+        "After": null
+      }
+    ]
+  },
+  {
+    "AlterPos": 481,
+    "StatementEnd": 613,
+    "TableIdentifier": {
+      "Database": {
+        "Name": "test",
+        "QuoteType": 1,
+        "NamePos": 493,
+        "NameEnd": 497
+      },
+      "Table": {
+        "Name": "events_local",
+        "QuoteType": 1,
+        "NamePos": 498,
+        "NameEnd": 510
+      }
+    },
+    "OnCluster": {
+      "OnPos": 511,
+      "Expr": {
+        "LiteralPos": 523,
+        "LiteralEnd": 538,
+        "Literal": "default_cluster"
+      }
+    },
+    "AlterExprs": [
+      {
+        "AddPos": 540,
+        "StatementEnd": 613,
+        "Index": {
+          "IndexPos": 544,
+          "Name": {
+            "Ident": {
+              "Name": "output_idx",
+              "QuoteType": 1,
+              "NamePos": 550,
+              "NameEnd": 560
+            },
+            "DotIdent": null
+          },
+          "ColumnExpr": {
+            "Name": "output",
+            "QuoteType": 1,
+            "NamePos": 561,
+            "NameEnd": 567
+          },
+          "ColumnType": {
+            "LeftParenPos": 584,
+            "RightParenPos": 598,
+            "Name": {
+              "Name": "ngrambf_v1",
+              "QuoteType": 1,
+              "NamePos": 573,
+              "NameEnd": 583
+            },
+            "Params": [
+              {
+                "NumPos": 584,
+                "NumEnd": 585,
+                "Literal": "3",
+                "Base": 10
+              },
+              {
+                "NumPos": 587,
+                "NumEnd": 592,
+                "Literal": "10000",
+                "Base": 10
+              },
+              {
+                "NumPos": 594,
+                "NumEnd": 595,
+                "Literal": "2",
+                "Base": 10
+              },
+              {
+                "NumPos": 597,
+                "NumEnd": 598,
+                "Literal": "1",
+                "Base": 10
+              }
+            ]
+          },
+          "Granularity": {
+            "NumPos": 612,
+            "NumEnd": 613,
+            "Literal": "2",
+            "Base": 10
+          }
+        },
+        "IfNotExists": false,
+        "After": null
+      }
+    ]
   }
 ]
\ No newline at end of file
diff --git a/parser/testdata/ddl/output/create_table_with_index.sql.golden.json b/parser/testdata/ddl/output/create_table_with_index.sql.golden.json
new file mode 100644
index 0000000..67e0632
--- /dev/null
+++ b/parser/testdata/ddl/output/create_table_with_index.sql.golden.json
@@ -0,0 +1,766 @@
+[
+  {
+    "CreatePos": 0,
+    "StatementEnd": 918,
+    "Name": {
+      "Database": null,
+      "Table": {
+        "Name": "test_local",
+        "QuoteType": 1,
+        "NamePos": 27,
+        "NameEnd": 37
+      }
+    },
+    "IfNotExists": true,
+    "UUID": null,
+    "OnCluster": null,
+    "TableSchema": {
+      "SchemaPos": 38,
+      "SchemaEnd": 481,
+      "Columns": [
+        {
+          "NamePos": 42,
+          "ColumnEnd": 74,
+          "Name": {
+            "Ident": {
+              "Name": "id",
+              "QuoteType": 3,
+              "NamePos": 42,
+              "NameEnd": 44
+            },
+            "DotIdent": null
+          },
+          "Type": {
+            "Name": {
+              "Name": "UInt64",
+              "QuoteType": 1,
+              "NamePos": 46,
+              "NameEnd": 52
+            }
+          },
+          "NotNull": null,
+          "Nullable": null,
+          "Property": null,
+          "Codec": {
+            "CodecPos": 53,
+            "RightParenPos": 74,
+            "Type": {
+              "Name": "Delta",
+              "QuoteType": 1,
+              "NamePos": 59,
+              "NameEnd": 64
+            },
+            "Name": {
+              "Name": "ZSTD",
+              "QuoteType": 1,
+              "NamePos": 66,
+              "NameEnd": 70
+            },
+            "Level": {
+              "NumPos": 70,
+              "NumEnd": 72,
+              "Literal": "1",
+              "Base": 10
+            }
+          },
+          "TTL": null,
+          "Comment": null,
+          "CompressionCodec": null
+        },
+        {
+          "NamePos": 78,
+          "ColumnEnd": 107,
+          "Name": {
+            "Ident": {
+              "Name": "api_id",
+              "QuoteType": 3,
+              "NamePos": 78,
+              "NameEnd": 84
+            },
+            "DotIdent": null
+          },
+          "Type": {
+            "Name": {
+              "Name": "UInt64",
+              "QuoteType": 1,
+              "NamePos": 86,
+              "NameEnd": 92
+            }
+          },
+          "NotNull": null,
+          "Nullable": null,
+          "Property": null,
+          "Codec": {
+            "CodecPos": 93,
+            "RightParenPos": 107,
+            "Type": null,
+            "Name": {
+              "Name": "ZSTD",
+              "QuoteType": 1,
+              "NamePos": 99,
+              "NameEnd": 103
+            },
+            "Level": {
+              "NumPos": 103,
+              "NumEnd": 105,
+              "Literal": "1",
+              "Base": 10
+            }
+          },
+          "TTL": null,
+          "Comment": null,
+          "CompressionCodec": null
+        },
+        {
+          "NamePos": 111,
+          "ColumnEnd": 127,
+          "Name": {
+            "Ident": {
+              "Name": "arr",
+              "QuoteType": 3,
+              "NamePos": 111,
+              "NameEnd": 114
+            },
+            "DotIdent": null
+          },
+          "Type": {
+            "LeftParenPos": 122,
+            "RightParenPos": 127,
+            "Name": {
+              "Name": "Array",
+              "QuoteType": 1,
+              "NamePos": 116,
+              "NameEnd": 121
+            },
+            "Params": [
+              {
+                "Name": {
+                  "Name": "Int64",
+                  "QuoteType": 1,
+                  "NamePos": 122,
+                  "NameEnd": 127
+                }
+              }
+            ]
+          },
+          "NotNull": null,
+          "Nullable": null,
+          "Property": null,
+          "Codec": null,
+          "TTL": null,
+          "Comment": null,
+          "CompressionCodec": null
+        },
+        {
+          "NamePos": 132,
+          "ColumnEnd": 162,
+          "Name": {
+            "Ident": {
+              "Name": "content",
+              "QuoteType": 3,
+              "NamePos": 132,
+              "NameEnd": 139
+            },
+            "DotIdent": null
+          },
+          "Type": {
+            "Name": {
+              "Name": "String",
+              "QuoteType": 1,
+              "NamePos": 141,
+              "NameEnd": 147
+            }
+          },
+          "NotNull": null,
+          "Nullable": null,
+          "Property": null,
+          "Codec": {
+            "CodecPos": 148,
+            "RightParenPos": 162,
+            "Type": null,
+            "Name": {
+              "Name": "ZSTD",
+              "QuoteType": 1,
+              "NamePos": 154,
+              "NameEnd": 158
+            },
+            "Level": {
+              "NumPos": 158,
+              "NumEnd": 160,
+              "Literal": "1",
+              "Base": 10
+            }
+          },
+          "TTL": null,
+          "Comment": null,
+          "CompressionCodec": null
+        },
+        {
+          "NamePos": 166,
+          "ColumnEnd": 180,
+          "Name": {
+            "Ident": {
+              "Name": "output",
+              "QuoteType": 3,
+              "NamePos": 166,
+              "NameEnd": 172
+            },
+            "DotIdent": null
+          },
+          "Type": {
+            "Name": {
+              "Name": "String",
+              "QuoteType": 1,
+              "NamePos": 174,
+              "NameEnd": 180
+            }
+          },
+          "NotNull": null,
+          "Nullable": null,
+          "Property": null,
+          "Codec": null,
+          "TTL": null,
+          "Comment": null,
+          "CompressionCodec": null
+        },
+        {
+          "IndexPos": 183,
+          "Name": {
+            "Ident": {
+              "Name": "id_idx",
+              "QuoteType": 1,
+              "NamePos": 189,
+              "NameEnd": 195
+            },
+            "DotIdent": null
+          },
+          "ColumnExpr": {
+            "Name": "id",
+            "QuoteType": 1,
+            "NamePos": 196,
+            "NameEnd": 198
+          },
+          "ColumnType": {
+            "Name": {
+              "Name": "minmax",
+              "QuoteType": 1,
+              "NamePos": 204,
+              "NameEnd": 210
+            }
+          },
+          "Granularity": {
+            "NumPos": 223,
+            "NumEnd": 225,
+            "Literal": "10",
+            "Base": 10
+          }
+        },
+        {
+          "IndexPos": 228,
+          "Name": {
+            "Ident": {
+              "Name": "api_id_idx",
+              "QuoteType": 1,
+              "NamePos": 234,
+              "NameEnd": 244
+            },
+            "DotIdent": null
+          },
+          "ColumnExpr": {
+            "Name": "api_id",
+            "QuoteType": 1,
+            "NamePos": 245,
+            "NameEnd": 251
+          },
+          "ColumnType": {
+            "LeftParenPos": 261,
+            "RightParenPos": 264,
+            "Name": {
+              "Name": "set",
+              "QuoteType": 1,
+              "NamePos": 257,
+              "NameEnd": 260
+            },
+            "Params": [
+              {
+                "NumPos": 261,
+                "NumEnd": 264,
+                "Literal": "100",
+                "Base": 10
+              }
+            ]
+          },
+          "Granularity": {
+            "NumPos": 278,
+            "NumEnd": 279,
+            "Literal": "2",
+            "Base": 10
+          }
+        },
+        {
+          "IndexPos": 282,
+          "Name": {
+            "Ident": {
+              "Name": "arr_idx",
+              "QuoteType": 1,
+              "NamePos": 288,
+              "NameEnd": 295
+            },
+            "DotIdent": null
+          },
+          "ColumnExpr": {
+            "Name": "arr",
+            "QuoteType": 1,
+            "NamePos": 296,
+            "NameEnd": 299
+          },
+          "ColumnType": {
+            "LeftParenPos": 318,
+            "RightParenPos": 322,
+            "Name": {
+              "Name": "bloom_filter",
+              "QuoteType": 1,
+              "NamePos": 305,
+              "NameEnd": 317
+            },
+            "Params": [
+              {
+                "NumPos": 318,
+                "NumEnd": 322,
+                "Literal": "0.01",
+                "Base": 10
+              }
+            ]
+          },
+          "Granularity": {
+            "NumPos": 336,
+            "NumEnd": 337,
+            "Literal": "3",
+            "Base": 10
+          }
+        },
+        {
+          "IndexPos": 340,
+          "Name": {
+            "Ident": {
+              "Name": "content_idx",
+              "QuoteType": 1,
+              "NamePos": 346,
+              "NameEnd": 357
+            },
+            "DotIdent": null
+          },
+          "ColumnExpr": {
+            "Name": "content",
+            "QuoteType": 1,
+            "NamePos": 358,
+            "NameEnd": 365
+          },
+          "ColumnType": {
+            "LeftParenPos": 382,
+            "RightParenPos": 393,
+            "Name": {
+              "Name": "tokenbf_v1",
+              "QuoteType": 1,
+              "NamePos": 371,
+              "NameEnd": 381
+            },
+            "Params": [
+              {
+                "NumPos": 382,
+                "NumEnd": 387,
+                "Literal": "30720",
+                "Base": 10
+              },
+              {
+                "NumPos": 389,
+                "NumEnd": 390,
+                "Literal": "2",
+                "Base": 10
+              },
+              {
+                "NumPos": 392,
+                "NumEnd": 393,
+                "Literal": "0",
+                "Base": 10
+              }
+            ]
+          },
+          "Granularity": {
+            "NumPos": 407,
+            "NumEnd": 408,
+            "Literal": "1",
+            "Base": 10
+          }
+        },
+        {
+          "IndexPos": 411,
+          "Name": {
+            "Ident": {
+              "Name": "output_idx",
+              "QuoteType": 1,
+              "NamePos": 417,
+              "NameEnd": 427
+            },
+            "DotIdent": null
+          },
+          "ColumnExpr": {
+            "Name": "output",
+            "QuoteType": 1,
+            "NamePos": 428,
+            "NameEnd": 434
+          },
+          "ColumnType": {
+            "LeftParenPos": 451,
+            "RightParenPos": 465,
+            "Name": {
+              "Name": "ngrambf_v1",
+              "QuoteType": 1,
+              "NamePos": 440,
+              "NameEnd": 450
+            },
+            "Params": [
+              {
+                "NumPos": 451,
+                "NumEnd": 452,
+                "Literal": "3",
+                "Base": 10
+              },
+              {
+                "NumPos": 454,
+                "NumEnd": 459,
+                "Literal": "10000",
+                "Base": 10
+              },
+              {
+                "NumPos": 461,
+                "NumEnd": 462,
+                "Literal": "2",
+                "Base": 10
+              },
+              {
+                "NumPos": 464,
+                "NumEnd": 465,
+                "Literal": "1",
+                "Base": 10
+              }
+            ]
+          },
+          "Granularity": {
+            "NumPos": 479,
+            "NumEnd": 480,
+            "Literal": "2",
+            "Base": 10
+          }
+        }
+      ],
+      "AliasTable": null,
+      "TableFunction": null
+    },
+    "Engine": {
+      "EnginePos": 483,
+      "EngineEnd": 918,
+      "Name": "ReplicatedMergeTree",
+      "Params": {
+        "LeftParenPos": 511,
+        "RightParenPos": 543,
+        "Items": {
+          "ListPos": 513,
+          "ListEnd": 542,
+          "HasDistinct": false,
+          "Items": [
+            {
+              "LiteralPos": 513,
+              "LiteralEnd": 529,
+              "Literal": "/root/test_local"
+            },
+            {
+              "LiteralPos": 533,
+              "LiteralEnd": 542,
+              "Literal": "{replica}"
+            }
+          ]
+        },
+        "ColumnArgList": null
+      },
+      "PrimaryKey": null,
+      "PartitionBy": {
+        "PartitionPos": 545,
+        "Expr": {
+          "ListPos": 558,
+          "ListEnd": 583,
+          "HasDistinct": false,
+          "Items": [
+            {
+              "Name": {
+                "Name": "toStartOfHour",
+                "QuoteType": 1,
+                "NamePos": 558,
+                "NameEnd": 571
+              },
+              "Params": {
+                "LeftParenPos": 571,
+                "RightParenPos": 583,
+                "Items": {
+                  "ListPos": 573,
+                  "ListEnd": 582,
+                  "HasDistinct": false,
+                  "Items": [
+                    {
+                      "Name": "timestamp",
+                      "QuoteType": 3,
+                      "NamePos": 573,
+                      "NameEnd": 582
+                    }
+                  ]
+                },
+                "ColumnArgList": null
+              }
+            }
+          ]
+        }
+      },
+      "SampleBy": null,
+      "TTL": {
+        "TTLPos": 641,
+        "ListEnd": 732,
+        "Items": [
+          {
+            "TTLPos": 641,
+            "Expr": {
+              "LeftExpr": {
+                "Name": {
+                  "Name": "toStartOfHour",
+                  "QuoteType": 1,
+                  "NamePos": 645,
+                  "NameEnd": 658
+                },
+                "Params": {
+                  "LeftParenPos": 658,
+                  "RightParenPos": 670,
+                  "Items": {
+                    "ListPos": 660,
+                    "ListEnd": 669,
+                    "HasDistinct": false,
+                    "Items": [
+                      {
+                        "Name": "timestamp",
+                        "QuoteType": 3,
+                        "NamePos": 660,
+                        "NameEnd": 669
+                      }
+                    ]
+                  },
+                  "ColumnArgList": null
+                }
+              },
+              "Operation": "+",
+              "RightExpr": {
+                "IntervalPos": 674,
+                "Expr": {
+                  "NumPos": 683,
+                  "NumEnd": 684,
+                  "Literal": "7",
+                  "Base": 10
+                },
+                "Unit": {
+                  "Name": "DAY",
+                  "QuoteType": 1,
+                  "NamePos": 685,
+                  "NameEnd": 688
+                }
+              },
+              "HasGlobal": false,
+              "HasNot": false
+            }
+          },
+          {
+            "TTLPos": 641,
+            "Expr": {
+              "LeftExpr": {
+                "Name": {
+                  "Name": "toStartOfHour",
+                  "QuoteType": 1,
+                  "NamePos": 689,
+                  "NameEnd": 702
+                },
+                "Params": {
+                  "LeftParenPos": 702,
+                  "RightParenPos": 714,
+                  "Items": {
+                    "ListPos": 704,
+                    "ListEnd": 713,
+                    "HasDistinct": false,
+                    "Items": [
+                      {
+                        "Name": "timestamp",
+                        "QuoteType": 3,
+                        "NamePos": 704,
+                        "NameEnd": 713
+                      }
+                    ]
+                  },
+                  "ColumnArgList": null
+                }
+              },
+              "Operation": "+",
+              "RightExpr": {
+                "IntervalPos": 718,
+                "Expr": {
+                  "NumPos": 727,
+                  "NumEnd": 728,
+                  "Literal": "2",
+                  "Base": 10
+                },
+                "Unit": {
+                  "Name": "DAY",
+                  "QuoteType": 1,
+                  "NamePos": 729,
+                  "NameEnd": 732
+                }
+              },
+              "HasGlobal": false,
+              "HasNot": false
+            }
+          }
+        ]
+      },
+      "Settings": {
+        "SettingsPos": 733,
+        "ListEnd": 918,
+        "Items": [
+          {
+            "SettingsPos": 742,
+            "Name": {
+              "Name": "execute_merges_on_single_replica_time_threshold",
+              "QuoteType": 1,
+              "NamePos": 742,
+              "NameEnd": 789
+            },
+            "Expr": {
+              "NumPos": 790,
+              "NumEnd": 794,
+              "Literal": "1200",
+              "Base": 10
+            }
+          },
+          {
+            "SettingsPos": 796,
+            "Name": {
+              "Name": "index_granularity",
+              "QuoteType": 1,
+              "NamePos": 796,
+              "NameEnd": 813
+            },
+            "Expr": {
+              "NumPos": 814,
+              "NumEnd": 819,
+              "Literal": "16384",
+              "Base": 10
+            }
+          },
+          {
+            "SettingsPos": 821,
+            "Name": {
+              "Name": "max_bytes_to_merge_at_max_space_in_pool",
+              "QuoteType": 1,
+              "NamePos": 821,
+              "NameEnd": 860
+            },
+            "Expr": {
+              "NumPos": 861,
+              "NumEnd": 872,
+              "Literal": "64424509440",
+              "Base": 10
+            }
+          },
+          {
+            "SettingsPos": 874,
+            "Name": {
+              "Name": "storage_policy",
+              "QuoteType": 1,
+              "NamePos": 874,
+              "NameEnd": 888
+            },
+            "Expr": {
+              "LiteralPos": 890,
+              "LiteralEnd": 894,
+              "Literal": "main"
+            }
+          },
+          {
+            "SettingsPos": 897,
+            "Name": {
+              "Name": "ttl_only_drop_parts",
+              "QuoteType": 1,
+              "NamePos": 897,
+              "NameEnd": 916
+            },
+            "Expr": {
+              "NumPos": 917,
+              "NumEnd": 918,
+              "Literal": "1",
+              "Base": 10
+            }
+          }
+        ]
+      },
+      "OrderBy": {
+        "OrderPos": 585,
+        "ListEnd": 639,
+        "Items": [
+          {
+            "OrderPos": 585,
+            "Expr": {
+              "LeftParenPos": 594,
+              "RightParenPos": 639,
+              "Items": {
+                "ListPos": 595,
+                "ListEnd": 638,
+                "HasDistinct": false,
+                "Items": [
+                  {
+                    "Name": {
+                      "Name": "toUnixTimestamp64Nano",
+                      "QuoteType": 1,
+                      "NamePos": 595,
+                      "NameEnd": 616
+                    },
+                    "Params": {
+                      "LeftParenPos": 616,
+                      "RightParenPos": 628,
+                      "Items": {
+                        "ListPos": 618,
+                        "ListEnd": 627,
+                        "HasDistinct": false,
+                        "Items": [
+                          {
+                            "Name": "timestamp",
+                            "QuoteType": 3,
+                            "NamePos": 618,
+                            "NameEnd": 627
+                          }
+                        ]
+                      },
+                      "ColumnArgList": null
+                    }
+                  },
+                  {
+                    "Name": "api_id",
+                    "QuoteType": 3,
+                    "NamePos": 632,
+                    "NameEnd": 638
+                  }
+                ]
+              },
+              "ColumnArgList": null
+            },
+            "Direction": "None"
+          }
+        ]
+      }
+    },
+    "SubQuery": null,
+    "HasTemporary": false
+  }
+]
\ No newline at end of file