Repository: AfterShip/clickhouse-sql-parser
Pull request #133 (2025-03-21 09:44:22)

[ Bug Fix ] Preserve DISTINCT in ParamExprList by Reusing ColumnExprList.String()
## Overview
`ParamExprList.String()` currently builds its output by manually iterating through each item in `f.Items.Items`. This approach inadvertently omits the `HasDistinct` attribute from `ColumnExprList`, causing the `DISTINCT` statement to be lost when generating SQL from the AST.

## Root Cause
Instead of leveraging the existing `String()` function in `ColumnExprList`—which properly accounts for `HasDistinct`—`ParamExprList.String()` re-implements the logic, thereby failing to include `DISTINCT`.

## Proposed Solution
Delegate the string conversion to `f.Items.String()`. By reusing `ColumnExprList.String()`, the `HasDistinct` attribute is preserved, ensuring accurate SQL translation. This approach centralizes responsibility and avoids duplicating logic.

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


### Details

* **1** of **1**   **(100.0%)**  changed or added relevant line in **1** file are covered.
* No unchanged relevant lines lost coverage.
* Overall coverage decreased (**-0.01%**) to **62.537%**

---



|  Totals | [![Coverage Status](https://coveralls.io/builds/72852451/badge)](https://coveralls.io/builds/72852451) |
| :-- | --: |
| Change from base [Build 13983746915](https://coveralls.io/builds/72847871): |  -0.01% |
| Covered Lines: | 6729 |
| Relevant Lines: | 10760 |

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

git-hulk: Hi @anshal21, thanks for your contribution.

Could you please add a sample SQL file in the [testdata/query](https://github.com/AfterShip/clickhouse-sql-parser/tree/master/parser/testdata/query) and run `make update_test` to update the test suites?

Patch:
diff --git a/parser/ast.go b/parser/ast.go
index 4933ce8..37f0282 100644
--- a/parser/ast.go
+++ b/parser/ast.go
@@ -2928,12 +2928,7 @@ func (f *ParamExprList) End() Pos {
 func (f *ParamExprList) String() string {
 	var builder strings.Builder
 	builder.WriteString("(")
-	for i, item := range f.Items.Items {
-		if i > 0 {
-			builder.WriteString(", ")
-		}
-		builder.WriteString(item.String())
-	}
+	builder.WriteString(f.Items.String())
 	builder.WriteString(")")
 	return builder.String()
 }


Test patch:
diff --git a/parser/testdata/query/format/select_with_distinct.sql b/parser/testdata/query/format/select_with_distinct.sql
new file mode 100644
index 0000000..60b6134
--- /dev/null
+++ b/parser/testdata/query/format/select_with_distinct.sql
@@ -0,0 +1,5 @@
+-- Origin SQL:
+SELECT count(DISTINCT(RECORD_ID)) FROM RECORD_TABLE
+
+-- Format SQL:
+SELECT count(DISTINCT (RECORD_ID)) FROM RECORD_TABLE;
diff --git a/parser/testdata/query/output/select_with_distinct.sql.golden.json b/parser/testdata/query/output/select_with_distinct.sql.golden.json
new file mode 100644
index 0000000..4228d75
--- /dev/null
+++ b/parser/testdata/query/output/select_with_distinct.sql.golden.json
@@ -0,0 +1,96 @@
+[
+  {
+    "SelectPos": 0,
+    "StatementEnd": 51,
+    "With": null,
+    "Top": null,
+    "SelectItems": [
+      {
+        "Expr": {
+          "Name": {
+            "Name": "count",
+            "QuoteType": 1,
+            "NamePos": 7,
+            "NameEnd": 12
+          },
+          "Params": {
+            "LeftParenPos": 12,
+            "RightParenPos": 32,
+            "Items": {
+              "ListPos": 13,
+              "ListEnd": 31,
+              "HasDistinct": true,
+              "Items": [
+                {
+                  "Expr": {
+                    "LeftParenPos": 21,
+                    "RightParenPos": 31,
+                    "Items": {
+                      "ListPos": 22,
+                      "ListEnd": 31,
+                      "HasDistinct": false,
+                      "Items": [
+                        {
+                          "Expr": {
+                            "Name": "RECORD_ID",
+                            "QuoteType": 1,
+                            "NamePos": 22,
+                            "NameEnd": 31
+                          },
+                          "Alias": null
+                        }
+                      ]
+                    },
+                    "ColumnArgList": null
+                  },
+                  "Alias": null
+                }
+              ]
+            },
+            "ColumnArgList": null
+          }
+        },
+        "Modifiers": [],
+        "Alias": null
+      }
+    ],
+    "From": {
+      "FromPos": 34,
+      "Expr": {
+        "Table": {
+          "TablePos": 39,
+          "TableEnd": 51,
+          "Alias": null,
+          "Expr": {
+            "Database": null,
+            "Table": {
+              "Name": "RECORD_TABLE",
+              "QuoteType": 1,
+              "NamePos": 39,
+              "NameEnd": 51
+            }
+          },
+          "HasFinal": false
+        },
+        "StatementEnd": 51,
+        "SampleRatio": null,
+        "HasFinal": false
+      }
+    },
+    "ArrayJoin": null,
+    "Window": null,
+    "Prewhere": null,
+    "Where": null,
+    "GroupBy": null,
+    "WithTotal": false,
+    "Having": null,
+    "OrderBy": null,
+    "LimitBy": null,
+    "Limit": null,
+    "Settings": null,
+    "Format": null,
+    "UnionAll": null,
+    "UnionDistinct": null,
+    "Except": null
+  }
+]
\ No newline at end of file
diff --git a/parser/testdata/query/select_with_distinct.sql b/parser/testdata/query/select_with_distinct.sql
new file mode 100644
index 0000000..aa301aa
--- /dev/null
+++ b/parser/testdata/query/select_with_distinct.sql
@@ -0,0 +1,1 @@
+SELECT count(DISTINCT(RECORD_ID)) FROM RECORD_TABLE
\ No newline at end of file