diff --git a/superset/assets/src/components/TableSelector.css b/superset/assets/src/components/TableSelector.css index f4098c14056..078e57df556 100644 --- a/superset/assets/src/components/TableSelector.css +++ b/superset/assets/src/components/TableSelector.css @@ -36,3 +36,6 @@ border-bottom: 1px solid #f2f2f2; margin: 15px 0; } +.TableLabel { + white-space: nowrap; +} diff --git a/superset/assets/src/components/TableSelector.jsx b/superset/assets/src/components/TableSelector.jsx index d0785d61af0..1b83acc32b4 100644 --- a/superset/assets/src/components/TableSelector.jsx +++ b/superset/assets/src/components/TableSelector.jsx @@ -221,7 +221,7 @@ export default class TableSelector extends React.PureComponent { onMouseEnter={() => focusOption(option)} style={style} > - + diff --git a/superset/db_engine_specs/base.py b/superset/db_engine_specs/base.py index b71966ce12f..bd7b1d1de09 100644 --- a/superset/db_engine_specs/base.py +++ b/superset/db_engine_specs/base.py @@ -20,7 +20,7 @@ from datetime import datetime import hashlib import os import re -from typing import Any, Dict, List, NamedTuple, Optional, Tuple, Union +from typing import Any, Dict, List, NamedTuple, Optional, Tuple, TYPE_CHECKING, Union from flask import g from flask_babel import lazy_gettext as _ @@ -40,6 +40,10 @@ from werkzeug.utils import secure_filename from superset import app, db, sql_parse from superset.utils import core as utils +if TYPE_CHECKING: + # prevent circular imports + from superset.models.core import Database + class TimeGrain(NamedTuple): name: str # TODO: redundant field, remove @@ -538,7 +542,9 @@ class BaseEngineSpec: return sorted(inspector.get_schema_names()) @classmethod - def get_table_names(cls, inspector: Inspector, schema: Optional[str]) -> List[str]: + def get_table_names( + cls, database: "Database", inspector: Inspector, schema: Optional[str] + ) -> List[str]: """ Get all tables from schema @@ -552,7 +558,9 @@ class BaseEngineSpec: return sorted(tables) @classmethod - def get_view_names(cls, inspector: Inspector, schema: Optional[str]) -> List[str]: + def get_view_names( + cls, database: "Database", inspector: Inspector, schema: Optional[str] + ) -> List[str]: """ Get all views from schema diff --git a/superset/db_engine_specs/postgres.py b/superset/db_engine_specs/postgres.py index 4716b07586a..5b8988021d4 100644 --- a/superset/db_engine_specs/postgres.py +++ b/superset/db_engine_specs/postgres.py @@ -16,12 +16,16 @@ # under the License. # pylint: disable=C,R,W from datetime import datetime -from typing import List, Optional, Tuple +from typing import List, Optional, Tuple, TYPE_CHECKING from sqlalchemy.dialects.postgresql.base import PGInspector from superset.db_engine_specs.base import BaseEngineSpec, LimitMethod +if TYPE_CHECKING: + # prevent circular imports + from superset.models.core import Database + class PostgresBaseEngineSpec(BaseEngineSpec): """ Abstract class for Postgres 'like' databases """ @@ -64,7 +68,7 @@ class PostgresEngineSpec(PostgresBaseEngineSpec): @classmethod def get_table_names( - cls, inspector: PGInspector, schema: Optional[str] + cls, database: "Database", inspector: PGInspector, schema: Optional[str] ) -> List[str]: """Need to consider foreign tables for PostgreSQL""" tables = inspector.get_table_names(schema) diff --git a/superset/db_engine_specs/presto.py b/superset/db_engine_specs/presto.py index 6e28bd1e12a..ddc6442ac19 100644 --- a/superset/db_engine_specs/presto.py +++ b/superset/db_engine_specs/presto.py @@ -23,7 +23,7 @@ import logging import re import textwrap import time -from typing import Any, cast, Dict, List, Optional, Set, Tuple +from typing import Any, cast, Dict, List, Optional, Set, Tuple, TYPE_CHECKING from urllib import parse import simplejson as json @@ -40,6 +40,10 @@ from superset.models.sql_types.presto_sql_types import type_map as presto_type_m from superset.sql_parse import ParsedQuery from superset.utils import core as utils +if TYPE_CHECKING: + # prevent circular imports + from superset.models.core import Database + QueryStatus = utils.QueryStatus config = app.config @@ -128,14 +132,44 @@ class PrestoEngineSpec(BaseEngineSpec): return version is not None and StrictVersion(version) >= StrictVersion("0.319") @classmethod - def get_view_names(cls, inspector: Inspector, schema: Optional[str]) -> List[str]: + def get_table_names( + cls, database: "Database", inspector: Inspector, schema: Optional[str] + ) -> List[str]: + tables = super().get_table_names(database, inspector, schema) + if not is_feature_enabled("PRESTO_SPLIT_VIEWS_FROM_TABLES"): + return tables + + views = set(cls.get_view_names(database, inspector, schema)) + actual_tables = set(tables) - views + return list(actual_tables) + + @classmethod + def get_view_names( + cls, database: "Database", inspector: Inspector, schema: Optional[str] + ) -> List[str]: """Returns an empty list get_table_names() function returns all table names and view names, and get_view_names() is not implemented in sqlalchemy_presto.py https://github.com/dropbox/PyHive/blob/e25fc8440a0686bbb7a5db5de7cb1a77bdb4167a/pyhive/sqlalchemy_presto.py """ - return [] + if not is_feature_enabled("PRESTO_SPLIT_VIEWS_FROM_TABLES"): + return [] + + if schema: + sql = "SELECT table_name FROM information_schema.views WHERE table_schema=%(schema)s" + params = {"schema": schema} + else: + sql = "SELECT table_name FROM information_schema.views" + params = {} + + engine = cls.get_engine(database, schema=schema) + with closing(engine.raw_connection()) as conn: + with closing(conn.cursor()) as cursor: + cursor.execute(sql, params) + results = cursor.fetchall() + + return [row[0] for row in results] @classmethod def _create_column_info(cls, name: str, data_type: str) -> dict: diff --git a/superset/db_engine_specs/sqlite.py b/superset/db_engine_specs/sqlite.py index 28c8843057c..ff7074b3409 100644 --- a/superset/db_engine_specs/sqlite.py +++ b/superset/db_engine_specs/sqlite.py @@ -16,13 +16,17 @@ # under the License. # pylint: disable=C,R,W from datetime import datetime -from typing import List +from typing import List, TYPE_CHECKING from sqlalchemy.engine.reflection import Inspector from superset.db_engine_specs.base import BaseEngineSpec from superset.utils import core as utils +if TYPE_CHECKING: + # prevent circular imports + from superset.models.core import Database + class SqliteEngineSpec(BaseEngineSpec): engine = "sqlite" @@ -79,6 +83,8 @@ class SqliteEngineSpec(BaseEngineSpec): return "'{}'".format(iso) @classmethod - def get_table_names(cls, inspector: Inspector, schema: str) -> List[str]: + def get_table_names( + cls, database: "Database", inspector: Inspector, schema: str + ) -> List[str]: """Need to disregard the schema for Sqlite""" return sorted(inspector.get_table_names()) diff --git a/superset/models/core.py b/superset/models/core.py index b31fb6710c5..200af496431 100755 --- a/superset/models/core.py +++ b/superset/models/core.py @@ -1063,7 +1063,7 @@ class Database(Model, AuditMixinNullable, ImportMixin): """ try: tables = self.db_engine_spec.get_table_names( - inspector=self.inspector, schema=schema + database=self, inspector=self.inspector, schema=schema ) return [ utils.DatasourceName(table=table, schema=schema) for table in tables @@ -1097,7 +1097,7 @@ class Database(Model, AuditMixinNullable, ImportMixin): """ try: views = self.db_engine_spec.get_view_names( - inspector=self.inspector, schema=schema + database=self, inspector=self.inspector, schema=schema ) return [utils.DatasourceName(table=view, schema=schema) for view in views] except Exception as e: diff --git a/superset/views/core.py b/superset/views/core.py index 36fb3ca7727..ad6c3babf57 100755 --- a/superset/views/core.py +++ b/superset/views/core.py @@ -1572,6 +1572,7 @@ class Superset(BaseSupersetView): for vn in views[:max_views] ] ) + table_options.sort(key=lambda value: value["label"]) payload = {"tableLength": len(tables) + len(views), "options": table_options} return json_success(json.dumps(payload)) diff --git a/tests/db_engine_specs_test.py b/tests/db_engine_specs_test.py index f6354c0eeac..ec6ff24f228 100644 --- a/tests/db_engine_specs_test.py +++ b/tests/db_engine_specs_test.py @@ -342,7 +342,9 @@ class DbEngineSpecsTestCase(SupersetTestCase): self.assertSetEqual(defined_grains, intersection, engine) def test_presto_get_view_names_return_empty_list(self): - self.assertEquals([], PrestoEngineSpec.get_view_names(mock.ANY, mock.ANY)) + self.assertEquals( + [], PrestoEngineSpec.get_view_names(mock.ANY, mock.ANY, mock.ANY) + ) def verify_presto_column(self, column, expected_results): inspector = mock.Mock() @@ -877,7 +879,9 @@ class DbEngineSpecsTestCase(SupersetTestCase): self.assertEqual("SELECT \nWHERE ds = '01-01-19' AND hour = 1", query_result) def test_hive_get_view_names_return_empty_list(self): - self.assertEquals([], HiveEngineSpec.get_view_names(mock.ANY, mock.ANY)) + self.assertEquals( + [], HiveEngineSpec.get_view_names(mock.ANY, mock.ANY, mock.ANY) + ) def test_bigquery_sqla_column_label(self): label = BigQueryEngineSpec.make_label_compatible(column("Col").name) @@ -952,7 +956,7 @@ class DbEngineSpecsTestCase(SupersetTestCase): ie. when try_remove_schema_from_table_name == True. """ base_result_expected = ["table", "table_2"] base_result = BaseEngineSpec.get_table_names( - schema="schema", inspector=inspector + database=mock.ANY, schema="schema", inspector=inspector ) self.assertListEqual(base_result_expected, base_result) @@ -960,7 +964,7 @@ class DbEngineSpecsTestCase(SupersetTestCase): ie. when try_remove_schema_from_table_name == False. """ pg_result_expected = ["schema.table", "table_2", "table_3"] pg_result = PostgresEngineSpec.get_table_names( - schema="schema", inspector=inspector + database=mock.ANY, schema="schema", inspector=inspector ) self.assertListEqual(pg_result_expected, pg_result)