Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

prototype for Extract #857

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
32 changes: 32 additions & 0 deletions piccolo/columns/column_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ class Band(Table):

if t.TYPE_CHECKING: # pragma: no cover
from piccolo.columns.base import ColumnMeta
from piccolo.custom_types import ExtractField
from piccolo.query.methods.select import Extract
from piccolo.table import Table


Expand Down Expand Up @@ -928,6 +930,36 @@ def __init__(
kwargs.update({"default": default})
super().__init__(**kwargs)

###########################################################################
# Extract part of the timestamp in a query (e.g. the year).

def extract(self, field: ExtractField) -> Extract:
"""
Used for extracting part of a timestamp. For example::

>>> await Concert.select(Concert.starts.extract('year')).distinct()

Or alternatively, use this convenience method::

>>> await Concert.select(Concert.starts.year).distinct()

"""
from piccolo.query.methods.select import Extract

return Extract(column=self, field=field)

@property
def year(self) -> Extract:
return self.extract(field="year")

@property
def month(self) -> Extract:
return self.extract(field="month")

@property
def day(self) -> Extract:
return self.extract(field="day")

###########################################################################
# For update queries

Expand Down
27 changes: 27 additions & 0 deletions piccolo/custom_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import typing as t

from typing_extensions import Literal

if t.TYPE_CHECKING: # pragma: no cover
from piccolo.columns.combination import And, Or, Where, WhereRaw # noqa
from piccolo.table import Table
Expand All @@ -14,6 +16,31 @@
TableInstance = t.TypeVar("TableInstance", bound="Table")
QueryResponseType = t.TypeVar("QueryResponseType", bound=t.Any)

ExtractField = Literal[
"century",
"day",
"decade",
"dow",
"doy",
"epoch",
"hour",
"isodow",
"isoyear",
"julian",
"microseconds",
"millennium",
"milliseconds",
"minute",
"month",
"quarter",
"second",
"timezone",
"timezone_hour",
"timezone_minute",
"week",
"year",
]


###############################################################################
# For backwards compatibility:
Expand Down
25 changes: 24 additions & 1 deletion piccolo/query/methods/select.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
from piccolo.utils.warnings import colored_warning

if t.TYPE_CHECKING: # pragma: no cover
from piccolo.custom_types import Combinable
from piccolo.custom_types import Combinable, ExtractField
from piccolo.table import Table # noqa


Expand Down Expand Up @@ -63,6 +63,29 @@ def get_select_string(
return self.querystring.__str__()


class Extract(Selectable):
def __init__(
self,
column: Column,
field: ExtractField,
alias: t.Optional[str] = None,
):
self.column = column
self.field = field
self._alias = alias

def get_select_string(
self, engine_type: str, with_alias: bool = True
) -> str:
# TODO - need to use strftime for SQLite
column_name = self.column._meta.get_full_name(with_alias=False)
field = self.field
alias = self._alias or field
return f'EXTRACT({field} FROM {column_name}) AS "{alias}"'

# TODO - proxy methods like >, ==, != to the underlying column


class Avg(Selectable):
"""
``AVG()`` SQL function. Column type must be numeric to run the query.
Expand Down