1
1
from __future__ import annotations
2
2
3
3
import math
4
+ import sqlite3
4
5
5
6
import sqlglot as sg
6
7
import sqlglot .expressions as sge
@@ -19,6 +20,8 @@ class SQLiteCompiler(SQLGlotCompiler):
19
20
20
21
dialect = SQLite
21
22
type_mapper = SQLiteType
23
+ supports_time_shift_modifiers = sqlite3 .sqlite_version_info >= (3 , 46 , 0 )
24
+ supports_subsec = sqlite3 .sqlite_version_info >= (3 , 42 , 0 )
22
25
23
26
# We could set `supports_order_by=True` for SQLite >= 3.44.0 (2023-11-01).
24
27
agg = AggGen (supports_filter = True )
@@ -53,10 +56,7 @@ class SQLiteCompiler(SQLGlotCompiler):
53
56
ops .IntervalSubtract ,
54
57
ops .IntervalMultiply ,
55
58
ops .IntervalFloorDivide ,
56
- ops .IntervalFromInteger ,
57
59
ops .TimestampBucket ,
58
- ops .TimestampAdd ,
59
- ops .TimestampSub ,
60
60
ops .TimestampDiff ,
61
61
ops .StringToDate ,
62
62
ops .StringToTimestamp ,
@@ -333,18 +333,65 @@ def visit_TimestampTruncate(self, op, *, arg, unit):
333
333
return self ._temporal_truncate (self .f .anon .datetime , arg , unit )
334
334
335
335
def visit_DateArithmetic (self , op , * , left , right ):
336
- unit = op .right .dtype .unit
337
- sign = "+" if isinstance (op , ops .DateAdd ) else "-"
338
- if unit not in (IntervalUnit .YEAR , IntervalUnit .MONTH , IntervalUnit .DAY ):
336
+ right = right .this
337
+
338
+ if (unit := op .right .dtype .unit ) in (
339
+ IntervalUnit .QUARTER ,
340
+ IntervalUnit .MICROSECOND ,
341
+ IntervalUnit .NANOSECOND ,
342
+ ):
339
343
raise com .UnsupportedOperationError (
340
- "SQLite does not allow binary op {sign!r} with INTERVAL offset {unit} "
344
+ f "SQLite does not support ` { unit } ` units in temporal arithmetic "
341
345
)
342
- if isinstance (op .right , ops .Literal ):
343
- return self .f .date (left , f"{ sign } { op .right .value } { unit .plural } " )
346
+ elif unit == IntervalUnit .WEEK :
347
+ unit = IntervalUnit .DAY
348
+ right *= 7
349
+ elif unit == IntervalUnit .MILLISECOND :
350
+ # sqlite doesn't allow milliseconds, so divide milliseconds by 1e3 to
351
+ # get seconds, and change the unit to seconds
352
+ unit = IntervalUnit .SECOND
353
+ right /= 1e3
354
+
355
+ # compute whether we're adding or subtracting an interval
356
+ sign = "+" if isinstance (op , (ops .DateAdd , ops .TimestampAdd )) else "-"
357
+
358
+ modifiers = []
359
+
360
+ # floor the result if the unit is a year, month, or day to match other
361
+ # backend behavior
362
+ if unit in (IntervalUnit .YEAR , IntervalUnit .MONTH , IntervalUnit .DAY ):
363
+ if not self .supports_time_shift_modifiers :
364
+ raise com .UnsupportedOperationError (
365
+ "SQLite does not support time shift modifiers until version 3.46; "
366
+ f"found version { sqlite3 .sqlite_version } "
367
+ )
368
+ modifiers .append ("floor" )
369
+
370
+ if isinstance (op , (ops .TimestampAdd , ops .TimestampSub )):
371
+ # if the left operand is a timestamp, return as much precision as
372
+ # possible
373
+ if not self .supports_subsec :
374
+ raise com .UnsupportedOperationError (
375
+ "SQLite does not support subsecond resolution until version 3.42; "
376
+ f"found version { sqlite3 .sqlite_version } "
377
+ )
378
+ func = self .f .datetime
379
+ modifiers .append ("subsec" )
344
380
else :
345
- return self .f .date (left , self .f .concat (sign , right , f" { unit .plural } " ))
381
+ func = self .f .date
382
+
383
+ return func (
384
+ left ,
385
+ self .f .concat (
386
+ sign , self .cast (right , dt .string ), " " , unit .singular .lower ()
387
+ ),
388
+ * modifiers ,
389
+ dialect = self .dialect ,
390
+ )
346
391
347
- visit_DateAdd = visit_DateSub = visit_DateArithmetic
392
+ visit_TimestampAdd = visit_TimestampSub = visit_DateAdd = visit_DateSub = (
393
+ visit_DateArithmetic
394
+ )
348
395
349
396
def visit_DateDiff (self , op , * , left , right ):
350
397
return self .f .julianday (left ) - self .f .julianday (right )
0 commit comments