From 002b27dbddcefb4c6e63611c5d2637556b3b157a Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 23 Sep 2022 15:46:31 +0530 Subject: [PATCH] fix: pypika does not parse datetime.time (backport #18184) (#18205) * fix: pypika does not parse datetime.time (cherry picked from commit b8f5a4304cccda954822cfe69d4805a18d9f07a7) * fix: style - imports sort order fixed (cherry picked from commit c3562c643ab9bff38d230202675d8ce1ae190912) * test: add test for query builder parsing datetime.time (cherry picked from commit 99889c270199da4b26b0d3858ae0f0a416d49d90) * fix: format_time instead of format_datetime, test was failing! (cherry picked from commit 5d697a22ac8cb98c8f8f44b3f99c57b932c5223c) * fix(style): linter issues (cherry picked from commit e231e1b0eadf63e84c9f81cfed9e2926e086fc44) * test: add test for postgres (cherry picked from commit 695591c43af9672cb83ce856a46c97e0bddfa9c3) * fix: converting datepart and timepart to strings for Combinedatetime (cherry picked from commit 1ebda943a607688bfb3db9fb42c80723a9050973) * fix: style, linter issues (cherry picked from commit c01262ad02b8831fdb0c5332b6973499e8adf6ad) Co-authored-by: Anoop Kurungadam Co-authored-by: Aradhya --- frappe/query_builder/functions.py | 4 +++ frappe/query_builder/terms.py | 9 ++++--- frappe/tests/test_query_builder.py | 43 ++++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 3 deletions(-) diff --git a/frappe/query_builder/functions.py b/frappe/query_builder/functions.py index e725dff828..24e2ee0e5f 100644 --- a/frappe/query_builder/functions.py +++ b/frappe/query_builder/functions.py @@ -1,3 +1,4 @@ +from datetime import time from enum import Enum from pypika.functions import * @@ -47,6 +48,9 @@ Match = ImportMapper({db_type_is.MARIADB: MATCH, db_type_is.POSTGRES: TO_TSVECTO class _PostgresTimestamp(ArithmeticExpression): def __init__(self, datepart, timepart, alias=None): + """Postgres would need both datepart and timepart to be a string for concatenation""" + if isinstance(timepart, time) or isinstance(datepart, time): + timepart, datepart = str(timepart), str(datepart) if isinstance(datepart, str): datepart = Cast(datepart, "date") if isinstance(timepart, str): diff --git a/frappe/query_builder/terms.py b/frappe/query_builder/terms.py index 4d3c98b6a3..c1af0d9aa2 100644 --- a/frappe/query_builder/terms.py +++ b/frappe/query_builder/terms.py @@ -1,11 +1,11 @@ -from datetime import timedelta +from datetime import time, timedelta from typing import Any from pypika.queries import QueryBuilder from pypika.terms import Criterion, Function, ValueWrapper from pypika.utils import format_alias_sql -from frappe.utils.data import format_timedelta +from frappe.utils.data import format_time, format_timedelta class NamedParameterWrapper: @@ -55,9 +55,12 @@ class ParameterizedValueWrapper(ValueWrapper): value_sql = self.get_value_sql(quote_char=quote_char, **kwargs) sql = param_wrapper.get_sql(param_value=value_sql, **kwargs) else: - # * BUG: pypika doesen't parse timedeltas + # * BUG: pypika doesen't parse timedeltas and datetime.time if isinstance(self.value, timedelta): self.value = format_timedelta(self.value) + elif isinstance(self.value, time): + self.value = format_time(self.value) + sql = self.get_value_sql( quote_char=quote_char, secondary_quote_char=secondary_quote_char, diff --git a/frappe/tests/test_query_builder.py b/frappe/tests/test_query_builder.py index 06f2f5f6a7..b80e3ad8e2 100644 --- a/frappe/tests/test_query_builder.py +++ b/frappe/tests/test_query_builder.py @@ -1,5 +1,6 @@ import unittest from collections.abc import Callable +from datetime import time import frappe from frappe.query_builder import Case @@ -74,6 +75,26 @@ class TestCustomFunctionsMariaDB(FrappeTestCase): str(select_query).lower(), ) + def test_time(self): + note = frappe.qb.DocType("Note") + self.assertEqual( + "TIMESTAMP('2021-01-01','00:00:21')", CombineDatetime("2021-01-01", time(0, 0, 21)).get_sql() + ) + + select_query = frappe.qb.from_(note).select( + CombineDatetime(note.posting_date, note.posting_time) + ) + self.assertIn("select timestamp(`posting_date`,`posting_time`)", str(select_query).lower()) + + select_query = select_query.where( + CombineDatetime(note.posting_date, note.posting_time) + >= CombineDatetime("2021-01-01", time(0, 0, 1)) + ) + self.assertIn( + "timestamp(`posting_date`,`posting_time`)>=timestamp('2021-01-01','00:00:01')", + str(select_query).lower(), + ) + def test_cast(self): note = frappe.qb.DocType("Note") self.assertEqual("CONCAT(name,'')", Cast_(note.name, "varchar").get_sql()) @@ -141,6 +162,28 @@ class TestCustomFunctionsPostgres(FrappeTestCase): '"tabnote"."posting_date"+"tabnote"."posting_time" "timestamp"', str(select_query).lower() ) + def test_time(self): + note = frappe.qb.DocType("Note") + + self.assertEqual( + "CAST('2021-01-01' AS DATE)+CAST('00:00:21' AS TIME)", + CombineDatetime("2021-01-01", time(0, 0, 21)).get_sql(), + ) + + select_query = frappe.qb.from_(note).select( + CombineDatetime(note.posting_date, note.posting_time) + ) + self.assertIn('select "posting_date"+"posting_time"', str(select_query).lower()) + + select_query = select_query.where( + CombineDatetime(note.posting_date, note.posting_time) + >= CombineDatetime("2021-01-01", time(0, 0, 1)) + ) + self.assertIn( + """where "posting_date"+"posting_time">=cast('2021-01-01' as date)+cast('00:00:01' as time)""", + str(select_query).lower(), + ) + def test_cast(self): note = frappe.qb.DocType("Note") self.assertEqual("CAST(name AS VARCHAR)", Cast_(note.name, "varchar").get_sql())