diff --git a/superset/db_engine_specs/mysql.py b/superset/db_engine_specs/mysql.py index d5e5a5c7561..870dbe853d5 100644 --- a/superset/db_engine_specs/mysql.py +++ b/superset/db_engine_specs/mysql.py @@ -35,7 +35,7 @@ from sqlalchemy.dialects.mysql import ( from sqlalchemy.engine.url import URL from sqlalchemy.types import TypeEngine -from superset.db_engine_specs.base import BaseEngineSpec +from superset.db_engine_specs.base import BaseEngineSpec, BasicParametersMixin from superset.errors import SupersetErrorType from superset.utils import core as utils from superset.utils.core import ColumnSpec, GenericDataType @@ -53,11 +53,18 @@ CONNECTION_HOST_DOWN_REGEX = re.compile( CONNECTION_UNKNOWN_DATABASE_REGEX = re.compile("Unknown database '(?P.*?)'") -class MySQLEngineSpec(BaseEngineSpec): +class MySQLEngineSpec(BaseEngineSpec, BasicParametersMixin): engine = "mysql" engine_name = "MySQL" max_column_name_length = 64 + drivername = "mysql+mysqldb" + sqlalchemy_uri_placeholder = ( + "mysql://user:password@host:port/dbname[?key=value&key=value...]" + ) + + encryption_parameters = {"ssl": "1"} + column_type_mappings: Tuple[ Tuple[ Pattern[str], @@ -111,22 +118,22 @@ class MySQLEngineSpec(BaseEngineSpec): 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 MySQL 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"]}, ), } diff --git a/tests/databases/api_tests.py b/tests/databases/api_tests.py index 4d550095f69..7f53c579db3 100644 --- a/tests/databases/api_tests.py +++ b/tests/databases/api_tests.py @@ -36,6 +36,7 @@ from superset import db, security_manager from superset.connectors.sqla.models import SqlaTable from superset.db_engine_specs.mysql import MySQLEngineSpec from superset.db_engine_specs.postgres import PostgresEngineSpec +from superset.db_engine_specs.hana import HanaEngineSpec from superset.errors import SupersetError from superset.models.core import Database, ConfigurationMethod from superset.models.reports import ReportSchedule, ReportScheduleType @@ -1371,8 +1372,8 @@ class TestDatabaseApi(SupersetTestCase): def test_available(self, app, get_available_engine_specs): app.config = {"PREFERRED_DATABASES": ["postgresql"]} get_available_engine_specs.return_value = [ - MySQLEngineSpec, PostgresEngineSpec, + HanaEngineSpec, ] self.login(username="admin") @@ -1380,7 +1381,6 @@ class TestDatabaseApi(SupersetTestCase): rv = self.client.get(uri) response = json.loads(rv.data.decode("utf-8")) - assert rv.status_code == 200 assert response == { "databases": [ @@ -1428,7 +1428,73 @@ class TestDatabaseApi(SupersetTestCase): "preferred": True, "sqlalchemy_uri_placeholder": "postgresql+psycopg2://user:password@host:port/dbname[?key=value&key=value...]", }, - {"engine": "mysql", "name": "MySQL", "preferred": False}, + {"engine": "hana", "name": "SAP HANA", "preferred": False}, + ] + } + + @mock.patch("superset.databases.api.get_available_engine_specs") + @mock.patch("superset.databases.api.app") + def test_available_with_mysql(self, app, get_available_engine_specs): + app.config = {"PREFERRED_DATABASES": ["mysql"]} + get_available_engine_specs.return_value = [ + MySQLEngineSpec, + HanaEngineSpec, + ] + + self.login(username="admin") + uri = "api/v1/database/available/" + + rv = self.client.get(uri) + response = json.loads(rv.data.decode("utf-8")) + print(response) + assert rv.status_code == 200 + assert response == { + "databases": [ + { + "engine": "mysql", + "name": "MySQL", + "parameters": { + "properties": { + "database": { + "description": "Database name", + "type": "string", + }, + "encryption": { + "description": "Use an encrypted connection to the database", + "type": "boolean", + }, + "host": { + "description": "Hostname or IP address", + "type": "string", + }, + "password": { + "description": "Password", + "nullable": True, + "type": "string", + }, + "port": { + "description": "Database port", + "format": "int32", + "type": "integer", + }, + "query": { + "additionalProperties": {}, + "description": "Additional parameters", + "type": "object", + }, + "username": { + "description": "Username", + "nullable": True, + "type": "string", + }, + }, + "required": ["database", "host", "port", "username"], + "type": "object", + }, + "preferred": True, + "sqlalchemy_uri_placeholder": "mysql://user:password@host:port/dbname[?key=value&key=value...]", + }, + {"engine": "hana", "name": "SAP HANA", "preferred": False}, ] } diff --git a/tests/db_engine_specs/mysql_tests.py b/tests/db_engine_specs/mysql_tests.py index be5262d5d34..aa23f2935f5 100644 --- a/tests/db_engine_specs/mysql_tests.py +++ b/tests/db_engine_specs/mysql_tests.py @@ -115,6 +115,7 @@ class TestMySQLEngineSpecsDbEngineSpec(TestDbEngineSpec): message='Either the username "test" or the password is incorrect.', level=ErrorLevel.ERROR, extra={ + "invalid": ["username", "password"], "engine_name": "MySQL", "issue_codes": [ { @@ -140,6 +141,7 @@ class TestMySQLEngineSpecsDbEngineSpec(TestDbEngineSpec): message='Unknown MySQL server host "badhostname.com".', level=ErrorLevel.ERROR, extra={ + "invalid": ["host"], "engine_name": "MySQL", "issue_codes": [ { @@ -161,6 +163,7 @@ class TestMySQLEngineSpecsDbEngineSpec(TestDbEngineSpec): "down and can't be reached.", level=ErrorLevel.ERROR, extra={ + "invalid": ["host", "port"], "engine_name": "MySQL", "issue_codes": [ { @@ -181,6 +184,7 @@ class TestMySQLEngineSpecsDbEngineSpec(TestDbEngineSpec): message='The host "93.184.216.34" might be down and can\'t be reached.', level=ErrorLevel.ERROR, extra={ + "invalid": ["host", "port"], "engine_name": "MySQL", "issue_codes": [ { @@ -195,12 +199,14 @@ class TestMySQLEngineSpecsDbEngineSpec(TestDbEngineSpec): msg = "mysql: Unknown database 'badDB'" result = MySQLEngineSpec.extract_errors(Exception(msg)) + print(result) assert result == [ SupersetError( message='Unable to connect to database "badDB".', error_type=SupersetErrorType.CONNECTION_UNKNOWN_DATABASE_ERROR, level=ErrorLevel.ERROR, extra={ + "invalid": ["database"], "engine_name": "MySQL", "issue_codes": [ {