Repository: AfterShip/clickhouse-sql-parser (go)

Issue:
Bug in Query Parser - Incorrect Translation of NOT isZeroOrNull(x)
### Description:

We have identified a bug in the query parser responsible for parsing  statements. The parser incorrectly translates the NOT isZeroOrNull(x) function, transforming it into an invalid statement. This issue needs to be addressed to ensure the parser correctly handles negations of functions.

**Current Behavior:**
The statement:

```
CREATE MATERIALIZED VIEW infra_bm.view_name 
    ON CLUSTER 'default_cluster' TO infra_bm.table_name
(
  `f1` DateTime64(3), 
  `f2` String, 
  `f3` String, 
  `f4` String, 
  `f5` String, 
  `f6` Int64
) AS
SELECT f1,
       f2,
       visitParamExtractString(properties, 'f3') AS f3,
       visitParamExtractString(properties, 'f4') AS f4,
       visitParamExtractString(properties, 'f5') AS f5,
       visitParamExtractInt(properties, 'f6') AS f6
FROM infra_bm.table_name1
WHERE infra_bm.table_name1.event = 'test-event' AND
      NOT isZeroOrNull(f2)
```

is incorrectly translated by the parser into:

```
CREATE MATERIALIZED VIEW infra_bm.view_name 
    ON CLUSTER 'default_cluster' TO infra_bm.table_name
(
  `f1` DateTime64(3), 
  `f2` String, 
  `f3` String, 
  `f4` String, 
  `f5` String, 
  `f6` Int64
) AS
SELECT f1,
       f2,
       visitParamExtractString(properties, 'f3') AS f3,
       visitParamExtractString(properties, 'f4') AS f4,
       visitParamExtractString(properties, 'f5') AS f5,
       visitParamExtractInt(properties, 'f6') AS f6
FROM infra_bm.table_name1
WHERE infra_bm.table_name1.event = 'test-event' AND
      -isZeroOrNull(f2)
COMMENT 'Comment for table';
```

The incorrect translation is invalid due to the use of -isZeroOrNull(f2) instead of NOT isZeroOrNull(f2). In some case clickhouse throws error code `43`. 

Pull request:
Fix wrongly format the string in UnaryExpr
This closes #122 

