-
Notifications
You must be signed in to change notification settings - Fork 14.7k
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
[SIP-15] Fixing datetime conversion and SQL literal #8464
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -439,15 +439,15 @@ def _allowed_file(filename: str) -> bool: | |
db.session.commit() | ||
|
||
@classmethod | ||
def convert_dttm(cls, target_type: str, dttm: datetime) -> str: | ||
def convert_dttm(cls, target_type: str, dttm: datetime) -> Optional[str]: | ||
""" | ||
Convert DateTime object to sql expression | ||
Convert Python datetime object to a SQL expression | ||
|
||
:param target_type: Target type of expression | ||
:param dttm: DateTime object | ||
:return: SQL expression | ||
:param target_type: The target type of expression | ||
:param dttm: The datetime object | ||
:return: The SQL expression | ||
""" | ||
return "'{}'".format(dttm.strftime("%Y-%m-%d %H:%M:%S")) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is now handled in |
||
return None | ||
|
||
@classmethod | ||
def get_all_datasource_names( | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,7 +17,7 @@ | |
import hashlib | ||
import re | ||
from datetime import datetime | ||
from typing import Any, Dict, List, Tuple | ||
from typing import Any, Dict, List, Optional, Tuple | ||
|
||
import pandas as pd | ||
from sqlalchemy import literal_column | ||
|
@@ -72,11 +72,15 @@ class BigQueryEngineSpec(BaseEngineSpec): | |
} | ||
|
||
@classmethod | ||
def convert_dttm(cls, target_type: str, dttm: datetime) -> str: | ||
def convert_dttm(cls, target_type: str, dttm: datetime) -> Optional[str]: | ||
tt = target_type.upper() | ||
if tt == "DATE": | ||
return "'{}'".format(dttm.strftime("%Y-%m-%d")) | ||
return "'{}'".format(dttm.strftime("%Y-%m-%d %H:%M:%S")) | ||
return f"CAST('{dttm.date().isoformat()}' AS DATE)" | ||
if tt == "DATETIME": | ||
return f"""CAST('{dttm.isoformat(timespec="microseconds")}' AS DATETIME)""" | ||
if tt == "TIMESTAMP": | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @betodealmeida do you know why the |
||
return f"""CAST('{dttm.isoformat(timespec="microseconds")}' AS TIMESTAMP)""" | ||
return None | ||
|
||
@classmethod | ||
def fetch_data(cls, cursor, limit: int) -> List[Tuple]: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,6 +15,7 @@ | |
# specific language governing permissions and limitations | ||
# under the License. | ||
from datetime import datetime | ||
from typing import Optional | ||
from urllib import parse | ||
|
||
from superset.db_engine_specs.base import BaseEngineSpec | ||
|
@@ -49,13 +50,13 @@ def epoch_ms_to_dttm(cls) -> str: | |
return "TO_DATE({col})" | ||
|
||
@classmethod | ||
def convert_dttm(cls, target_type: str, dttm: datetime) -> str: | ||
def convert_dttm(cls, target_type: str, dttm: datetime) -> Optional[str]: | ||
tt = target_type.upper() | ||
if tt == "DATE": | ||
return "CAST('{}' AS DATE)".format(dttm.isoformat()[:10]) | ||
return f"TO_DATE('{dttm.date().isoformat()}', 'yyyy-MM-dd')" | ||
elif tt == "TIMESTAMP": | ||
return "CAST('{}' AS TIMESTAMP)".format(dttm.strftime("%Y-%m-%d %H:%M:%S")) | ||
return "'{}'".format(dttm.strftime("%Y-%m-%d %H:%M:%S")) | ||
return f"""TO_TIMESTAMP('{dttm.isoformat(sep=" ", timespec="seconds")}', 'yyyy-MM-dd HH:mm:ss')""" # pylint: disable=line-too-long | ||
return None | ||
Comment on lines
-52
to
+59
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @cgivre can you check if these are valid and if there's anything missing? A quick googling didn't turn up any There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will do |
||
|
||
@classmethod | ||
def adjust_database_uri(cls, uri, selected_schema): | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,7 +16,7 @@ | |
# under the License. | ||
# pylint: disable=C,R,W | ||
from datetime import datetime | ||
from typing import Dict | ||
from typing import Dict, Optional | ||
|
||
from superset.db_engine_specs.base import BaseEngineSpec | ||
|
||
|
@@ -41,7 +41,7 @@ class ElasticSearchEngineSpec(BaseEngineSpec): | |
type_code_map: Dict[int, str] = {} # loaded from get_datatype only if needed | ||
|
||
@classmethod | ||
def convert_dttm(cls, target_type: str, dttm: datetime) -> str: | ||
if target_type.upper() in ("DATETIME", "DATE"): | ||
return f"'{dttm.isoformat()}'" | ||
return f"'{dttm.strftime('%Y-%m-%d %H:%M:%S')}'" | ||
def convert_dttm(cls, target_type: str, dttm: datetime) -> Optional[str]: | ||
if target_type.upper() == "DATETIME": | ||
return f"""CAST('{dttm.isoformat(timespec="seconds")}' AS DATETIME)""" | ||
return None | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @john-bodley thanks for pointing out to this, the logic looks good and I've retested it and found no problems |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -50,8 +50,15 @@ def epoch_to_dttm(cls): | |
return "dateadd(S, {col}, '1970-01-01')" | ||
|
||
@classmethod | ||
def convert_dttm(cls, target_type: str, dttm: datetime) -> str: | ||
return "CONVERT(DATETIME, '{}', 126)".format(dttm.isoformat()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We shouldn't be casting all types to a |
||
def convert_dttm(cls, target_type: str, dttm: datetime) -> Optional[str]: | ||
tt = target_type.upper() | ||
if tt == "DATE": | ||
return f"CONVERT(DATE, '{dttm.date().isoformat()}', 23)" | ||
if tt == "DATETIME": | ||
return f"""CONVERT(DATETIME, '{dttm.isoformat(timespec="milliseconds")}', 126)""" # pylint: disable=line-too-long | ||
if tt == "SMALLDATETIME": | ||
return f"""CONVERT(SMALLDATETIME, '{dttm.isoformat(sep=" ", timespec="seconds")}', 20)""" # pylint: disable=line-too-long | ||
return None | ||
|
||
@classmethod | ||
def fetch_data(cls, cursor, limit: int) -> List[Tuple]: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,6 +15,7 @@ | |
# specific language governing permissions and limitations | ||
# under the License. | ||
from datetime import datetime | ||
from typing import Optional | ||
|
||
from superset.db_engine_specs.base import LimitMethod | ||
from superset.db_engine_specs.postgres import PostgresBaseEngineSpec | ||
|
@@ -39,7 +40,10 @@ class OracleEngineSpec(PostgresBaseEngineSpec): | |
} | ||
|
||
@classmethod | ||
def convert_dttm(cls, target_type: str, dttm: datetime) -> str: | ||
return ("""TO_TIMESTAMP('{}', 'YYYY-MM-DD"T"HH24:MI:SS.ff6')""").format( | ||
dttm.isoformat() | ||
) | ||
def convert_dttm(cls, target_type: str, dttm: datetime) -> Optional[str]: | ||
tt = target_type.upper() | ||
if tt == "DATE": | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note there was no previous logic for handling the |
||
return f"TO_DATE('{dttm.date().isoformat()}', 'YYYY-MM-DD')" | ||
if tt == "TIMESTAMP": | ||
return f"""TO_TIMESTAMP('{dttm.isoformat(timespec="microseconds")}', 'YYYY-MM-DD"T"HH24:MI:SS.ff6')""" # pylint: disable=line-too-long | ||
return None |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -75,11 +75,10 @@ def get_all_datasource_names( | |
raise Exception(f"Unsupported datasource_type: {datasource_type}") | ||
|
||
@classmethod | ||
def convert_dttm(cls, target_type: str, dttm: datetime) -> str: | ||
iso = dttm.isoformat().replace("T", " ") | ||
if "." not in iso: | ||
iso += ".000000" | ||
return "'{}'".format(iso) | ||
def convert_dttm(cls, target_type: str, dttm: datetime) -> Optional[str]: | ||
if target_type.upper() == "TEXT": | ||
return f"""'{dttm.isoformat(sep=" ", timespec="microseconds")}'""" | ||
return None | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. SQLite also supports time stored in REAL and INTEGER columns according to the docs:
Should we support the REAL and INTEGER conversions here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @willbarrett epoch is handled by specifying Also a Note per the referenced PoC PR the future end goal is to provide an engine specific graph which maps between various SQLAlchemy and |
||
|
||
@classmethod | ||
def get_table_names( | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We shouldn't be casting other types to
TIMESTAMP
. The reason for this function is to ensure that the LHS and RHS for the time filter comparison are equivalent types.