From c8e7b8c4ffadc330fd1ea58a4607a1512e6afae1 Mon Sep 17 00:00:00 2001 From: Sergey Malinin Date: Tue, 14 May 2024 23:50:51 +0300 Subject: [PATCH] Added engine spec for Virtuoso DBMS --- superset/db_engine_specs/virtuoso.py | 95 ++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 superset/db_engine_specs/virtuoso.py diff --git a/superset/db_engine_specs/virtuoso.py b/superset/db_engine_specs/virtuoso.py new file mode 100644 index 0000000000000..5fda7fc2e73a1 --- /dev/null +++ b/superset/db_engine_specs/virtuoso.py @@ -0,0 +1,95 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +from datetime import datetime +from typing import Any, Optional + +from sqlalchemy import types + +from superset.constants import TimeGrain +from superset.db_engine_specs.base import BaseEngineSpec, LimitMethod + + +class VirtuosoEngineSpec(BaseEngineSpec): + """Engine for Virtuoso""" + + engine = "virtuoso" + engine_name = "Virtuoso" + + sqlalchemy_uri_placeholder = ( + "virtuoso+pyodbc://user:password@dsn" + ) + + # Virtuoso uses FIRST to limit: `SELECT FIRST 10 * FROM table` + limit_method = LimitMethod.FETCH_MANY + max_column_name_length = 100 + # supports_catalog = True + disable_ssh_tunneling = True + + _time_grain_expressions = { + None: "{col}", + TimeGrain.SECOND: ( + "CAST(CAST({col} AS DATE) " + "|| ' ' " + "|| EXTRACT(HOUR FROM {col}) " + "|| ':' " + "|| EXTRACT(MINUTE FROM {col}) " + "|| ':' " + "|| FLOOR(EXTRACT(SECOND FROM {col})) AS TIMESTAMP)" + ), + TimeGrain.MINUTE: ( + "CAST(CAST({col} AS DATE) " + "|| ' ' " + "|| EXTRACT(HOUR FROM {col}) " + "|| ':' " + "|| EXTRACT(MINUTE FROM {col}) " + "|| ':00' AS TIMESTAMP)" + ), + TimeGrain.HOUR: ( + "CAST(CAST({col} AS DATE) " + "|| ' ' " + "|| EXTRACT(HOUR FROM {col}) " + "|| ':00:00' AS TIMESTAMP)" + ), + TimeGrain.DAY: "CAST({col} AS DATE)", + TimeGrain.MONTH: ( + "CAST(EXTRACT(YEAR FROM {col}) " + "|| '-' " + "|| EXTRACT(MONTH FROM {col}) " + "|| '-01' AS DATE)" + ), + TimeGrain.YEAR: "CAST(EXTRACT(YEAR FROM {col}) || '-01-01' AS DATE)", + } + + @classmethod + def epoch_to_dttm(cls) -> str: + return "DATEADD('second', {col}, stringdate('1970-01-01'))" + + @classmethod + def convert_dttm( + cls, target_type: str, dttm: datetime, db_extra: Optional[dict[str, Any]] = None + ) -> Optional[str]: + sqla_type = cls.get_sqla_column_type(target_type) + + if isinstance(sqla_type, types.Date): + return f"CAST('{dttm.date().isoformat()}' AS DATE)" + if isinstance(sqla_type, types.DateTime): + dttm_formatted = dttm.isoformat(sep=" ") + dttm_valid_precision = dttm_formatted[: len("YYYY-MM-DD HH:MM:SS.MMMM")] + return f"CAST('{dttm_valid_precision}' AS TIMESTAMP)" + if isinstance(sqla_type, types.Time): + return f"CAST('{dttm.time().isoformat()}' AS TIME)" + return None