From 69d37d8b2a8de4750b2949905a6e05b28cdf88a6 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Sat, 17 Sep 2016 21:30:50 +0200 Subject: [PATCH] Fix double escaping of dttm expressions (#744) (#1103) If ddtm_expr is an expression with special characters then timestamp_grain escapes the special characters already escaped. Solution discussed with sqlalchemy upstream: https://bitbucket.org/zzzeek/sqlalchemy/issues/3737/literal_column-given-a-specific-sql Fix #617 --- caravel/models.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/caravel/models.py b/caravel/models.py index 1ca7483a3f..785ebd8a4e 100644 --- a/caravel/models.py +++ b/caravel/models.py @@ -39,10 +39,11 @@ from sqlalchemy import ( DateTime, Date, Table, Numeric, create_engine, MetaData, desc, asc, select, and_, func ) +from sqlalchemy.ext.compiler import compiles from sqlalchemy.ext.declarative import declared_attr from sqlalchemy.orm import relationship from sqlalchemy.sql import table, literal_column, text, column -from sqlalchemy.sql.expression import TextAsFrom +from sqlalchemy.sql.expression import ColumnClause, TextAsFrom from sqlalchemy_utils import EncryptedType from werkzeug.datastructures import ImmutableMultiDict @@ -805,6 +806,22 @@ class SqlaTable(Model, Queryable, AuditMixinNullable): metrics_exprs = [] if granularity: + + # TODO: sqlalchemy 1.2 release should be doing this on its own. + # Patch only if the column clause is specific for DateTime set and + # granularity is selected. + @compiles(ColumnClause) + def _(element, compiler, **kw): + text = compiler.visit_column(element, **kw) + try: + if element.is_literal and hasattr(element.type, 'python_type') and \ + type(element.type) is DateTime: + + text = text.replace('%%', '%') + except NotImplementedError: + pass # Some elements raise NotImplementedError for python_type + return text + dttm_col = cols[granularity] dttm_expr = dttm_col.sqla_col.label('timestamp') timestamp = dttm_expr @@ -820,7 +837,7 @@ class SqlaTable(Model, Queryable, AuditMixinNullable): col=dttm_expr) udf = self.database.grains_dict().get(time_grain_sqla, '{col}') timestamp_grain = literal_column( - udf.function.format(col=dttm_expr)).label('timestamp') + udf.function.format(col=dttm_expr), type_=DateTime).label('timestamp') else: timestamp_grain = timestamp