Repository: tobymao/sqlglot

Issue:
sqlglot does not roundtrip assignment operator `:=`
There is no way to roundrip `:=` through sqlglot. It's converted into `EQ`, vs `PropertyEQ`.

**Fully reproducible code snippet**

> Please include a fully reproducible code snippet or the input sql, dialect, and expected output.

```python
In [28]: sqlglot.parse_one('SELECT @x := 1', read='mysql')
Out[28]:
(SELECT expressions:
  (EQ this:
    (PARAMETER this:
      (VAR this: x)), expression:
    (LITERAL this: 1, is_string: False)))

In [29]: sqlglot.parse_one('SELECT @x := 1', read='mysql').sql()
Out[29]: 'SELECT @x = 1'
```

Expected output: `SELECT @x := 1`.

**Official Documentation**

> Please include links to official SQL documentation related to your issue.

I couldn't find anything meaningful about `PropertyEQ`.


Discussion:

Patch:
diff --git a/sqlglot/dialects/duckdb.py b/sqlglot/dialects/duckdb.py
index bed638bd..ff3a72f8 100644
--- a/sqlglot/dialects/duckdb.py
+++ b/sqlglot/dialects/duckdb.py
@@ -117,7 +117,6 @@ class DuckDB(Dialect):
     class Tokenizer(tokens.Tokenizer):
         KEYWORDS = {
             **tokens.Tokenizer.KEYWORDS,
-            ":=": TokenType.COLON_EQ,
             "//": TokenType.DIV,
             "ATTACH": TokenType.COMMAND,
             "BINARY": TokenType.VARBINARY,
@@ -356,9 +355,6 @@ class DuckDB(Dialect):
             exp.VolatileProperty: exp.Properties.Location.UNSUPPORTED,
         }
 
-        def propertyeq_sql(self, expression: exp.PropertyEQ) -> str:
-            return self.binary(expression, ":=")
-
         def interval_sql(self, expression: exp.Interval) -> str:
             multiplier: t.Optional[int] = None
             unit = expression.text("unit").lower()
diff --git a/sqlglot/generator.py b/sqlglot/generator.py
index 35b84c38..24fc9eaf 100644
--- a/sqlglot/generator.py
+++ b/sqlglot/generator.py
@@ -2648,7 +2648,7 @@ class Generator:
         return self.binary(expression, "=")
 
     def propertyeq_sql(self, expression: exp.PropertyEQ) -> str:
-        return self.binary(expression, "=")
+        return self.binary(expression, ":=")
 
     def escape_sql(self, expression: exp.Escape) -> str:
         return self.binary(expression, "ESCAPE")
diff --git a/sqlglot/tokens.py b/sqlglot/tokens.py
index 19be0367..4359184f 100644
--- a/sqlglot/tokens.py
+++ b/sqlglot/tokens.py
@@ -526,6 +526,7 @@ class Tokenizer(metaclass=_Tokenizer):
         "<=": TokenType.LTE,
         "<>": TokenType.NEQ,
         "!=": TokenType.NEQ,
+        ":=": TokenType.COLON_EQ,
         "<=>": TokenType.NULLSAFE_EQ,
         "->": TokenType.ARROW,
         "->>": TokenType.DARROW,


Test patch:
diff --git a/tests/dialects/test_mysql.py b/tests/dialects/test_mysql.py
index 97eb65b0..2cbcb9dc 100644
--- a/tests/dialects/test_mysql.py
+++ b/tests/dialects/test_mysql.py
@@ -123,6 +123,7 @@ class TestMySQL(Validator):
         self.validate_identity("ALTER TABLE test_table ALTER COLUMN test_column SET DEFAULT 1")
 
     def test_identity(self):
+        self.validate_identity("SELECT @var1 := 1, @var2")
         self.validate_identity("UNLOCK TABLES")
         self.validate_identity("LOCK TABLES `app_fields` WRITE")
         self.validate_identity("SELECT 1 XOR 0")