From 86a63d6268a1165789beba5dbde16f603f74b796 Mon Sep 17 00:00:00 2001 From: dacopan Date: Wed, 26 Jun 2024 18:16:23 -0500 Subject: [PATCH 1/3] feat: add support to NOT LIKE operator --- superset/models/helpers.py | 59 ++++++++++++++++++++++---------------- superset/utils/core.py | 1 + 2 files changed, 35 insertions(+), 25 deletions(-) diff --git a/superset/models/helpers.py b/superset/models/helpers.py index 7b211f98b11aa..6b2df48f2e9bf 100644 --- a/superset/models/helpers.py +++ b/superset/models/helpers.py @@ -103,7 +103,6 @@ from superset.db_engine_specs import BaseEngineSpec from superset.models.core import Database - config = app.config logger = logging.getLogger(__name__) @@ -200,7 +199,8 @@ def _unique_constraints(cls) -> list[set[str]]: for u in cls.__table_args__ # type: ignore if isinstance(u, UniqueConstraint) ] - unique.extend({c.name} for c in cls.__table__.columns if c.unique) # type: ignore + unique.extend( + {c.name} for c in cls.__table__.columns if c.unique) # type: ignore return unique @classmethod @@ -1028,7 +1028,7 @@ def assign_column_label(df: pd.DataFrame) -> Optional[pd.DataFrame]: _("Db engine did not return all queried columns") ) if len(df.columns) > len(labels_expected): - df = df.iloc[:, 0 : len(labels_expected)] + df = df.iloc[:, 0: len(labels_expected)] df.columns = labels_expected return df @@ -1200,9 +1200,9 @@ def handle_single_value(value: Optional[FilterValue]) -> Optional[FilterValue]: target_generic_type == utils.GenericDataType.NUMERIC and operator not in { - utils.FilterOperator.ILIKE, - utils.FilterOperator.LIKE, - } + utils.FilterOperator.ILIKE, + utils.FilterOperator.LIKE, + } ): # For backwards compatibility and edge cases # where a column data type might have changed @@ -1448,7 +1448,8 @@ def convert_tbl_column_to_sqla_col( col = self.make_sqla_column_compatible(col, label) return col - def get_sqla_query( # pylint: disable=too-many-arguments,too-many-locals,too-many-branches,too-many-statements + def get_sqla_query( + # pylint: disable=too-many-arguments,too-many-locals,too-many-branches,too-many-statements self, apply_fetch_values_predicate: bool = False, columns: Optional[list[Column]] = None, @@ -1819,8 +1820,8 @@ def get_sqla_query( # pylint: disable=too-many-arguments,too-many-locals,too-ma if ( col_advanced_data_type != "" and feature_flag_manager.is_feature_enabled( - "ENABLE_ADVANCED_DATA_TYPES" - ) + "ENABLE_ADVANCED_DATA_TYPES" + ) and col_advanced_data_type in ADVANCED_DATA_TYPES ): values = eq if is_list_target else [eq] # type: ignore @@ -1873,9 +1874,9 @@ def get_sqla_query( # pylint: disable=too-many-arguments,too-many-locals,too-ma if ( op not in { - utils.FilterOperator.EQUALS.value, - utils.FilterOperator.NOT_EQUALS.value, - } + utils.FilterOperator.EQUALS.value, + utils.FilterOperator.NOT_EQUALS.value, + } and eq is None ): raise QueryObjectValidationError( @@ -1907,6 +1908,13 @@ def get_sqla_query( # pylint: disable=too-many-arguments,too-many-locals,too-ma where_clause_and.append(sqla_col.like(eq)) else: where_clause_and.append(sqla_col.ilike(eq)) + elif op in { + utils.FilterOperator.NOT_LIKE.value + }: + if target_generic_type != GenericDataType.STRING: + sqla_col = sa.cast(sqla_col, sa.String) + + where_clause_and.append(sqla_col.not_like(eq)) elif ( op == utils.FilterOperator.TEMPORAL_RANGE.value and isinstance(eq, str) @@ -2112,21 +2120,22 @@ def get_sqla_query( # pylint: disable=too-many-arguments,too-many-locals,too-ma filter_columns = [flt.get("col") for flt in filter] if filter else [] rejected_filter_columns = [ - col - for col in filter_columns - if col - and not is_adhoc_column(col) - and col not in self.column_names - and col not in applied_template_filters - ] + rejected_adhoc_filters_columns + col + for col in filter_columns + if col + and not is_adhoc_column(col) + and col not in self.column_names + and col not in applied_template_filters + ] + rejected_adhoc_filters_columns applied_filter_columns = [ - col - for col in filter_columns - if col - and not is_adhoc_column(col) - and (col in self.column_names or col in applied_template_filters) - ] + applied_adhoc_filters_columns + col + for col in filter_columns + if col + and not is_adhoc_column(col) + and ( + col in self.column_names or col in applied_template_filters) + ] + applied_adhoc_filters_columns return SqlaQuery( applied_template_filters=applied_template_filters, diff --git a/superset/utils/core.py b/superset/utils/core.py index ee5e451f3eb76..d01dd517efbac 100644 --- a/superset/utils/core.py +++ b/superset/utils/core.py @@ -217,6 +217,7 @@ class FilterOperator(StrEnum): GREATER_THAN_OR_EQUALS = ">=" LESS_THAN_OR_EQUALS = "<=" LIKE = "LIKE" + NOT_LIKE = "NOT LIKE" ILIKE = "ILIKE" IS_NULL = "IS NULL" IS_NOT_NULL = "IS NOT NULL" From 56b6b021ad404caf4c47a87bd48a237bdff119d9 Mon Sep 17 00:00:00 2001 From: dacopan Date: Wed, 26 Jun 2024 18:19:40 -0500 Subject: [PATCH 2/3] feat: add support to NOT LIKE operator --- .../packages/superset-ui-core/src/query/types/Operator.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/superset-frontend/packages/superset-ui-core/src/query/types/Operator.ts b/superset-frontend/packages/superset-ui-core/src/query/types/Operator.ts index 5b598312c0bce..0d2cb5b59ba7f 100644 --- a/superset-frontend/packages/superset-ui-core/src/query/types/Operator.ts +++ b/superset-frontend/packages/superset-ui-core/src/query/types/Operator.ts @@ -30,6 +30,7 @@ const BINARY_OPERATORS = [ '<=', 'ILIKE', 'LIKE', + 'NOT LIKE', 'REGEX', 'TEMPORAL_RANGE', ] as const; From 606045c1c1edb26941632cc66702c4ba56b15148 Mon Sep 17 00:00:00 2001 From: dacopan Date: Mon, 1 Jul 2024 21:24:05 -0500 Subject: [PATCH 3/3] style: reformat using ruff and pylint --- superset/models/helpers.py | 56 ++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 30 deletions(-) diff --git a/superset/models/helpers.py b/superset/models/helpers.py index 6b2df48f2e9bf..e2cca8003703b 100644 --- a/superset/models/helpers.py +++ b/superset/models/helpers.py @@ -103,6 +103,7 @@ from superset.db_engine_specs import BaseEngineSpec from superset.models.core import Database + config = app.config logger = logging.getLogger(__name__) @@ -199,8 +200,7 @@ def _unique_constraints(cls) -> list[set[str]]: for u in cls.__table_args__ # type: ignore if isinstance(u, UniqueConstraint) ] - unique.extend( - {c.name} for c in cls.__table__.columns if c.unique) # type: ignore + unique.extend({c.name} for c in cls.__table__.columns if c.unique) # type: ignore return unique @classmethod @@ -1028,7 +1028,7 @@ def assign_column_label(df: pd.DataFrame) -> Optional[pd.DataFrame]: _("Db engine did not return all queried columns") ) if len(df.columns) > len(labels_expected): - df = df.iloc[:, 0: len(labels_expected)] + df = df.iloc[:, 0 : len(labels_expected)] df.columns = labels_expected return df @@ -1200,9 +1200,9 @@ def handle_single_value(value: Optional[FilterValue]) -> Optional[FilterValue]: target_generic_type == utils.GenericDataType.NUMERIC and operator not in { - utils.FilterOperator.ILIKE, - utils.FilterOperator.LIKE, - } + utils.FilterOperator.ILIKE, + utils.FilterOperator.LIKE, + } ): # For backwards compatibility and edge cases # where a column data type might have changed @@ -1448,8 +1448,7 @@ def convert_tbl_column_to_sqla_col( col = self.make_sqla_column_compatible(col, label) return col - def get_sqla_query( - # pylint: disable=too-many-arguments,too-many-locals,too-many-branches,too-many-statements + def get_sqla_query( # pylint: disable=too-many-arguments,too-many-locals,too-many-branches,too-many-statements self, apply_fetch_values_predicate: bool = False, columns: Optional[list[Column]] = None, @@ -1820,8 +1819,8 @@ def get_sqla_query( if ( col_advanced_data_type != "" and feature_flag_manager.is_feature_enabled( - "ENABLE_ADVANCED_DATA_TYPES" - ) + "ENABLE_ADVANCED_DATA_TYPES" + ) and col_advanced_data_type in ADVANCED_DATA_TYPES ): values = eq if is_list_target else [eq] # type: ignore @@ -1874,9 +1873,9 @@ def get_sqla_query( if ( op not in { - utils.FilterOperator.EQUALS.value, - utils.FilterOperator.NOT_EQUALS.value, - } + utils.FilterOperator.EQUALS.value, + utils.FilterOperator.NOT_EQUALS.value, + } and eq is None ): raise QueryObjectValidationError( @@ -1908,9 +1907,7 @@ def get_sqla_query( where_clause_and.append(sqla_col.like(eq)) else: where_clause_and.append(sqla_col.ilike(eq)) - elif op in { - utils.FilterOperator.NOT_LIKE.value - }: + elif op in {utils.FilterOperator.NOT_LIKE.value}: if target_generic_type != GenericDataType.STRING: sqla_col = sa.cast(sqla_col, sa.String) @@ -2120,22 +2117,21 @@ def get_sqla_query( filter_columns = [flt.get("col") for flt in filter] if filter else [] rejected_filter_columns = [ - col - for col in filter_columns - if col - and not is_adhoc_column(col) - and col not in self.column_names - and col not in applied_template_filters - ] + rejected_adhoc_filters_columns + col + for col in filter_columns + if col + and not is_adhoc_column(col) + and col not in self.column_names + and col not in applied_template_filters + ] + rejected_adhoc_filters_columns applied_filter_columns = [ - col - for col in filter_columns - if col - and not is_adhoc_column(col) - and ( - col in self.column_names or col in applied_template_filters) - ] + applied_adhoc_filters_columns + col + for col in filter_columns + if col + and not is_adhoc_column(col) + and (col in self.column_names or col in applied_template_filters) + ] + applied_adhoc_filters_columns return SqlaQuery( applied_template_filters=applied_template_filters,