This is an automated email from the ASF dual-hosted git repository.
yongjiezhao pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git
The following commit(s) were added to refs/heads/master by this push:
new b4560d442b feat(database): Add OceanBase support (#29496)
b4560d442b is described below
commit b4560d442b874c3ee65265c291e4c4afa6885710
Author: yuanoOo <[email protected]>
AuthorDate: Tue Jul 9 17:26:23 2024 +0800
feat(database): Add OceanBase support (#29496)
---
docs/docs/configuration/databases.mdx | 14 ++
docs/src/resources/data.js | 5 +
docs/static/img/databases/oceanbase.svg | 67 ++++++++
pyproject.toml | 1 +
superset-frontend/src/assets/images/oceanbase.svg | 67 ++++++++
superset/db_engine_specs/oceanbase.py | 183 +++++++++++++++++++++
tests/unit_tests/db_engine_specs/test_oceanbase.py | 59 +++++++
7 files changed, 396 insertions(+)
diff --git a/docs/docs/configuration/databases.mdx
b/docs/docs/configuration/databases.mdx
index 2b23c464dd..5681acc14d 100644
--- a/docs/docs/configuration/databases.mdx
+++ b/docs/docs/configuration/databases.mdx
@@ -64,6 +64,7 @@ are compatible with Superset.
| [IBM Db2](/docs/configuration/databases#ibm-db2) |
`pip install ibm_db_sa`
| `db2+ibm_db://`
|
| [IBM Netezza Performance
Server](/docs/configuration/databases#ibm-netezza-performance-server) | `pip
install nzalchemy` |
`netezza+nzpy://<UserName>:<DBPassword>@<Database Host>/<Database Name>`
|
| [MySQL](/docs/configuration/databases#mysql) |
`pip install mysqlclient`
| `mysql://<UserName>:<DBPassword>@<Database Host>/<Database Name>`
|
+| [OceanBase](/docs/configuration/databases#oceanbase) |
`pip install oceanbase_py`
| `oceanbase://<UserName>:<DBPassword>@<Database Host>/<Database Name>`
|
| [Oracle](/docs/configuration/databases#oracle) |
`pip install cx_Oracle`
| `oracle://`
|
| [PostgreSQL](/docs/configuration/databases#postgres) |
`pip install psycopg2`
| `postgresql://<UserName>:<DBPassword>@<Database Host>/<Database Name>`
|
| [Presto](/docs/configuration/databases#presto) |
`pip install pyhive`
| `presto://`
|
@@ -988,6 +989,19 @@ Here's the recommended connection string:
netezza+nzpy://{username}:{password}@{hostname}:{port}/{database}
```
+#### OceanBase
+
+The [sqlalchemy-oceanbase](https://pypi.org/project/oceanbase_py/) library is
the recommended
+way to connect to OceanBase through SQLAlchemy.
+
+
+The connection string for OceanBase looks like this:
+
+```
+oceanbase://<User>:<Password>@<Host>:<Port>/<Database>
+```
+
+
#### Ocient DB
The recommended connector library for Ocient is
[sqlalchemy-ocient](https://pypi.org/project/sqlalchemy-ocient).
diff --git a/docs/src/resources/data.js b/docs/src/resources/data.js
index 42cf835a49..766e32c5dd 100644
--- a/docs/src/resources/data.js
+++ b/docs/src/resources/data.js
@@ -122,4 +122,9 @@ export const Databases = [
href: 'https://doris.apache.org/',
imgName: 'doris.png',
},
+ {
+ title: 'OceanBase',
+ href: 'https://www.oceanbase.com/',
+ imgName: 'oceanbase.svg',
+ },
];
diff --git a/docs/static/img/databases/oceanbase.svg
b/docs/static/img/databases/oceanbase.svg
new file mode 100644
index 0000000000..01e48adbcb
--- /dev/null
+++ b/docs/static/img/databases/oceanbase.svg
@@ -0,0 +1,67 @@
+<!--
+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 id="_图层_1" data-name="图层 1" xmlns="http://www.w3.org/2000/svg" viewBox="0
0 95.26 13.73">
+ <defs>
+ <style>
+ .cls-1 {
+ fill: #0181fd;
+ }
+
+ .cls-2 {
+ fill: #fff;
+ }
+
+ .cls-3 {
+ fill: #ffa005;
+ }
+
+ .cls-4 {
+ fill: #181818;
+ }
+
+ .cls-5 {
+ fill: #07c846;
+ }
+ </style>
+ </defs>
+ <rect class="cls-2" width="95.26" height="13.73"/>
+ <g>
+ <g>
+ <path class="cls-4"
d="M51,2.66l-4.58,8.49h2.28l.74-1.48h3.05l.24,1.48h2.28l-1.68-8.49h-2.33Zm-.7,5.34l1.44-2.83,.48,2.83h-1.92Z"/>
+ <path class="cls-4"
d="M31.98,3.71c-.33-.41-.76-.72-1.28-.95-.52-.23-1.11-.34-1.77-.34s-1.3,.11-1.9,.34c-.6,.23-1.13,.55-1.6,.95-.47,.41-.86,.88-1.18,1.42-.32,.54-.53,1.13-.64,1.76-.11,.63-.1,1.22,.04,1.76,.13,.54,.36,1.02,.69,1.42,.33,.41,.75,.72,1.27,.95,.52,.23,1.11,.34,1.78,.34s1.29-.11,1.89-.34c.6-.23,1.13-.55,1.61-.95,.47-.41,.87-.88,1.19-1.42,.32-.54,.53-1.13,.64-1.76,.11-.63,.1-1.22-.04-1.76-.13-.54-.37-1.02-.7-1.42m-1.84,4.12c-.17,.29-.38,.53-.63,.74-.25,.21-.53,.37-.83,.4
[...]
+ <polygon class="cls-4" points="46.4 9.28 42.07 9.28 42.32 7.81 46.51
7.81 46.82 5.94 42.64 5.94 42.88 4.52 47.21 4.52 47.53 2.66 41.06 2.66 39.61
11.15 46.08 11.15 46.4 9.28"/>
+ <polygon class="cls-4" points="61.29 7.75 58.79 2.66 56.65 2.66 55.2
11.15 57.34 11.15 58.23 6.05 60.72 11.15 62.85 11.15 64.3 2.66 62.17 2.66 61.29
7.75"/>
+ <path class="cls-4"
d="M70.83,8.68c.04-.26,.06-.5,.04-.72-.02-.23-.08-.43-.19-.61s-.25-.33-.45-.46c-.19-.12-.44-.22-.73-.28,.34-.2,.61-.43,.8-.72,.2-.28,.33-.64,.41-1.06,.12-.71,.02-1.25-.31-1.62-.33-.38-.87-.56-1.63-.56h-3.47l-1.45,8.49h3.72c.44,0,.84-.05,1.2-.16,.37-.1,.69-.26,.97-.47,.28-.21,.51-.47,.7-.78,.19-.31,.31-.66,.38-1.06m-3.67-4.34h.67c.67,0,.96,.27,.86,.82-.09,.55-.47,.82-1.14,.82h-.67l.28-1.64Zm1.07,4.88c-.26,.14-.65,.21-1.19,.21h-.75l.31-1.8h.75c.54,0,.91,.07,1.12,.
[...]
+ <path class="cls-4"
d="M83.57,5.97c-.16-.06-.33-.12-.5-.17-.17-.05-.32-.11-.45-.19-.13-.07-.23-.16-.3-.25-.07-.1-.09-.22-.07-.37,.04-.22,.15-.39,.35-.53,.2-.14,.43-.2,.71-.2,.22,0,.44,.05,.65,.14,.22,.09,.42,.24,.62,.43l1.14-1.72c-.37-.23-.77-.4-1.19-.51-.42-.12-.84-.17-1.25-.17-.44,0-.84,.07-1.22,.2-.38,.13-.71,.32-.99,.57-.29,.24-.52,.54-.72,.88-.19,.34-.33,.72-.4,1.14-.07,.42-.07,.77,.01,1.04,.08,.27,.21,.5,.39,.68,.18,.18,.39,.32,.63,.43,.25,.11,.5,.21,.76,.3,.22,.08,.4,.16,.56
[...]
+ <path class="cls-4"
d="M38.16,2.42c-.57,0-1.14,.11-1.7,.33-.56,.22-1.07,.53-1.54,.92-.46,.39-.86,.86-1.19,1.41-.33,.54-.55,1.14-.66,1.8-.11,.65-.09,1.26,.05,1.81,.15,.55,.38,1.03,.71,1.43,.33,.4,.73,.71,1.21,.93,.48,.22,1.01,.33,1.57,.33,.28,0,.56-.03,.85-.08,.29-.06,.61-.14,.96-.26l.23-.08,.45-2.61c-.65,.6-1.31,.9-2,.9-.31,0-.59-.06-.84-.18-.25-.12-.45-.29-.62-.5-.16-.21-.28-.46-.34-.74-.06-.29-.07-.6,0-.93,.06-.33,.17-.64,.33-.92,.16-.29,.36-.53,.59-.74,.23-.21,.49-.37,.79-.49,.2
[...]
+ <path class="cls-4"
d="M78.31,9.36c-.35-.07-1.02-.11-2.18,.17l.26,1.65h2.28l-.36-1.81Z"/>
+ <path class="cls-4"
d="M78.22,8.87l-.37-1.87c-.55,0-1.24,.09-2.1,.31-.09,.02-.19,.05-.29,.08-.47,.13-.91,.22-1.31,.28l1.25-2.46,.27,1.65c.86-.22,1.55-.3,2.1-.31l-.77-3.85h-2.33l-4.58,8.49h2.28l.82-1.62c.69-.05,1.52-.17,2.46-.43,.14-.04,.27-.07,.39-.1,1.16-.27,1.83-.24,2.18-.17"/>
+ <g>
+ <path class="cls-4"
d="M89.66,6.01c-1.7,.44-2.99,.4-3.55,.35l-.3,1.83c.16,.01,.35,.02,.56,.03,.84,.02,2.06-.06,3.54-.45,1.4-.37,2.23-.34,2.65-.27l.3-1.83c-.71-.07-1.75-.03-3.21,.35"/>
+ <path class="cls-4"
d="M90.1,2.73c-1.62,.42-2.87,.41-3.47,.36l-.45,2.8c.4,.04,1.15,.07,2.15-.07l.16-.98c.56-.07,1.19-.18,1.87-.36,1.5-.39,2.34-.34,2.73-.26l.3-1.84c-.71-.09-1.77-.05-3.29,.34"/>
+ <path class="cls-4"
d="M89.25,9.18c-.54,.14-1.03,.23-1.48,.29l.15-.9c-.62,.07-1.16,.09-1.59,.08-.21,0-.39-.01-.55-.03l-.46,2.72c.18,.01,.39,.03,.64,.03,.84,.02,2.06-.06,3.54-.45,1.32-.35,2.13-.35,2.58-.29l.3-1.83c-.71-.06-1.72,0-3.13,.36"/>
+ </g>
+ </g>
+ <g>
+ <path class="cls-5"
d="M11.91,12.89c.36-.15,.72-.29,1.1-.42,.17-.06,.35-.12,.52-.17,.14-.04,.28-.08,.42-.12,2.12-.59,4.27-.75,6.35-.52v-2.17c-2.3-.23-4.67-.03-7.02,.63-.14,.04-.28,.08-.42,.12-.18,.06-.37,.12-.55,.18-.36,.12-.72,.26-1.07,.4l.67,2.06Z"/>
+ <path class="cls-1"
d="M20.29,2.46v6.49c-2.39-.23-4.85-.02-7.27,.69-.3,.09-.59,.18-.89,.28-.36,.12-.72,.24-1.07,.34-3.06,.9-6.16,1.22-9.18,1V4.77c2.36,.23,4.79,.02,7.18-.66,.33-.09,.65-.2,.98-.31,.33-.11,.66-.22,.98-.32,3.09-.92,6.22-1.24,9.27-1.03Z"/>
+ <path class="cls-3"
d="M10.26,.84c-.36,.15-.72,.29-1.1,.42-.17,.06-.35,.12-.52,.17-.14,.04-.28,.08-.42,.12-2.12,.59-4.27,.75-6.35,.52v2.17c2.3,.23,4.67,.03,7.02-.63,.14-.04,.28-.08,.42-.12,.18-.06,.37-.12,.55-.18,.36-.12,.72-.26,1.07-.4l-.67-2.06Z"/>
+ </g>
+ </g>
+</svg>
diff --git a/pyproject.toml b/pyproject.toml
index 26b020f98c..c6e0c16a21 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -173,6 +173,7 @@ vertica = ["sqlalchemy-vertica-python>=0.5.9, < 0.6"]
netezza = ["nzalchemy>=11.0.2"]
starrocks = ["starrocks>=1.0.0"]
doris = ["pydoris>=1.0.0, <2.0.0"]
+oceanbase = ["oceanbase_py>=0.0.1"]
development = [
"docker",
"flask-testing",
diff --git a/superset-frontend/src/assets/images/oceanbase.svg
b/superset-frontend/src/assets/images/oceanbase.svg
new file mode 100644
index 0000000000..01e48adbcb
--- /dev/null
+++ b/superset-frontend/src/assets/images/oceanbase.svg
@@ -0,0 +1,67 @@
+<!--
+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 id="_图层_1" data-name="图层 1" xmlns="http://www.w3.org/2000/svg" viewBox="0
0 95.26 13.73">
+ <defs>
+ <style>
+ .cls-1 {
+ fill: #0181fd;
+ }
+
+ .cls-2 {
+ fill: #fff;
+ }
+
+ .cls-3 {
+ fill: #ffa005;
+ }
+
+ .cls-4 {
+ fill: #181818;
+ }
+
+ .cls-5 {
+ fill: #07c846;
+ }
+ </style>
+ </defs>
+ <rect class="cls-2" width="95.26" height="13.73"/>
+ <g>
+ <g>
+ <path class="cls-4"
d="M51,2.66l-4.58,8.49h2.28l.74-1.48h3.05l.24,1.48h2.28l-1.68-8.49h-2.33Zm-.7,5.34l1.44-2.83,.48,2.83h-1.92Z"/>
+ <path class="cls-4"
d="M31.98,3.71c-.33-.41-.76-.72-1.28-.95-.52-.23-1.11-.34-1.77-.34s-1.3,.11-1.9,.34c-.6,.23-1.13,.55-1.6,.95-.47,.41-.86,.88-1.18,1.42-.32,.54-.53,1.13-.64,1.76-.11,.63-.1,1.22,.04,1.76,.13,.54,.36,1.02,.69,1.42,.33,.41,.75,.72,1.27,.95,.52,.23,1.11,.34,1.78,.34s1.29-.11,1.89-.34c.6-.23,1.13-.55,1.61-.95,.47-.41,.87-.88,1.19-1.42,.32-.54,.53-1.13,.64-1.76,.11-.63,.1-1.22-.04-1.76-.13-.54-.37-1.02-.7-1.42m-1.84,4.12c-.17,.29-.38,.53-.63,.74-.25,.21-.53,.37-.83,.4
[...]
+ <polygon class="cls-4" points="46.4 9.28 42.07 9.28 42.32 7.81 46.51
7.81 46.82 5.94 42.64 5.94 42.88 4.52 47.21 4.52 47.53 2.66 41.06 2.66 39.61
11.15 46.08 11.15 46.4 9.28"/>
+ <polygon class="cls-4" points="61.29 7.75 58.79 2.66 56.65 2.66 55.2
11.15 57.34 11.15 58.23 6.05 60.72 11.15 62.85 11.15 64.3 2.66 62.17 2.66 61.29
7.75"/>
+ <path class="cls-4"
d="M70.83,8.68c.04-.26,.06-.5,.04-.72-.02-.23-.08-.43-.19-.61s-.25-.33-.45-.46c-.19-.12-.44-.22-.73-.28,.34-.2,.61-.43,.8-.72,.2-.28,.33-.64,.41-1.06,.12-.71,.02-1.25-.31-1.62-.33-.38-.87-.56-1.63-.56h-3.47l-1.45,8.49h3.72c.44,0,.84-.05,1.2-.16,.37-.1,.69-.26,.97-.47,.28-.21,.51-.47,.7-.78,.19-.31,.31-.66,.38-1.06m-3.67-4.34h.67c.67,0,.96,.27,.86,.82-.09,.55-.47,.82-1.14,.82h-.67l.28-1.64Zm1.07,4.88c-.26,.14-.65,.21-1.19,.21h-.75l.31-1.8h.75c.54,0,.91,.07,1.12,.
[...]
+ <path class="cls-4"
d="M83.57,5.97c-.16-.06-.33-.12-.5-.17-.17-.05-.32-.11-.45-.19-.13-.07-.23-.16-.3-.25-.07-.1-.09-.22-.07-.37,.04-.22,.15-.39,.35-.53,.2-.14,.43-.2,.71-.2,.22,0,.44,.05,.65,.14,.22,.09,.42,.24,.62,.43l1.14-1.72c-.37-.23-.77-.4-1.19-.51-.42-.12-.84-.17-1.25-.17-.44,0-.84,.07-1.22,.2-.38,.13-.71,.32-.99,.57-.29,.24-.52,.54-.72,.88-.19,.34-.33,.72-.4,1.14-.07,.42-.07,.77,.01,1.04,.08,.27,.21,.5,.39,.68,.18,.18,.39,.32,.63,.43,.25,.11,.5,.21,.76,.3,.22,.08,.4,.16,.56
[...]
+ <path class="cls-4"
d="M38.16,2.42c-.57,0-1.14,.11-1.7,.33-.56,.22-1.07,.53-1.54,.92-.46,.39-.86,.86-1.19,1.41-.33,.54-.55,1.14-.66,1.8-.11,.65-.09,1.26,.05,1.81,.15,.55,.38,1.03,.71,1.43,.33,.4,.73,.71,1.21,.93,.48,.22,1.01,.33,1.57,.33,.28,0,.56-.03,.85-.08,.29-.06,.61-.14,.96-.26l.23-.08,.45-2.61c-.65,.6-1.31,.9-2,.9-.31,0-.59-.06-.84-.18-.25-.12-.45-.29-.62-.5-.16-.21-.28-.46-.34-.74-.06-.29-.07-.6,0-.93,.06-.33,.17-.64,.33-.92,.16-.29,.36-.53,.59-.74,.23-.21,.49-.37,.79-.49,.2
[...]
+ <path class="cls-4"
d="M78.31,9.36c-.35-.07-1.02-.11-2.18,.17l.26,1.65h2.28l-.36-1.81Z"/>
+ <path class="cls-4"
d="M78.22,8.87l-.37-1.87c-.55,0-1.24,.09-2.1,.31-.09,.02-.19,.05-.29,.08-.47,.13-.91,.22-1.31,.28l1.25-2.46,.27,1.65c.86-.22,1.55-.3,2.1-.31l-.77-3.85h-2.33l-4.58,8.49h2.28l.82-1.62c.69-.05,1.52-.17,2.46-.43,.14-.04,.27-.07,.39-.1,1.16-.27,1.83-.24,2.18-.17"/>
+ <g>
+ <path class="cls-4"
d="M89.66,6.01c-1.7,.44-2.99,.4-3.55,.35l-.3,1.83c.16,.01,.35,.02,.56,.03,.84,.02,2.06-.06,3.54-.45,1.4-.37,2.23-.34,2.65-.27l.3-1.83c-.71-.07-1.75-.03-3.21,.35"/>
+ <path class="cls-4"
d="M90.1,2.73c-1.62,.42-2.87,.41-3.47,.36l-.45,2.8c.4,.04,1.15,.07,2.15-.07l.16-.98c.56-.07,1.19-.18,1.87-.36,1.5-.39,2.34-.34,2.73-.26l.3-1.84c-.71-.09-1.77-.05-3.29,.34"/>
+ <path class="cls-4"
d="M89.25,9.18c-.54,.14-1.03,.23-1.48,.29l.15-.9c-.62,.07-1.16,.09-1.59,.08-.21,0-.39-.01-.55-.03l-.46,2.72c.18,.01,.39,.03,.64,.03,.84,.02,2.06-.06,3.54-.45,1.32-.35,2.13-.35,2.58-.29l.3-1.83c-.71-.06-1.72,0-3.13,.36"/>
+ </g>
+ </g>
+ <g>
+ <path class="cls-5"
d="M11.91,12.89c.36-.15,.72-.29,1.1-.42,.17-.06,.35-.12,.52-.17,.14-.04,.28-.08,.42-.12,2.12-.59,4.27-.75,6.35-.52v-2.17c-2.3-.23-4.67-.03-7.02,.63-.14,.04-.28,.08-.42,.12-.18,.06-.37,.12-.55,.18-.36,.12-.72,.26-1.07,.4l.67,2.06Z"/>
+ <path class="cls-1"
d="M20.29,2.46v6.49c-2.39-.23-4.85-.02-7.27,.69-.3,.09-.59,.18-.89,.28-.36,.12-.72,.24-1.07,.34-3.06,.9-6.16,1.22-9.18,1V4.77c2.36,.23,4.79,.02,7.18-.66,.33-.09,.65-.2,.98-.31,.33-.11,.66-.22,.98-.32,3.09-.92,6.22-1.24,9.27-1.03Z"/>
+ <path class="cls-3"
d="M10.26,.84c-.36,.15-.72,.29-1.1,.42-.17,.06-.35,.12-.52,.17-.14,.04-.28,.08-.42,.12-2.12,.59-4.27,.75-6.35,.52v2.17c2.3,.23,4.67,.03,7.02-.63,.14-.04,.28-.08,.42-.12,.18-.06,.37-.12,.55-.18,.36-.12,.72-.26,1.07-.4l-.67-2.06Z"/>
+ </g>
+ </g>
+</svg>
diff --git a/superset/db_engine_specs/oceanbase.py
b/superset/db_engine_specs/oceanbase.py
new file mode 100644
index 0000000000..3f4e2d4ed9
--- /dev/null
+++ b/superset/db_engine_specs/oceanbase.py
@@ -0,0 +1,183 @@
+# 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.
+import logging
+import re
+from re import Pattern
+from typing import Any, Optional
+
+from flask_babel import gettext as __
+from sqlalchemy import Numeric, TEXT, types
+from sqlalchemy.sql.type_api import TypeEngine
+
+from superset.db_engine_specs.mysql import MySQLEngineSpec
+from superset.errors import SupersetErrorType
+from superset.utils.core import GenericDataType
+
+# Regular expressions to catch custom errors
+CONNECTION_ACCESS_DENIED_REGEX = re.compile(
+ "Access denied for user '(?P<username>.*?)'"
+)
+CONNECTION_INVALID_HOSTNAME_REGEX = re.compile(
+ "Unknown OceanBase server host '(?P<hostname>.*?)'"
+)
+CONNECTION_UNKNOWN_DATABASE_REGEX = re.compile("Unknown database
'(?P<database>.*?)'")
+CONNECTION_HOST_DOWN_REGEX = re.compile(
+ "Can't connect to OceanBase server on '(?P<hostname>.*?)'"
+)
+SYNTAX_ERROR_REGEX = re.compile(
+ "check the manual that corresponds to your OceanBase server "
+ "version for the right syntax to use near '(?P<server_error>.*)"
+)
+
+logger = logging.getLogger(__name__)
+
+
+class NUMBER(Numeric):
+ __visit_name__ = "NUMBER"
+
+
+class NUMERIC(Numeric):
+ __visit_name__ = "NUMERIC"
+
+
+class ARRAY(TypeEngine):
+ __visit_name__ = "ARRAY"
+
+ @property
+ def python_type(self) -> Optional[type[list[Any]]]:
+ return list
+
+
+class MAP(TypeEngine):
+ __visit_name__ = "MAP"
+
+ @property
+ def python_type(self) -> Optional[type[dict[Any, Any]]]:
+ return dict
+
+
+class OceanBaseEngineSpec(MySQLEngineSpec):
+ engine = "oceanbase"
+ engine_aliases = {"oceanbase", "oceanbase_py"}
+ engine_name = "OceanBase"
+ max_column_name_length = 128
+ default_driver = "oceanbase"
+
+ sqlalchemy_uri_placeholder = (
+ "oceanbase://user:password@host:port/db[?key=value&key=value...]"
+ )
+ encryption_parameters = {"ssl": "0"}
+ supports_dynamic_schema = True
+
+ column_type_mappings = ( # type: ignore
+ (
+ re.compile(r"^tinyint", re.IGNORECASE),
+ types.SMALLINT(),
+ GenericDataType.NUMERIC,
+ ),
+ (
+ re.compile(r"^largeint", re.IGNORECASE),
+ types.BIGINT(),
+ GenericDataType.NUMERIC,
+ ),
+ (
+ re.compile(r"^decimal.*", re.IGNORECASE),
+ types.DECIMAL(),
+ GenericDataType.NUMERIC,
+ ),
+ (
+ re.compile(r"^double", re.IGNORECASE),
+ types.FLOAT(),
+ GenericDataType.NUMERIC,
+ ),
+ (
+ re.compile(r"^varchar(\((\d+)\))*$", re.IGNORECASE),
+ types.VARCHAR(),
+ GenericDataType.STRING,
+ ),
+ (
+ re.compile(r"^char(\((\d+)\))*$", re.IGNORECASE),
+ types.CHAR(),
+ GenericDataType.STRING,
+ ),
+ (
+ re.compile(r"^json.*", re.IGNORECASE),
+ types.JSON(),
+ GenericDataType.STRING,
+ ),
+ (
+ re.compile(r"^binary.*", re.IGNORECASE),
+ types.BINARY(),
+ GenericDataType.STRING,
+ ),
+ (
+ re.compile(r"^array.*", re.IGNORECASE),
+ ARRAY(),
+ GenericDataType.STRING,
+ ),
+ (
+ re.compile(r"^map.*", re.IGNORECASE),
+ MAP(),
+ GenericDataType.STRING,
+ ),
+ (
+ re.compile(r"^text.*", re.IGNORECASE),
+ TEXT(),
+ GenericDataType.STRING,
+ ),
+ (
+ re.compile(r"^number.*", re.IGNORECASE),
+ NUMBER(),
+ GenericDataType.NUMERIC,
+ ),
+ (
+ re.compile(r"^numeric.*", re.IGNORECASE),
+ NUMERIC(),
+ GenericDataType.NUMERIC,
+ ),
+ )
+
+ custom_errors: dict[Pattern[str], tuple[str, SupersetErrorType, dict[str,
Any]]] = {
+ CONNECTION_ACCESS_DENIED_REGEX: (
+ __('Either the username "%(username)s" or the password is
incorrect.'),
+ SupersetErrorType.CONNECTION_ACCESS_DENIED_ERROR,
+ {"invalid": ["username", "password"]},
+ ),
+ CONNECTION_INVALID_HOSTNAME_REGEX: (
+ __('Unknown OceanBase server host "%(hostname)s".'),
+ SupersetErrorType.CONNECTION_INVALID_HOSTNAME_ERROR,
+ {"invalid": ["host"]},
+ ),
+ CONNECTION_HOST_DOWN_REGEX: (
+ __('The host "%(hostname)s" might be down and can\'t be reached.'),
+ SupersetErrorType.CONNECTION_HOST_DOWN_ERROR,
+ {"invalid": ["host", "port"]},
+ ),
+ CONNECTION_UNKNOWN_DATABASE_REGEX: (
+ __('Unable to connect to database "%(database)s".'),
+ SupersetErrorType.CONNECTION_UNKNOWN_DATABASE_ERROR,
+ {"invalid": ["database"]},
+ ),
+ SYNTAX_ERROR_REGEX: (
+ __(
+ 'Please check your query for syntax errors near
"%(server_error)s". '
+ "Then, try running your query again."
+ ),
+ SupersetErrorType.SYNTAX_ERROR,
+ {},
+ ),
+ }
diff --git a/tests/unit_tests/db_engine_specs/test_oceanbase.py
b/tests/unit_tests/db_engine_specs/test_oceanbase.py
new file mode 100644
index 0000000000..87fddb63ec
--- /dev/null
+++ b/tests/unit_tests/db_engine_specs/test_oceanbase.py
@@ -0,0 +1,59 @@
+# 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 typing import Any, Optional
+
+import pytest
+from sqlalchemy import JSON, types
+
+from superset.db_engine_specs.oceanbase import ARRAY, MAP, NUMBER, NUMERIC
+from superset.utils.core import GenericDataType
+from tests.unit_tests.db_engine_specs.utils import assert_column_spec
+
+
[email protected](
+ "native_type,sqla_type,attrs,generic_type,is_dttm",
+ [
+ # Numeric
+ ("tinyint", types.SMALLINT, None, GenericDataType.NUMERIC, False),
+ ("largeint", types.BIGINT, None, GenericDataType.NUMERIC, False),
+ ("decimal(38,18)", types.DECIMAL, None, GenericDataType.NUMERIC,
False),
+ ("number(38,18)", NUMBER, None, GenericDataType.NUMERIC, False),
+ ("numeric(38,18)", NUMERIC, None, GenericDataType.NUMERIC, False),
+ ("double", types.FLOAT, None, GenericDataType.NUMERIC, False),
+ # String
+ ("char(10)", types.CHAR, None, GenericDataType.STRING, False),
+ ("varchar(65533)", types.VARCHAR, None, GenericDataType.STRING, False),
+ ("binary", types.BINARY, None, GenericDataType.STRING, False),
+ ("text", types.TEXT, None, GenericDataType.STRING, False),
+ # Complex type
+ ("array<varchar(65533)>", ARRAY, None, GenericDataType.STRING, False),
+ ("map<string,int>", MAP, None, GenericDataType.STRING, False),
+ ("json", JSON, None, GenericDataType.STRING, False),
+ ("jsonb", JSON, None, GenericDataType.STRING, False),
+ ],
+)
+def test_get_column_spec(
+ native_type: str,
+ sqla_type: type[types.TypeEngine],
+ attrs: Optional[dict[str, Any]],
+ generic_type: GenericDataType,
+ is_dttm: bool,
+) -> None:
+ from superset.db_engine_specs.oceanbase import OceanBaseEngineSpec as spec
+
+ assert_column_spec(spec, native_type, sqla_type, attrs, generic_type,
is_dttm)