Repository: tobymao/sqlglot

Issue:
Timestamp trunc method issue from postgres to MySQL
I was trying to convert an SQL query from Postgres to MySQL. Check the following code snippet

```
import sqlglot
sqlglot.transpile("SELECT date_trunc('hour', timestamp '2001-02-16 20:38:40') FROM dual", read="postgres", write="mysql") 
```

This returns

`["SELECT TIMESTAMP_TRUNC(CAST('2001-02-16 20:38:40' AS DATETIME), HOUR) FROM dual"]`

But MySQL doesn't have TIMESTAMP_TRUNC method. Please look into this.



Discussion:
I have a similar issue

```
import sqlglot

print(sqlglot.transpile("SELECT mt.mode_was, AVG(EXTRACT(epoch FROM (mt.cross_time::TIMESTAMP - mt.second_cross_time::TIMESTAMP))) AS average_time FROM main_table mt GROUP BY mt.mode_was ORDER BY average_time DESC LIMIT 1", read="postgres", write="mysql")[0])
```

this returns
`SELECT mt.mode_was, AVG(EXTRACT(epoch FROM (CAST(mt.cross_time AS DATETIME) - CAST(mt.second_cross_time AS DATETIME)))) AS average_time FROM main_table AS mt GROUP BY mt.mode_was ORDER BY CASE WHEN average_time IS NULL THEN 1 ELSE 0 END DESC, average_time DESC LIMIT 1`
What's the issue @FaizelK?
@georgesittas 
epoch is not a thing in mysql, i wanted to convert that query to mysql, but it still kept epoch. when running this my mysql here are the results because  MySQL does not support the EXTRACT(epoch FROM :

`You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'epoch FROM (CAST(mt.cross_time AS DATETIME) - CAST(mt.second_cross_time AS DATET' at line 1`

Hi @FaizelK, can you please open a separate issue describing the faulty behavior? Please include a minimum reproducible example, what you would ideally want to transpile to as well as any docs validating that.

Thanks!

Resolving patch:
diff --git a/sqlglot/dialects/mysql.py b/sqlglot/dialects/mysql.py
--- a/sqlglot/dialects/mysql.py
+++ b/sqlglot/dialects/mysql.py
@@ -867,3 +867,16 @@ def chr_sql(self, expression: exp.Chr) -> str:
             charset = expression.args.get("charset")
             using = f" USING {self.sql(charset)}" if charset else ""
             return f"CHAR({this}{using})"
+
+        def timestamptrunc_sql(self, expression: exp.TimestampTrunc) -> str:
+            unit = expression.args.get("unit")
+
+            # Pick an old-enough date to avoid negative timestamp diffs
+            start_ts = "'0000-01-01 00:00:00'"
+
+            # Source: https://stackoverflow.com/a/32955740
+            timestamp_diff = build_date_delta(exp.TimestampDiff)([unit, start_ts, expression.this])
+            interval = exp.Interval(this=timestamp_diff, unit=unit)
+            dateadd = build_date_delta_with_interval(exp.DateAdd)([start_ts, interval])
+
+            return self.sql(dateadd)


Test patch:
diff --git a/tests/dialects/test_mysql.py b/tests/dialects/test_mysql.py
--- a/tests/dialects/test_mysql.py
+++ b/tests/dialects/test_mysql.py
@@ -1109,3 +1109,23 @@ def test_safe_div(self):
                 "tsql": "CAST(a AS FLOAT) / NULLIF(b, 0)",
             },
         )
+
+    def test_timestamp_trunc(self):
+        for dialect in ("postgres", "snowflake", "duckdb", "spark", "databricks"):
+            for unit in (
+                "MILLISECOND",
+                "SECOND",
+                "DAY",
+                "MONTH",
+                "YEAR",
+            ):
+                with self.subTest(f"MySQL -> {dialect} Timestamp Trunc with unit {unit}: "):
+                    self.validate_all(
+                        f"DATE_ADD('0000-01-01 00:00:00', INTERVAL (TIMESTAMPDIFF({unit}, '0000-01-01 00:00:00', CAST('2001-02-16 20:38:40' AS DATETIME))) {unit})",
+                        read={
+                            dialect: f"DATE_TRUNC({unit}, TIMESTAMP '2001-02-16 20:38:40')",
+                        },
+                        write={
+                            "mysql": f"DATE_ADD('0000-01-01 00:00:00', INTERVAL (TIMESTAMPDIFF({unit}, '0000-01-01 00:00:00', CAST('2001-02-16 20:38:40' AS DATETIME))) {unit})",
+                        },
+                    )