Patch:
diff --git a/parser/ast.go b/parser/ast.go
index a4719ee..d719a53 100644
--- a/parser/ast.go
+++ b/parser/ast.go
@@ -6645,7 +6645,7 @@ func (n *UnaryExpr) End() Pos {
 }
 
 func (n *UnaryExpr) String() string {
-	return "-" + n.Expr.String()
+	return string(n.Kind) + " " + n.Expr.String()
 }
 
 func (n *UnaryExpr) Accept(visitor ASTVisitor) error {
diff --git a/parser/parser_column.go b/parser/parser_column.go
index 1db650c..d13d658 100644
--- a/parser/parser_column.go
+++ b/parser/parser_column.go
@@ -299,7 +299,7 @@ func (p *Parser) parseColumnExtractExpr(pos Pos) (*ExtractExpr, error) {
 }
 
 func (p *Parser) parseUnaryExpr(pos Pos) (Expr, error) {
-	kind := p.last()
+	op := p.last()
 	switch {
 	case p.matchTokenKind(TokenKindPlus),
 		p.matchTokenKind(TokenKindMinus),
@@ -316,7 +316,7 @@ func (p *Parser) parseUnaryExpr(pos Pos) (Expr, error) {
 
 	return &UnaryExpr{
 		UnaryPos: pos,
-		Kind:     kind.Kind,
+		Kind:     TokenKind(op.ToString()),
 		Expr:     expr,
 	}, nil
 


Test patch:
diff --git a/parser/testdata/ddl/create_mv_with_not_op.sql b/parser/testdata/ddl/create_mv_with_not_op.sql
new file mode 100644
index 0000000..5161721
--- /dev/null
+++ b/parser/testdata/ddl/create_mv_with_not_op.sql
@@ -0,0 +1,19 @@
+CREATE MATERIALIZED VIEW infra_bm.view_name
+    ON CLUSTER 'default_cluster' TO infra_bm.table_name
+(
+  `f1` DateTime64(3),
+  `f2` String,
+  `f3` String,
+  `f4` String,
+  `f5` String,
+  `f6` Int64
+) AS
+SELECT f1,
+       f2,
+       visitParamExtractString(properties, 'f3') AS f3,
+       visitParamExtractString(properties, 'f4') AS f4,
+       visitParamExtractString(properties, 'f5') AS f5,
+       visitParamExtractInt(properties, 'f6') AS f6
+FROM infra_bm.table_name1
+WHERE infra_bm.table_name1.event = 'test-event' AND
+    NOT isZeroOrNull(f2) AND f6-2 > 0
\ No newline at end of file
diff --git a/parser/testdata/ddl/format/create_mv_with_not_op.sql b/parser/testdata/ddl/format/create_mv_with_not_op.sql
new file mode 100644
index 0000000..29b49bf
--- /dev/null
+++ b/parser/testdata/ddl/format/create_mv_with_not_op.sql
@@ -0,0 +1,23 @@
+-- Origin SQL:
+CREATE MATERIALIZED VIEW infra_bm.view_name
+    ON CLUSTER 'default_cluster' TO infra_bm.table_name
+(
+  `f1` DateTime64(3),
+  `f2` String,
+  `f3` String,
+  `f4` String,
+  `f5` String,
+  `f6` Int64
+) AS
+SELECT f1,
+       f2,
+       visitParamExtractString(properties, 'f3') AS f3,
+       visitParamExtractString(properties, 'f4') AS f4,
+       visitParamExtractString(properties, 'f5') AS f5,
+       visitParamExtractInt(properties, 'f6') AS f6
+FROM infra_bm.table_name1
+WHERE infra_bm.table_name1.event = 'test-event' AND
+    NOT isZeroOrNull(f2) AND f6-2 > 0
+
+-- Format SQL:
+CREATE MATERIALIZED VIEW infra_bm.view_name ON CLUSTER 'default_cluster' TO infra_bm.table_name (`f1` DateTime64(3), `f2` String, `f3` String, `f4` String, `f5` String, `f6` Int64) AS SELECT f1, f2, visitParamExtractString(properties, 'f3') AS f3, visitParamExtractString(properties, 'f4') AS f4, visitParamExtractString(properties, 'f5') AS f5, visitParamExtractInt(properties, 'f6') AS f6 FROM infra_bm.table_name1 WHERE infra_bm.table_name1.event = 'test-event' AND NOT isZeroOrNull(f2) AND f6 - 2 > 0;
diff --git a/parser/testdata/ddl/output/create_mv_with_not_op.sql.golden.json b/parser/testdata/ddl/output/create_mv_with_not_op.sql.golden.json
new file mode 100644
index 0000000..444962d
--- /dev/null
+++ b/parser/testdata/ddl/output/create_mv_with_not_op.sql.golden.json
@@ -0,0 +1,608 @@
+[
+  {
+    "CreatePos": 0,
+    "StatementEnd": 559,
+    "Name": {
+      "Database": {
+        "Name": "infra_bm",
+        "QuoteType": 1,
+        "NamePos": 25,
+        "NameEnd": 33
+      },
+      "Table": {
+        "Name": "view_name",
+        "QuoteType": 1,
+        "NamePos": 34,
+        "NameEnd": 43
+      }
+    },
+    "IfNotExists": false,
+    "OnCluster": {
+      "OnPos": 48,
+      "Expr": {
+        "LiteralPos": 60,
+        "LiteralEnd": 75,
+        "Literal": "default_cluster"
+      }
+    },
+    "Engine": null,
+    "Destination": {
+      "ToPos": 77,
+      "TableIdentifier": {
+        "Database": {
+          "Name": "infra_bm",
+          "QuoteType": 1,
+          "NamePos": 80,
+          "NameEnd": 88
+        },
+        "Table": {
+          "Name": "table_name",
+          "QuoteType": 1,
+          "NamePos": 89,
+          "NameEnd": 99
+        }
+      },
+      "TableSchema": {
+        "SchemaPos": 100,
+        "SchemaEnd": 197,
+        "Columns": [
+          {
+            "NamePos": 105,
+            "ColumnEnd": 121,
+            "Name": {
+              "Ident": {
+                "Name": "f1",
+                "QuoteType": 3,
+                "NamePos": 105,
+                "NameEnd": 107
+              },
+              "DotIdent": null
+            },
+            "Type": {
+              "LeftParenPos": 120,
+              "RightParenPos": 121,
+              "Name": {
+                "Name": "DateTime64",
+                "QuoteType": 1,
+                "NamePos": 109,
+                "NameEnd": 119
+              },
+              "Params": [
+                {
+                  "NumPos": 120,
+                  "NumEnd": 121,
+                  "Literal": "3",
+                  "Base": 10
+                }
+              ]
+            },
+            "NotNull": null,
+            "Nullable": null,
+            "DefaultExpr": null,
+            "MaterializedExpr": null,
+            "AliasExpr": null,
+            "Codec": null,
+            "TTL": null,
+            "Comment": null,
+            "CompressionCodec": null
+          },
+          {
+            "NamePos": 127,
+            "ColumnEnd": 137,
+            "Name": {
+              "Ident": {
+                "Name": "f2",
+                "QuoteType": 3,
+                "NamePos": 127,
+                "NameEnd": 129
+              },
+              "DotIdent": null
+            },
+            "Type": {
+              "Name": {
+                "Name": "String",
+                "QuoteType": 1,
+                "NamePos": 131,
+                "NameEnd": 137
+              }
+            },
+            "NotNull": null,
+            "Nullable": null,
+            "DefaultExpr": null,
+            "MaterializedExpr": null,
+            "AliasExpr": null,
+            "Codec": null,
+            "TTL": null,
+            "Comment": null,
+            "CompressionCodec": null
+          },
+          {
+            "NamePos": 142,
+            "ColumnEnd": 152,
+            "Name": {
+              "Ident": {
+                "Name": "f3",
+                "QuoteType": 3,
+                "NamePos": 142,
+                "NameEnd": 144
+              },
+              "DotIdent": null
+            },
+            "Type": {
+              "Name": {
+                "Name": "String",
+                "QuoteType": 1,
+                "NamePos": 146,
+                "NameEnd": 152
+              }
+            },
+            "NotNull": null,
+            "Nullable": null,
+            "DefaultExpr": null,
+            "MaterializedExpr": null,
+            "AliasExpr": null,
+            "Codec": null,
+            "TTL": null,
+            "Comment": null,
+            "CompressionCodec": null
+          },
+          {
+            "NamePos": 157,
+            "ColumnEnd": 167,
+            "Name": {
+              "Ident": {
+                "Name": "f4",
+                "QuoteType": 3,
+                "NamePos": 157,
+                "NameEnd": 159
+              },
+              "DotIdent": null
+            },
+            "Type": {
+              "Name": {
+                "Name": "String",
+                "QuoteType": 1,
+                "NamePos": 161,
+                "NameEnd": 167
+              }
+            },
+            "NotNull": null,
+            "Nullable": null,
+            "DefaultExpr": null,
+            "MaterializedExpr": null,
+            "AliasExpr": null,
+            "Codec": null,
+            "TTL": null,
+            "Comment": null,
+            "CompressionCodec": null
+          },
+          {
+            "NamePos": 172,
+            "ColumnEnd": 182,
+            "Name": {
+              "Ident": {
+                "Name": "f5",
+                "QuoteType": 3,
+                "NamePos": 172,
+                "NameEnd": 174
+              },
+              "DotIdent": null
+            },
+            "Type": {
+              "Name": {
+                "Name": "String",
+                "QuoteType": 1,
+                "NamePos": 176,
+                "NameEnd": 182
+              }
+            },
+            "NotNull": null,
+            "Nullable": null,
+            "DefaultExpr": null,
+            "MaterializedExpr": null,
+            "AliasExpr": null,
+            "Codec": null,
+            "TTL": null,
+            "Comment": null,
+            "CompressionCodec": null
+          },
+          {
+            "NamePos": 187,
+            "ColumnEnd": 196,
+            "Name": {
+              "Ident": {
+                "Name": "f6",
+                "QuoteType": 3,
+                "NamePos": 187,
+                "NameEnd": 189
+              },
+              "DotIdent": null
+            },
+            "Type": {
+              "Name": {
+                "Name": "Int64",
+                "QuoteType": 1,
+                "NamePos": 191,
+                "NameEnd": 196
+              }
+            },
+            "NotNull": null,
+            "Nullable": null,
+            "DefaultExpr": null,
+            "MaterializedExpr": null,
+            "AliasExpr": null,
+            "Codec": null,
+            "TTL": null,
+            "Comment": null,
+            "CompressionCodec": null
+          }
+        ],
+        "AliasTable": null,
+        "TableFunction": null
+      }
+    },
+    "SubQuery": {
+      "HasParen": false,
+      "Select": {
+        "SelectPos": 202,
+        "StatementEnd": 559,
+        "With": null,
+        "Top": null,
+        "SelectItems": [
+          {
+            "Expr": {
+              "Name": "f1",
+              "QuoteType": 1,
+              "NamePos": 209,
+              "NameEnd": 211
+            },
+            "Modifiers": [],
+            "Alias": null
+          },
+          {
+            "Expr": {
+              "Name": "f2",
+              "QuoteType": 1,
+              "NamePos": 220,
+              "NameEnd": 222
+            },
+            "Modifiers": [],
+            "Alias": null
+          },
+          {
+            "Expr": {
+              "Name": {
+                "Name": "visitParamExtractString",
+                "QuoteType": 1,
+                "NamePos": 231,
+                "NameEnd": 254
+              },
+              "Params": {
+                "LeftParenPos": 254,
+                "RightParenPos": 271,
+                "Items": {
+                  "ListPos": 255,
+                  "ListEnd": 270,
+                  "HasDistinct": false,
+                  "Items": [
+                    {
+                      "Expr": {
+                        "Name": "properties",
+                        "QuoteType": 1,
+                        "NamePos": 255,
+                        "NameEnd": 265
+                      },
+                      "Alias": null
+                    },
+                    {
+                      "Expr": {
+                        "LiteralPos": 268,
+                        "LiteralEnd": 270,
+                        "Literal": "f3"
+                      },
+                      "Alias": null
+                    }
+                  ]
+                },
+                "ColumnArgList": null
+              }
+            },
+            "Modifiers": [],
+            "Alias": {
+              "Name": "f3",
+              "QuoteType": 1,
+              "NamePos": 276,
+              "NameEnd": 278
+            }
+          },
+          {
+            "Expr": {
+              "Name": {
+                "Name": "visitParamExtractString",
+                "QuoteType": 1,
+                "NamePos": 287,
+                "NameEnd": 310
+              },
+              "Params": {
+                "LeftParenPos": 310,
+                "RightParenPos": 327,
+                "Items": {
+                  "ListPos": 311,
+                  "ListEnd": 326,
+                  "HasDistinct": false,
+                  "Items": [
+                    {
+                      "Expr": {
+                        "Name": "properties",
+                        "QuoteType": 1,
+                        "NamePos": 311,
+                        "NameEnd": 321
+                      },
+                      "Alias": null
+                    },
+                    {
+                      "Expr": {
+                        "LiteralPos": 324,
+                        "LiteralEnd": 326,
+                        "Literal": "f4"
+                      },
+                      "Alias": null
+                    }
+                  ]
+                },
+                "ColumnArgList": null
+              }
+            },
+            "Modifiers": [],
+            "Alias": {
+              "Name": "f4",
+              "QuoteType": 1,
+              "NamePos": 332,
+              "NameEnd": 334
+            }
+          },
+          {
+            "Expr": {
+              "Name": {
+                "Name": "visitParamExtractString",
+                "QuoteType": 1,
+                "NamePos": 343,
+                "NameEnd": 366
+              },
+              "Params": {
+                "LeftParenPos": 366,
+                "RightParenPos": 383,
+                "Items": {
+                  "ListPos": 367,
+                  "ListEnd": 382,
+                  "HasDistinct": false,
+                  "Items": [
+                    {
+                      "Expr": {
+                        "Name": "properties",
+                        "QuoteType": 1,
+                        "NamePos": 367,
+                        "NameEnd": 377
+                      },
+                      "Alias": null
+                    },
+                    {
+                      "Expr": {
+                        "LiteralPos": 380,
+                        "LiteralEnd": 382,
+                        "Literal": "f5"
+                      },
+                      "Alias": null
+                    }
+                  ]
+                },
+                "ColumnArgList": null
+              }
+            },
+            "Modifiers": [],
+            "Alias": {
+              "Name": "f5",
+              "QuoteType": 1,
+              "NamePos": 388,
+              "NameEnd": 390
+            }
+          },
+          {
+            "Expr": {
+              "Name": {
+                "Name": "visitParamExtractInt",
+                "QuoteType": 1,
+                "NamePos": 399,
+                "NameEnd": 419
+              },
+              "Params": {
+                "LeftParenPos": 419,
+                "RightParenPos": 436,
+                "Items": {
+                  "ListPos": 420,
+                  "ListEnd": 435,
+                  "HasDistinct": false,
+                  "Items": [
+                    {
+                      "Expr": {
+                        "Name": "properties",
+                        "QuoteType": 1,
+                        "NamePos": 420,
+                        "NameEnd": 430
+                      },
+                      "Alias": null
+                    },
+                    {
+                      "Expr": {
+                        "LiteralPos": 433,
+                        "LiteralEnd": 435,
+                        "Literal": "f6"
+                      },
+                      "Alias": null
+                    }
+                  ]
+                },
+                "ColumnArgList": null
+              }
+            },
+            "Modifiers": [],
+            "Alias": {
+              "Name": "f6",
+              "QuoteType": 1,
+              "NamePos": 441,
+              "NameEnd": 443
+            }
+          }
+        ],
+        "From": {
+          "FromPos": 444,
+          "Expr": {
+            "Table": {
+              "TablePos": 449,
+              "TableEnd": 469,
+              "Alias": null,
+              "Expr": {
+                "Database": {
+                  "Name": "infra_bm",
+                  "QuoteType": 1,
+                  "NamePos": 449,
+                  "NameEnd": 457
+                },
+                "Table": {
+                  "Name": "table_name1",
+                  "QuoteType": 1,
+                  "NamePos": 458,
+                  "NameEnd": 469
+                }
+              },
+              "HasFinal": false
+            },
+            "StatementEnd": 469,
+            "SampleRatio": null,
+            "HasFinal": false
+          }
+        },
+        "ArrayJoin": null,
+        "Window": null,
+        "Prewhere": null,
+        "Where": {
+          "WherePos": 470,
+          "Expr": {
+            "LeftExpr": {
+              "LeftExpr": {
+                "LeftExpr": {
+                  "Database": {
+                    "Name": "infra_bm",
+                    "QuoteType": 1,
+                    "NamePos": 476,
+                    "NameEnd": 484
+                  },
+                  "Table": {
+                    "Name": "table_name1",
+                    "QuoteType": 1,
+                    "NamePos": 485,
+                    "NameEnd": 496
+                  },
+                  "Column": {
+                    "Name": "event",
+                    "QuoteType": 1,
+                    "NamePos": 497,
+                    "NameEnd": 502
+                  }
+                },
+                "Operation": "=",
+                "RightExpr": {
+                  "LiteralPos": 506,
+                  "LiteralEnd": 516,
+                  "Literal": "test-event"
+                },
+                "HasGlobal": false,
+                "HasNot": false
+              },
+              "Operation": "AND",
+              "RightExpr": {
+                "UnaryPos": 526,
+                "Kind": "NOT",
+                "Expr": {
+                  "Name": {
+                    "Name": "isZeroOrNull",
+                    "QuoteType": 1,
+                    "NamePos": 530,
+                    "NameEnd": 542
+                  },
+                  "Params": {
+                    "LeftParenPos": 542,
+                    "RightParenPos": 545,
+                    "Items": {
+                      "ListPos": 543,
+                      "ListEnd": 545,
+                      "HasDistinct": false,
+                      "Items": [
+                        {
+                          "Expr": {
+                            "Name": "f2",
+                            "QuoteType": 1,
+                            "NamePos": 543,
+                            "NameEnd": 545
+                          },
+                          "Alias": null
+                        }
+                      ]
+                    },
+                    "ColumnArgList": null
+                  }
+                }
+              },
+              "HasGlobal": false,
+              "HasNot": false
+            },
+            "Operation": "AND",
+            "RightExpr": {
+              "LeftExpr": {
+                "LeftExpr": {
+                  "Name": "f6",
+                  "QuoteType": 1,
+                  "NamePos": 551,
+                  "NameEnd": 553
+                },
+                "Operation": "-",
+                "RightExpr": {
+                  "NumPos": 554,
+                  "NumEnd": 555,
+                  "Literal": "2",
+                  "Base": 10
+                },
+                "HasGlobal": false,
+                "HasNot": false
+              },
+              "Operation": "\u003e",
+              "RightExpr": {
+                "NumPos": 558,
+                "NumEnd": 559,
+                "Literal": "0",
+                "Base": 10
+              },
+              "HasGlobal": false,
+              "HasNot": false
+            },
+            "HasGlobal": false,
+            "HasNot": false
+          }
+        },
+        "GroupBy": null,
+        "WithTotal": false,
+        "Having": null,
+        "OrderBy": null,
+        "LimitBy": null,
+        "Limit": null,
+        "Settings": null,
+        "Format": null,
+        "UnionAll": null,
+        "UnionDistinct": null,
+        "Except": null
+      }
+    },
+    "Populate": false,
+    "Comment": null
+  }
+]
\ No newline at end of file