feat: add connector for CouchbaseDB (#29225)

Co-authored-by: ayush-couchbase <ayush.tripathi@couchbase.com>
This commit is contained in:
Ayush Tripathi 2024-07-10 14:24:36 +05:30 committed by GitHub
parent 5aacf563d8
commit ec5bbaa678
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 411 additions and 0 deletions

View File

@ -54,6 +54,7 @@ are compatible with Superset.
| [Azure MS SQL](/docs/configuration/databases#sql-server) | `pip install pymssql` | `mssql+pymssql://UserName@presetSQL:TestPassword@presetSQL.database.windows.net:1433/TestSchema` |
| [ClickHouse](/docs/configuration/databases#clickhouse) | `pip install clickhouse-connect` | `clickhousedb://{username}:{password}@{hostname}:{port}/{database}` |
| [CockroachDB](/docs/configuration/databases#cockroachdb) | `pip install cockroachdb` | `cockroachdb://root@{hostname}:{port}/{database}?sslmode=disable` |
| [CouchbaseDB](/docs/configuration/databases#couchbaseDB) | `pip install couchbase-sqlalchemy` | `couchbasedb://{username}:{password}@{hostname}:{port}?truststorepath={ssl certificate path}` |
| [Dremio](/docs/configuration/databases#dremio) | `pip install sqlalchemy_dremio` | `dremio://user:pwd@host:31010/` |
| [Elasticsearch](/docs/configuration/databases#elasticsearch) | `pip install elasticsearch-dbapi` | `elasticsearch+http://{user}:{password}@{host}:9200/` |
| [Exasol](/docs/configuration/databases#exasol) | `pip install sqlalchemy-exasol` | `exa+pyodbc://{username}:{password}@{hostname}:{port}/my_schema?CONNECTIONLCALL=en_US.UTF-8&driver=EXAODBC` |
@ -373,6 +374,22 @@ cockroachdb://root@{hostname}:{port}/{database}?sslmode=disable
```
#### CouchbaseDB
The recommended connector library for CouchbaseDB is
[couchbase-sqlalchemy](https://github.com/couchbase/couchbase-sqlalchemy).
```
pip install couchbase-sqlalchemy
```
The expected connection string is formatted as follows:
```
couchbasedb://{username}:{password}@{hostname}:{port}?truststorepath={certificate path}?ssl={true/false}
```
#### CrateDB
The recommended connector library for CrateDB is

View File

@ -127,4 +127,9 @@ export const Databases = [
href: 'https://www.oceanbase.com/',
imgName: 'oceanbase.svg',
},
{
title: 'Couchbase',
href: 'https://www.couchbase.com/',
imgName: 'couchbase.svg',
},
];

19
docs/static/img/databases/couchbase.svg vendored Normal file
View File

@ -0,0 +1,19 @@
<!--
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.
-->
<svg xmlns="http://www.w3.org/2000/svg" width="2500" height="575" viewBox="0.6 0.1 575.509 132.4"><title>logo</title><path d="M199.3 96.9c-20.3 0-30.5-14.601-30.5-30.8 0-16.1 10.6-30.6 30.7-30.6 7.7 0 13.2 1.7 17.9 4.7l-5.8 9.5c-3.3-2.1-6.9-3.4-12.3-3.4-10.9 0-16.7 8.7-16.7 19.5 0 11.101 5.6 20.3 16.9 20.3 6.4 0 10.2-2.199 13.3-4.5l5.3 9.101c-3 2.699-10.1 6.199-18.8 6.199zm43.1-36.4c-6.5 0-8.6 5.5-8.6 13.5s2.6 13.7 9.1 13.7c6.6 0 8.8-5.4 8.8-13.4-.1-8-2.7-13.8-9.3-13.8zm.2 36.4c-15.2 0-21.8-11.4-21.8-22.601 0-11.2 6.6-22.9 21.8-22.9 15.2 0 22.1 11.3 22.1 22.5C264.6 85 257.9 96.9 242.6 96.9zm41.2-44.4v27.8c0 4.3 1.5 6.4 5.601 6.4 4.399 0 7.699-4.2 8.6-5.2v-29h12.2v30.6c0 5.7.6 10.101 1.2 12.7H299.3c-.399-1.2-.8-4-.899-5.8-2.301 3-6.801 6.8-14 6.8-9.6 0-13-6.1-13-14.1V52.4h12.4v.1h-.001zm56.5 44.4c-14 0-22.6-9.101-22.6-22.7 0-14.6 9.7-22.8 23-22.8 7 0 11.2 2.1 13.3 3.4l-3.9 8.4c-1.899-1.2-4.699-2.5-9-2.5-6.8 0-10.399 5.3-10.399 13.2 0 7.899 3.399 13.6 10.7 13.6 5 0 7.899-1.8 9.199-2.6l3.7 8.199c-2 1.301-6 3.801-14 3.801zm46.8-1V68c0-4.3-1.5-6.4-5.6-6.4-4.4 0-7.8 4.1-8.7 5.2v29h-12.2v-64h12.2v25.8c2.2-2.4 6.4-6.2 13.5-6.2 9.601 0 13 6.1 13 14.1v30.3h-12.2v.1zm34.4-9.5c.8.3 2.6.899 5.8.899 6.3 0 10.2-4.6 10.2-13.5 0-8-2.7-12.8-8.9-12.8-3.6 0-6.399 2.3-7.1 3.2V86.4zm0-54.5v24c2-2 5.8-4.5 10.6-4.5 10.801 0 18.4 7.4 18.4 22.4 0 14.9-10 23.101-23.2 23.101-9.899 0-15.899-2.5-18.1-3.5V31.8h12.3v.1zm58.1 44.9h-1.5c-6.699 0-11.399 2-11.399 6.9 0 3.1 2.5 4.399 5.2 4.399 4.1 0 6.5-2.399 7.699-3.6V76.8zm1.3 19.1c-.4-1.101-.601-3.301-.7-4.601-1.9 2.3-6 5.7-12.101 5.7C460.7 97 455 92.6 455 84.8c0-11.3 11.6-15.399 23.1-15.399h1.5V67c0-3.6-1.5-5.8-6.899-5.8-5.601 0-9.4 2.9-11 4l-5.3-7.4c2.6-2.4 8.399-6.3 17.699-6.3 12 0 17.7 4.6 17.7 16.2v15.2c0 6 .601 10.199 1.2 13h-12.1zm32.9 1c-7.8 0-12.8-2.301-15.5-4.101l4.101-8.5C504.1 85.5 508.3 87.9 513.1 87.9c4.5 0 7-1.301 7-3.7 0-2.8-4.899-3.601-11.1-6.601-6-2.899-9.5-6.199-9.5-12.8 0-8.2 6.5-13.4 16.4-13.4 7.399 0 12 2.3 14 3.4l-4.2 8.3c-1.7-1-5.101-2.7-9.2-2.7s-5.9 1.4-5.9 3.7c0 2.8 4 3.5 9 5.5 6.801 2.801 11.7 6 11.7 13.2-.2 9.301-6.8 14.101-17.5 14.101zm50.3-28c-.1-4.8-2-8.7-7.199-8.7-4.801 0-7.601 3.1-8.4 8.7H564.1zm-4.6 19c5.9 0 9.1-1.9 11.3-3l3.9 7.8c-2.9 1.6-7.3 4.2-16.3 4.2-14.601 0-22.9-9.101-22.9-23 0-13.8 9.5-22.5 21.6-22.5 13.7 0 20.301 9.5 18.801 25.7H548.6c.5 6.5 3.9 10.8 10.9 10.8z"/><path d="M66.8.1C30.3.1.6 29.7.6 66.3c0 36.5 29.6 66.2 66.2 66.2 36.5 0 66.2-29.6 66.2-66.2S103.3.1 66.8.1zm44.7 77.8c0 4-2.3 7.5-6.8 8.3-7.8 1.399-24.2 2.2-37.9 2.2s-30.1-.801-37.9-2.2c-4.5-.8-6.8-4.3-6.8-8.3V52.1c0-4 3.1-7.7 6.8-8.3 2.3-.4 7.7-.8 11.9-.8 1.6 0 2.9 1.2 2.9 3.1v18.1l23.2-.5 23.2.5V46.1c0-1.9 1.3-3.1 2.9-3.1 4.2 0 9.6.4 11.9.8 3.8.6 6.8 4.3 6.8 8.3-.2 8.5-.2 17.2-.2 25.8z" fill="#ed2226"/></svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -0,0 +1,19 @@
<!--
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.
-->
<svg xmlns="http://www.w3.org/2000/svg" width="2500" height="575" viewBox="0.6 0.1 575.509 132.4"><title>logo</title><path d="M199.3 96.9c-20.3 0-30.5-14.601-30.5-30.8 0-16.1 10.6-30.6 30.7-30.6 7.7 0 13.2 1.7 17.9 4.7l-5.8 9.5c-3.3-2.1-6.9-3.4-12.3-3.4-10.9 0-16.7 8.7-16.7 19.5 0 11.101 5.6 20.3 16.9 20.3 6.4 0 10.2-2.199 13.3-4.5l5.3 9.101c-3 2.699-10.1 6.199-18.8 6.199zm43.1-36.4c-6.5 0-8.6 5.5-8.6 13.5s2.6 13.7 9.1 13.7c6.6 0 8.8-5.4 8.8-13.4-.1-8-2.7-13.8-9.3-13.8zm.2 36.4c-15.2 0-21.8-11.4-21.8-22.601 0-11.2 6.6-22.9 21.8-22.9 15.2 0 22.1 11.3 22.1 22.5C264.6 85 257.9 96.9 242.6 96.9zm41.2-44.4v27.8c0 4.3 1.5 6.4 5.601 6.4 4.399 0 7.699-4.2 8.6-5.2v-29h12.2v30.6c0 5.7.6 10.101 1.2 12.7H299.3c-.399-1.2-.8-4-.899-5.8-2.301 3-6.801 6.8-14 6.8-9.6 0-13-6.1-13-14.1V52.4h12.4v.1h-.001zm56.5 44.4c-14 0-22.6-9.101-22.6-22.7 0-14.6 9.7-22.8 23-22.8 7 0 11.2 2.1 13.3 3.4l-3.9 8.4c-1.899-1.2-4.699-2.5-9-2.5-6.8 0-10.399 5.3-10.399 13.2 0 7.899 3.399 13.6 10.7 13.6 5 0 7.899-1.8 9.199-2.6l3.7 8.199c-2 1.301-6 3.801-14 3.801zm46.8-1V68c0-4.3-1.5-6.4-5.6-6.4-4.4 0-7.8 4.1-8.7 5.2v29h-12.2v-64h12.2v25.8c2.2-2.4 6.4-6.2 13.5-6.2 9.601 0 13 6.1 13 14.1v30.3h-12.2v.1zm34.4-9.5c.8.3 2.6.899 5.8.899 6.3 0 10.2-4.6 10.2-13.5 0-8-2.7-12.8-8.9-12.8-3.6 0-6.399 2.3-7.1 3.2V86.4zm0-54.5v24c2-2 5.8-4.5 10.6-4.5 10.801 0 18.4 7.4 18.4 22.4 0 14.9-10 23.101-23.2 23.101-9.899 0-15.899-2.5-18.1-3.5V31.8h12.3v.1zm58.1 44.9h-1.5c-6.699 0-11.399 2-11.399 6.9 0 3.1 2.5 4.399 5.2 4.399 4.1 0 6.5-2.399 7.699-3.6V76.8zm1.3 19.1c-.4-1.101-.601-3.301-.7-4.601-1.9 2.3-6 5.7-12.101 5.7C460.7 97 455 92.6 455 84.8c0-11.3 11.6-15.399 23.1-15.399h1.5V67c0-3.6-1.5-5.8-6.899-5.8-5.601 0-9.4 2.9-11 4l-5.3-7.4c2.6-2.4 8.399-6.3 17.699-6.3 12 0 17.7 4.6 17.7 16.2v15.2c0 6 .601 10.199 1.2 13h-12.1zm32.9 1c-7.8 0-12.8-2.301-15.5-4.101l4.101-8.5C504.1 85.5 508.3 87.9 513.1 87.9c4.5 0 7-1.301 7-3.7 0-2.8-4.899-3.601-11.1-6.601-6-2.899-9.5-6.199-9.5-12.8 0-8.2 6.5-13.4 16.4-13.4 7.399 0 12 2.3 14 3.4l-4.2 8.3c-1.7-1-5.101-2.7-9.2-2.7s-5.9 1.4-5.9 3.7c0 2.8 4 3.5 9 5.5 6.801 2.801 11.7 6 11.7 13.2-.2 9.301-6.8 14.101-17.5 14.101zm50.3-28c-.1-4.8-2-8.7-7.199-8.7-4.801 0-7.601 3.1-8.4 8.7H564.1zm-4.6 19c5.9 0 9.1-1.9 11.3-3l3.9 7.8c-2.9 1.6-7.3 4.2-16.3 4.2-14.601 0-22.9-9.101-22.9-23 0-13.8 9.5-22.5 21.6-22.5 13.7 0 20.301 9.5 18.801 25.7H548.6c.5 6.5 3.9 10.8 10.9 10.8z"/><path d="M66.8.1C30.3.1.6 29.7.6 66.3c0 36.5 29.6 66.2 66.2 66.2 36.5 0 66.2-29.6 66.2-66.2S103.3.1 66.8.1zm44.7 77.8c0 4-2.3 7.5-6.8 8.3-7.8 1.399-24.2 2.2-37.9 2.2s-30.1-.801-37.9-2.2c-4.5-.8-6.8-4.3-6.8-8.3V52.1c0-4 3.1-7.7 6.8-8.3 2.3-.4 7.7-.8 11.9-.8 1.6 0 2.9 1.2 2.9 3.1v18.1l23.2-.5 23.2.5V46.1c0-1.9 1.3-3.1 2.9-3.1 4.2 0 9.6.4 11.9.8 3.8.6 6.8 4.3 6.8 8.3-.2 8.5-.2 17.2-.2 25.8z" fill="#ed2226"/></svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -0,0 +1,257 @@
# 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.
# pylint: disable=too-many-lines
from __future__ import annotations
from datetime import datetime
from typing import Any, Optional, TypedDict
from urllib import parse
from flask_babel import gettext as __
from marshmallow import fields, Schema
from sqlalchemy.engine.url import URL
from superset.constants import TimeGrain
from superset.databases.utils import make_url_safe
from superset.db_engine_specs.base import (
BaseEngineSpec,
BasicParametersMixin,
BasicParametersType as BaseBasicParametersType,
BasicPropertiesType as BaseBasicPropertiesType,
)
from superset.errors import ErrorLevel, SupersetError, SupersetErrorType
from superset.utils.network import is_hostname_valid, is_port_open
class BasicParametersType(TypedDict, total=False):
username: Optional[str]
password: Optional[str]
host: str
database: str
port: Optional[int]
query: dict[str, Any]
encryption: bool
class BasicPropertiesType(TypedDict):
parameters: BasicParametersType
class CouchbaseParametersSchema(Schema):
username = fields.String(allow_none=True, metadata={"description": __("Username")})
password = fields.String(allow_none=True, metadata={"description": __("Password")})
host = fields.String(
required=True, metadata={"description": __("Hostname or IP address")}
)
database = fields.String(
allow_none=True, metadata={"description": __("Database name")}
)
port = fields.Integer(
allow_none=True, metadata={"description": __("Database port")}
)
encryption = fields.Boolean(
dump_default=False,
metadata={"description": __("Use an encrypted connection to the database")},
)
query = fields.Dict(
keys=fields.Str(),
values=fields.Raw(),
metadata={"description": __("Additional parameters")},
)
class CouchbaseDbEngineSpec(BasicParametersMixin, BaseEngineSpec):
engine = "couchbasedb"
engine_name = "Couchbase"
default_driver = "couchbasedb"
allows_joins = False
allows_subqueries = False
sqlalchemy_uri_placeholder = (
"couchbasedb://user:password@host[:port]?truststorepath=value?ssl=value"
)
parameters_schema = CouchbaseParametersSchema()
_time_grain_expressions = {
None: "{col}",
TimeGrain.SECOND: "DATE_TRUNC_STR(TOSTRING({col}),'second')",
TimeGrain.MINUTE: "DATE_TRUNC_STR(TOSTRING({col}),'minute')",
TimeGrain.HOUR: "DATE_TRUNC_STR(TOSTRING({col}),'hour')",
TimeGrain.DAY: "DATE_TRUNC_STR(TOSTRING({col}),'day')",
TimeGrain.MONTH: "DATE_TRUNC_STR(TOSTRING({col}),'month')",
TimeGrain.YEAR: "DATE_TRUNC_STR(TOSTRING({col}),'year')",
TimeGrain.QUARTER: "DATE_TRUNC_STR(TOSTRING({col}),'quarter')",
}
@classmethod
def epoch_to_dttm(cls) -> str:
return "MILLIS_TO_STR({col} * 1000)"
@classmethod
def epoch_ms_to_dttm(cls) -> str:
return "MILLIS_TO_STR({col})"
@classmethod
def convert_dttm(
cls, target_type: str, dttm: datetime, db_extra: Optional[dict[str, Any]] = None
) -> Optional[str]:
if target_type.lower() == "date":
formatted_date = dttm.date().isoformat()
else:
formatted_date = dttm.replace(microsecond=0).isoformat()
return f"DATETIME(DATE_FORMAT_STR(STR_TO_UTC('{formatted_date}'), 'iso8601'))"
@classmethod
def build_sqlalchemy_uri(
cls,
parameters: BaseBasicParametersType,
encrypted_extra: Optional[dict[str, Any]] = None,
) -> str:
query_params = parameters.get("query", {}).copy()
if parameters.get("encryption"):
query_params["ssl"] = "true"
else:
query_params["ssl"] = "false"
if parameters.get("port") is None:
uri = URL.create(
"couchbasedb",
username=parameters.get("username"),
password=parameters.get("password"),
host=parameters["host"],
port=None,
query=query_params,
)
else:
uri = URL.create(
"couchbasedb",
username=parameters.get("username"),
password=parameters.get("password"),
host=parameters["host"],
port=parameters.get("port"),
query=query_params,
)
print(uri)
return str(uri)
@classmethod
def get_parameters_from_uri(
cls, uri: str, encrypted_extra: Optional[dict[str, Any]] = None
) -> BaseBasicParametersType:
print("get_parameters is called : ", uri)
url = make_url_safe(uri)
query = {
key: value
for key, value in url.query.items()
if (key, value) not in cls.encryption_parameters.items()
}
ssl_value = url.query.get("ssl", "false").lower()
encryption = ssl_value == "true"
return BaseBasicParametersType(
username=url.username,
password=url.password,
host=url.host,
port=url.port,
database=url.database,
query=query,
encryption=encryption,
)
@classmethod
def validate_parameters(
cls, properties: BaseBasicPropertiesType
) -> list[SupersetError]:
"""
Couchbase local server needs hostname and port but on cloud we need only connection String along with credentials to connect.
"""
errors: list[SupersetError] = []
required = {"host", "username", "password", "database"}
parameters = properties.get("parameters", {})
present = {key for key in parameters if parameters.get(key, ())}
if missing := sorted(required - present):
errors.append(
SupersetError(
message=f'One or more parameters are missing: {", ".join(missing)}',
error_type=SupersetErrorType.CONNECTION_MISSING_PARAMETERS_ERROR,
level=ErrorLevel.WARNING,
extra={"missing": missing},
),
)
host = parameters.get("host", None)
if not host:
return errors
# host can be a connection string in case of couchbase cloud. So Connection Check is not required in that case.
if not is_hostname_valid(host):
errors.append(
SupersetError(
message="The hostname provided can't be resolved.",
error_type=SupersetErrorType.CONNECTION_INVALID_HOSTNAME_ERROR,
level=ErrorLevel.ERROR,
extra={"invalid": ["host"]},
),
)
return errors
if port := parameters.get("port", None):
try:
port = int(port)
except (ValueError, TypeError):
errors.append(
SupersetError(
message="Port must be a valid integer.",
error_type=SupersetErrorType.CONNECTION_INVALID_PORT_ERROR,
level=ErrorLevel.ERROR,
extra={"invalid": ["port"]},
),
)
if not (isinstance(port, int) and 0 <= port < 2**16):
errors.append(
SupersetError(
message=(
"The port must be an integer between 0 and 65535 "
"(inclusive)."
),
error_type=SupersetErrorType.CONNECTION_INVALID_PORT_ERROR,
level=ErrorLevel.ERROR,
extra={"invalid": ["port"]},
),
)
elif not is_port_open(host, port):
errors.append(
SupersetError(
message="The port is closed.",
error_type=SupersetErrorType.CONNECTION_PORT_CLOSED_ERROR,
level=ErrorLevel.ERROR,
extra={"invalid": ["port"]},
),
)
return errors
@classmethod
def get_schema_from_engine_params(
cls,
sqlalchemy_uri: URL,
connect_args: dict[str, Any],
) -> Optional[str]:
"""
Return the configured schema.
"""
return parse.unquote(sqlalchemy_uri.database)

View File

@ -102,6 +102,7 @@ SQLGLOT_DIALECTS = {
"clickhouse": Dialects.CLICKHOUSE,
"clickhousedb": Dialects.CLICKHOUSE,
"cockroachdb": Dialects.POSTGRES,
"couchbasedb": Dialects.MYSQL,
# "crate": ???
# "databend": ???
"databricks": Dialects.DATABRICKS,

View File

@ -0,0 +1,93 @@
# 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
import pytest
from sqlalchemy import types
from superset.utils.core import GenericDataType
from tests.unit_tests.db_engine_specs.utils import (
assert_column_spec,
assert_convert_dttm,
)
from tests.unit_tests.fixtures.common import dttm # noqa: F401
def test_epoch_to_dttm() -> None:
"""
DB Eng Specs (couchbase): Test epoch to dttm
"""
from superset.db_engine_specs.couchbasedb import CouchbaseDbEngineSpec
assert CouchbaseDbEngineSpec.epoch_to_dttm() == "MILLIS_TO_STR({col} * 1000)"
def test_epoch_ms_to_dttm() -> None:
"""
DB Eng Specs (couchbase): Test epoch ms to dttm
"""
from superset.db_engine_specs.couchbasedb import CouchbaseDbEngineSpec
assert CouchbaseDbEngineSpec.epoch_ms_to_dttm() == "MILLIS_TO_STR({col})"
@pytest.mark.parametrize(
"target_type,expected_result",
[
("Date", "DATETIME(DATE_FORMAT_STR(STR_TO_UTC('2019-01-02'), 'iso8601'))"),
(
"DateTime",
"DATETIME(DATE_FORMAT_STR(STR_TO_UTC('2019-01-02T03:04:05'), 'iso8601'))",
),
],
)
def test_convert_dttm(
target_type: str,
expected_result: Optional[str],
dttm: datetime, # noqa: F811
) -> None:
from superset.db_engine_specs.couchbasedb import CouchbaseDbEngineSpec as spec
assert_convert_dttm(spec, target_type, expected_result, dttm)
@pytest.mark.parametrize(
"native_type,sqla_type,attrs,generic_type,is_dttm",
[
("SMALLINT", types.SmallInteger, None, GenericDataType.NUMERIC, False),
("INTEGER", types.Integer, None, GenericDataType.NUMERIC, False),
("BIGINT", types.BigInteger, None, GenericDataType.NUMERIC, False),
("DECIMAL", types.Numeric, None, GenericDataType.NUMERIC, False),
("NUMERIC", types.Numeric, None, GenericDataType.NUMERIC, False),
("CHAR", types.String, None, GenericDataType.STRING, False),
("VARCHAR", types.String, None, GenericDataType.STRING, False),
("TEXT", types.String, None, GenericDataType.STRING, False),
("BOOLEAN", types.Boolean, None, GenericDataType.BOOLEAN, False),
],
)
def test_get_column_spec(
native_type: str,
sqla_type: type[types.TypeEngine],
attrs: dict[str, Any] | None,
generic_type: GenericDataType,
is_dttm: bool,
) -> None:
from superset.db_engine_specs.couchbasedb import CouchbaseDbEngineSpec as spec
assert_column_spec(spec, native_type, sqla_type, attrs, generic_type, is_dttm)