feat: add connector for CouchbaseDB (#29225)
Co-authored-by: ayush-couchbase <ayush.tripathi@couchbase.com>
This commit is contained in:
parent
5aacf563d8
commit
ec5bbaa678
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -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 |
|
|
@ -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 |
|
|
@ -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)
|
||||
|
|
@ -102,6 +102,7 @@ SQLGLOT_DIALECTS = {
|
|||
"clickhouse": Dialects.CLICKHOUSE,
|
||||
"clickhousedb": Dialects.CLICKHOUSE,
|
||||
"cockroachdb": Dialects.POSTGRES,
|
||||
"couchbasedb": Dialects.MYSQL,
|
||||
# "crate": ???
|
||||
# "databend": ???
|
||||
"databricks": Dialects.DATABRICKS,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
Loading…
Reference in New Issue