11from sqlglot .dialects .duckdb import DuckDB as DuckdbDialect
22from sqlglot .dialects .postgres import Postgres as PostgresDialect
33from sqlglot .dialects .mysql import MySQL as MysqlDialect
4+ from sqlglot .dialects .snowflake import Snowflake as SnowflakeDialect
45from sqlglot import exp
56from sqlglot .helper import seq_get
67from sqlglot .generator import Generator
78from sqlglot .dialects .dialect import (
89 build_date_delta ,
910 build_date_delta_with_interval ,
11+ rename_func ,
12+ unit_to_str
1013)
1114
1215
@@ -34,15 +37,75 @@ def _postgres_unix_to_time_sql(self: Generator, expression: exp.UnixToTime) -> s
3437 return self .func ("to_timestamp" , exp .Div (this = timestamp , expression = exp .func ("POW" , 10 , scale )))
3538
3639
40+ # temporary fix for Postgres IN clause(bin filter)
41+ def _postgres_in_sql (self : Generator , expression : exp .In ) -> str :
42+ expression .set ("expressions" , [
43+ exp .Array (expressions = [
44+ exp .cast (item , to = exp .DataType .Type .DOUBLE ) if isinstance (item , exp .Literal ) and item .args .get ("is_string" ) is False else item
45+ for item in in_item_exp .args .get ("expressions" , [])
46+ ]) if isinstance (in_item_exp , exp .Array ) else in_item_exp
47+ for in_item_exp in expression .args .get ("expressions" , [])
48+ ])
49+ return self .in_sql (expression )
50+
51+
52+ def _postgres_timestamp_trunc (self : Generator , expression : exp .TimestampTrunc ) -> str :
53+ if expression .unit .this .lower () == "isoyear" :
54+ return self .func ("to_date" , self .func ("to_char" , expression .this , exp .Literal .string ("IYYY-0001" )), exp .Literal .string ("IYYY-IDDD" ))
55+
56+ return self .func ("DATE_TRUNC" , unit_to_str (expression ), expression .this )
57+
58+
59+ def _postgres_time_to_str_sql (self : Generator , expression : exp .TimeToStr ) -> str :
60+ if expression .args .get ("format" ).this == "%U" :
61+ # postgres not support non-iso week
62+ # current_pass_days = EXTRACT(isodow FROM DATE_TRUNC('year', date))
63+ # week_number = floor((EXTRACT(day from date) + current_pass_days - 1) / 7)
64+ return self .sql (exp .Floor (
65+ this = exp .Div (
66+ this = exp .Paren (this = exp .Add (
67+ this = exp .Sub (
68+ this = exp .Cast (this = self .func ("TO_CHAR" , expression .this , exp .Literal .string ("DDD" )), to = "int" ),
69+ expression = exp .Literal .number (1 )
70+ ),
71+ expression = exp .Extract (this = exp .Var (this = "isodow" ), expression = exp .TimestampTrunc (this = expression .this , unit = exp .Literal .string ("year" )))
72+ )),
73+ expression = exp .Literal .number (7 ),
74+ )
75+ ))
76+
77+ return self .func ("TO_CHAR" , expression .this , self .format_time (expression ))
78+
79+
80+ def _postgres_str_to_time_sql (self : Generator , expression : exp .StrToTime ) -> str :
81+ # adapter duckdb non-iso week
82+ if expression .args .get ("format" ).this == "%Y%U" and isinstance (expression .this , exp .TimeToStr ) and expression .this .args .get ("format" ).this == "%Y%U" :
83+ return self .sql (exp .Sub (
84+ this = exp .TimestampTrunc (this = expression .this .this , unit = exp .Literal .string ("day" )),
85+ expression = exp .Mul (
86+ this = exp .Extract (this = exp .Var (this = "dow" ), expression = expression .this .this ),
87+ expression = exp .Interval (this = exp .Literal .number (1 ), unit = exp .Var (this = "day" ))
88+ )
89+ ))
90+ return self .func ("TO_TIMESTAMP" , expression .this , self .format_time (expression ))
91+
92+
3793PostgresDialect .Generator .TRANSFORMS [exp .Round ] = lambda _ , e : _postgres_round_generator (e )
3894PostgresDialect .Generator .TRANSFORMS [exp .UnixToTime ] = _postgres_unix_to_time_sql
95+ PostgresDialect .Generator .TRANSFORMS [exp .In ] = _postgres_in_sql
96+ PostgresDialect .Generator .TRANSFORMS [exp .TimestampTrunc ] = _postgres_timestamp_trunc
97+ PostgresDialect .Generator .TRANSFORMS [exp .TimeToStr ] = _postgres_time_to_str_sql
98+ PostgresDialect .Generator .TRANSFORMS [exp .StrToTime ] = _postgres_str_to_time_sql
3999
40100
41101# Mysql Dialect
42102def _mysql_timestamptrunc_sql (self : Generator , expression : exp .TimestampTrunc ) -> str :
43103 unit = expression .args .get ("unit" )
44104
45- start_ts = "'0001-01-01 00:00:00'"
105+ if unit .this .lower () == "isoyear" :
106+ unit = exp .Var (this = "YEAR" )
107+
108+ start_ts = "'0006-01-01 00:00:00'"
46109
47110 timestamp_diff = build_date_delta (exp .TimestampDiff )([unit , start_ts , expression .this ])
48111 interval = exp .Interval (this = timestamp_diff , unit = unit )
@@ -57,6 +120,13 @@ def _mysql_extract_sql(self: Generator, expression: exp.Extract) -> str:
57120 return self .sql (exp .Sub (this = self .func ("DAYOFWEEK" , expression .expression ), expression = exp .Literal .number (1 )))
58121 if unit == "week" :
59122 return self .func ("WEEK" , expression .expression , exp .Literal .number (3 ))
123+ if unit == "isoyear" :
124+ return self .sql (exp .Floor (this = exp .Div (this = self .func ("YEARWEEK" , expression .expression , exp .Literal .number (3 )), expression = exp .Literal .number (100 ))))
125+ if unit == "isodow" :
126+ return self .sql (exp .Add (
127+ this = exp .Mod (this = exp .Add (this = self .func ("DAYOFWEEK" , expression .expression ), expression = exp .Literal .number (5 )), expression = exp .Literal .number (7 )),
128+ expression = exp .Literal .number (1 )
129+ ))
60130 return self .extract_sql (expression )
61131
62132
@@ -67,7 +137,93 @@ def _mysql_unix_to_time_sql(self: Generator, expression: exp.UnixToTime) -> str:
67137 return self .func ("FROM_UNIXTIME" , exp .Div (this = timestamp , expression = exp .func ("POW" , 10 , scale )), self .format_time (expression ))
68138
69139
140+ def _mysql_str_to_time_sql (self : Generator , expression : exp .StrToTime ) -> str :
141+ # adapter duckdb non-iso week
142+ if expression .args .get ("format" ).this == "%Y%U" and isinstance (expression .this , exp .TimeToStr ) and expression .this .args .get ("format" ).this == "%Y%U" :
143+ return _mysql_timestamptrunc_sql (self , exp .TimestampTrunc (this = expression .this .this , unit = exp .Literal .string ("WEEK" )))
144+ return self .func ("STR_TO_DATE" , expression .this , self .format_time (expression ))
145+
146+
70147MysqlDialect .Generator .TRANSFORMS [exp .Extract ] = _mysql_extract_sql
71148MysqlDialect .Generator .TRANSFORMS [exp .Array ] = lambda self , e : self .func ("JSON_ARRAY" , * e .expressions )
72149MysqlDialect .Generator .TRANSFORMS [exp .TimestampTrunc ] = _mysql_timestamptrunc_sql
73150MysqlDialect .Generator .TRANSFORMS [exp .UnixToTime ] = _mysql_unix_to_time_sql
151+ MysqlDialect .Generator .TRANSFORMS [exp .Mod ] = lambda self , e : self .func ("MOD" , e .this , e .expression )
152+ MysqlDialect .Generator .TRANSFORMS [exp .StrToTime ] = _mysql_str_to_time_sql
153+
154+
155+ # Snowflake Dialect
156+ def _snowflake_extract_sql (self : Generator , expression : exp .Extract ) -> str :
157+ unit = expression .this .this .lower ()
158+ if unit == "isoyear" :
159+ return self .func ("YEAROFWEEKISO" , expression .expression )
160+ if unit == "week" :
161+ return self .func ("WEEKISO" , expression .expression )
162+ if unit == "isodow" :
163+ return self .func ("DAYOFWEEKISO" , expression .expression )
164+ if unit == "dow" :
165+ return exp .Sub (this = self .func ("DAYOFWEEK" , expression .expression ), expression = exp .Literal .number (1 ))
166+ return rename_func ("DATE_PART" )(self , expression )
167+
168+
169+ def _snowflake_time_to_str (self : Generator , expression : exp .TimeToStr ) -> str :
170+ if expression .args .get ("format" ).this == "%U" :
171+ # snowflake not support non-iso week
172+ # IFF(TO_CHAR(TO_TIMESTAMP_TZ(TO_CHAR(date, 'YYYY'), 'YYYY'), 'DY') = 'Sun', WEEK(date), WEEK(date)-1)
173+ return self .func (
174+ "IFF" ,
175+ exp .EQ (
176+ this = self .func ("TO_CHAR" , self .func ("TO_TIMESTAMP_TZ" , self .func ("TO_CHAR" , expression .this , exp .Literal .string ('YYYY' )), exp .Literal .string ('YYYY' )), exp .Literal .string ("DY" )),
177+ expression = exp .Literal .string ('Sun' )
178+ ),
179+ self .func ("WEEK" , expression .this ),
180+ exp .Sub (this = self .func ("WEEK" , expression .this ), expression = exp .Literal .number (1 ))
181+ )
182+
183+ return self .func ("TO_CHAR" , exp .cast (expression .this , exp .DataType .Type .TIMESTAMP ), self .format_time (expression ))
184+
185+
186+ def _snowflake_str_to_time_sql (self : Generator , expression : exp .StrToTime ) -> str :
187+ # adapter duckdb non-iso week
188+ if expression .args .get ("format" ).this == "%Y%U" and isinstance (expression .this , exp .TimeToStr ) and expression .this .args .get ("format" ).this == "%Y%U" :
189+ return self .func ("DATE_TRUNC" , exp .Literal .string ("WEEK" ), expression .this .this )
190+ return self .func ("TO_TIMESTAMP" , expression .this , self .format_time (expression ))
191+
192+
193+ def _snowflake_timestamp_trunc_sql (self : Generator , expression : exp .TimestampTrunc ) -> str :
194+ unit = expression .unit .this .lower ()
195+
196+ # dateadd(day, -((date_extract(DAYOFWEEKISO from date)) - 1), date_trunc('day', date))
197+ trunc_iso_week = self .func (
198+ "dateadd" ,
199+ exp .Var (this = "day" ),
200+ exp .Sub (
201+ this = exp .Literal .number (1 ),
202+ expression = exp .Extract (this = exp .Var (this = "DAYOFWEEKISO" ), expression = expression .this )
203+ ),
204+ self .func ("date_trunc" , exp .Literal .string ("day" ), expression .this )
205+ )
206+
207+ # dateadd(week, 1-(WEEKISO(date)), trunc_iso_week)
208+ if unit == "isoyear" :
209+ return self .func (
210+ "dateadd" ,
211+ exp .Var (this = "week" ),
212+ exp .Sub (
213+ this = exp .Literal .number (1 ),
214+ expression = self .func ("WEEKISO" , expression .this )
215+ ),
216+ trunc_iso_week
217+ )
218+
219+ # duckdb week means "isoweek"
220+ if unit == "week" :
221+ return trunc_iso_week
222+
223+ return self .func ("DATE_TRUNC" , expression .unit , expression .this )
224+
225+
226+ SnowflakeDialect .Generator .TRANSFORMS [exp .Extract ] = _snowflake_extract_sql
227+ SnowflakeDialect .Generator .TRANSFORMS [exp .TimeToStr ] = _snowflake_time_to_str
228+ SnowflakeDialect .Generator .TRANSFORMS [exp .StrToTime ] = _snowflake_str_to_time_sql
229+ SnowflakeDialect .Generator .TRANSFORMS [exp .TimestampTrunc ] = _snowflake_timestamp_trunc_sql
0 commit comments