Skip to content

Commit 92ee8c9

Browse files
committed
Do not use parameter for EXTRACT field
Closes #97
1 parent a0ec4f0 commit 92ee8c9

3 files changed

Lines changed: 97 additions & 4 deletions

File tree

CHANGELOG

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
* Do not use parameter for EXTRACT field
12
* Remove support for Python older than 3.6
23

34
Version 1.7.0 - 2025-11-24

sql/functions.py

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# This file is part of python-sql. The COPYRIGHT file at the top level of
22
# this repository contains the full copyright notices and license terms.
3+
4+
from enum import Enum, auto
35
from itertools import chain
46

57
from sql import CombiningQuery, Expression, Flavor, FromItem, Select, Window
@@ -85,7 +87,7 @@ def __str__(self):
8587
return (self._function + '('
8688
+ ' '.join(chain(*zip(
8789
self._keywords,
88-
map(self._format, self.args))))[1:]
90+
map(self._format, self.args)))).strip()
8991
+ ')')
9092

9193

@@ -383,9 +385,67 @@ class DateTrunc(Function):
383385

384386

385387
class Extract(FunctionKeyword):
386-
__slots__ = ()
388+
__slots__ = ('_field',)
387389
_function = 'EXTRACT'
388-
_keywords = ('', 'FROM')
390+
391+
class Fields(str, Enum):
392+
def _generate_next_value_(name, start, count, last_values):
393+
return name.upper()
394+
395+
CENTURY = auto()
396+
DAY = auto()
397+
DECADE = auto()
398+
DOW = auto()
399+
DOY = auto()
400+
EPOCH = auto()
401+
HOUR = auto()
402+
ISODOW = auto()
403+
ISOYEAR = auto()
404+
JULIAN = auto()
405+
MICROSECONDS = auto()
406+
MILLENNIUM = auto()
407+
MILLISECONDS = auto()
408+
MINUTE = auto()
409+
MONTH = auto()
410+
QUARTER = auto()
411+
SECOND = auto()
412+
TIMEZONE = auto()
413+
TIMEZONE_HOUR = auto()
414+
TIMEZONE_MINUTE = auto()
415+
WEEK = auto()
416+
YEAR = auto()
417+
418+
def __init__(self, field, *args, **kwargs):
419+
super().__init__(*args, **kwargs)
420+
self.field = field
421+
422+
@property
423+
def field(self):
424+
return self._field
425+
426+
@field.setter
427+
def field(self, value):
428+
value = value.upper()
429+
if not hasattr(self.Fields, value):
430+
raise ValueError("invalid field: %r" % value)
431+
self._field = value
432+
433+
@property
434+
def _keywords(self):
435+
return ('%s FROM' % self.field,)
436+
437+
def __str__(self):
438+
Mapping = Flavor.get().function_mapping.get(self.__class__)
439+
if Mapping:
440+
return str(Mapping(self.field, *self.args))
441+
return super().__str__()
442+
443+
@property
444+
def params(self):
445+
Mapping = Flavor.get().function_mapping.get(self.__class__)
446+
if Mapping:
447+
return Mapping(self.field, *self.args).params
448+
return super().params
389449

390450

391451
class Isfinite(Function):

sql/tests/test_functions.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from sql import AliasManager, Flavor, Table, Window
66
from sql.functions import (
7-
Abs, AtTimeZone, CurrentTime, Div, Function, FunctionKeyword,
7+
Abs, AtTimeZone, CurrentTime, Div, Extract, Function, FunctionKeyword,
88
FunctionNotCallable, Overlay, Rank, Trim, WindowFunction)
99

1010

@@ -139,6 +139,38 @@ def test_current_time(self):
139139
self.assertEqual(str(current_time), 'CURRENT_TIME')
140140
self.assertEqual(current_time.params, ())
141141

142+
def test_extract(self):
143+
extract = Extract(Extract.Fields.DAY, self.table.c)
144+
self.assertEqual(str(extract), 'EXTRACT(DAY FROM "c")')
145+
self.assertEqual(extract.params, ())
146+
147+
extract = Extract('day', self.table.c)
148+
self.assertEqual(str(extract), 'EXTRACT(DAY FROM "c")')
149+
self.assertEqual(extract.params, ())
150+
151+
extract = Extract(Extract.Fields.DAY, '2000-01-01')
152+
self.assertEqual(str(extract), 'EXTRACT(DAY FROM %s)')
153+
self.assertEqual(extract.params, ('2000-01-01',))
154+
155+
def test_extract_mapping(self):
156+
class MyExtract(Function):
157+
_function = 'MY_EXTRACT'
158+
159+
extract = Extract(Extract.Fields.DAY, '2000-01-01')
160+
flavor = Flavor(function_mapping={
161+
Extract: MyExtract,
162+
})
163+
Flavor.set(flavor)
164+
try:
165+
self.assertEqual(str(extract), 'MY_EXTRACT(%s, %s)')
166+
self.assertEqual(extract.params, ('DAY', '2000-01-01'))
167+
finally:
168+
Flavor.set(Flavor())
169+
170+
def test_extract_invalid_field(self):
171+
with self.assertRaises(ValueError):
172+
Extract('foo', self.table.c)
173+
142174

143175
class TestWindowFunction(unittest.TestCase):
144176

0 commit comments

Comments
 (0)