2019-01-15 18:53:27 -05:00
|
|
|
# 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.
|
2018-08-06 18:30:13 -04:00
|
|
|
"""Unit tests for Superset"""
|
|
|
|
import json
|
2021-08-02 04:55:31 -04:00
|
|
|
from contextlib import contextmanager
|
2021-04-10 10:15:03 -04:00
|
|
|
from unittest import mock
|
2018-08-06 18:30:13 -04:00
|
|
|
|
2021-08-13 08:56:42 -04:00
|
|
|
import prison
|
2021-01-11 08:57:55 -05:00
|
|
|
import pytest
|
|
|
|
|
2022-06-21 07:22:39 -04:00
|
|
|
from superset import app, db
|
2022-07-22 08:14:42 -04:00
|
|
|
from superset.common.utils.query_cache_manager import QueryCacheManager
|
|
|
|
from superset.connectors.sqla.models import SqlaTable, SqlMetric, TableColumn
|
|
|
|
from superset.constants import CacheRegion
|
2023-06-18 21:32:32 -04:00
|
|
|
from superset.daos.exceptions import DatasourceNotFound, DatasourceTypeNotSupportedError
|
2021-01-25 18:09:03 -05:00
|
|
|
from superset.datasets.commands.exceptions import DatasetNotFoundError
|
2021-08-02 04:55:31 -04:00
|
|
|
from superset.exceptions import SupersetGenericDBErrorException
|
|
|
|
from superset.models.core import Database
|
2022-07-22 08:14:42 -04:00
|
|
|
from superset.utils.core import backend, get_example_default_schema
|
|
|
|
from superset.utils.database import get_example_database, get_main_database
|
2021-08-02 15:45:55 -04:00
|
|
|
from tests.integration_tests.base_tests import db_insert_temp_object, SupersetTestCase
|
2021-07-01 11:03:07 -04:00
|
|
|
from tests.integration_tests.fixtures.birth_names_dashboard import (
|
|
|
|
load_birth_names_dashboard_with_slices,
|
2021-12-16 19:11:47 -05:00
|
|
|
load_birth_names_data,
|
2021-07-01 11:03:07 -04:00
|
|
|
)
|
2021-08-02 15:45:55 -04:00
|
|
|
from tests.integration_tests.fixtures.datasource import get_datasource_post
|
2018-08-06 18:30:13 -04:00
|
|
|
|
|
|
|
|
2021-08-02 04:55:31 -04:00
|
|
|
@contextmanager
|
|
|
|
def create_test_table_context(database: Database):
|
2021-11-04 14:09:08 -04:00
|
|
|
schema = get_example_default_schema()
|
|
|
|
full_table_name = f"{schema}.test_table" if schema else "test_table"
|
|
|
|
|
2022-10-25 14:12:48 -04:00
|
|
|
with database.get_sqla_engine_with_context() as engine:
|
|
|
|
engine.execute(
|
|
|
|
f"CREATE TABLE IF NOT EXISTS {full_table_name} AS SELECT 1 as first, 2 as second"
|
|
|
|
)
|
|
|
|
engine.execute(f"INSERT INTO {full_table_name} (first, second) VALUES (1, 2)")
|
|
|
|
engine.execute(f"INSERT INTO {full_table_name} (first, second) VALUES (3, 4)")
|
2021-08-02 04:55:31 -04:00
|
|
|
|
|
|
|
yield db.session
|
2022-10-25 14:12:48 -04:00
|
|
|
|
|
|
|
with database.get_sqla_engine_with_context() as engine:
|
|
|
|
engine.execute(f"DROP TABLE {full_table_name}")
|
2021-08-02 04:55:31 -04:00
|
|
|
|
|
|
|
|
2020-06-29 18:36:06 -04:00
|
|
|
class TestDatasource(SupersetTestCase):
|
2021-01-11 08:57:55 -05:00
|
|
|
def setUp(self):
|
2021-08-02 15:45:55 -04:00
|
|
|
db.session.begin(subtransactions=True)
|
2021-01-11 08:57:55 -05:00
|
|
|
|
|
|
|
def tearDown(self):
|
2021-08-02 15:45:55 -04:00
|
|
|
db.session.rollback()
|
2021-01-11 08:57:55 -05:00
|
|
|
|
|
|
|
@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
|
2020-10-27 01:58:38 -04:00
|
|
|
def test_external_metadata_for_physical_table(self):
|
2019-06-25 16:34:48 -04:00
|
|
|
self.login(username="admin")
|
2021-08-02 15:45:55 -04:00
|
|
|
tbl = self.get_table(name="birth_names")
|
2020-10-27 01:58:38 -04:00
|
|
|
url = f"/datasource/external_metadata/table/{tbl.id}/"
|
2018-08-06 18:30:13 -04:00
|
|
|
resp = self.get_json_resp(url)
|
2023-06-20 13:54:19 -04:00
|
|
|
col_names = {o.get("column_name") for o in resp}
|
2019-10-21 10:49:12 -04:00
|
|
|
self.assertEqual(
|
2021-01-08 17:13:20 -05:00
|
|
|
col_names, {"num_boys", "num", "gender", "name", "ds", "state", "num_girls"}
|
2018-08-06 18:30:13 -04:00
|
|
|
)
|
|
|
|
|
2020-10-27 01:58:38 -04:00
|
|
|
def test_external_metadata_for_virtual_table(self):
|
|
|
|
self.login(username="admin")
|
|
|
|
session = db.session
|
|
|
|
table = SqlaTable(
|
|
|
|
table_name="dummy_sql_table",
|
|
|
|
database=get_example_database(),
|
2021-11-04 14:09:08 -04:00
|
|
|
schema=get_example_default_schema(),
|
2020-10-27 01:58:38 -04:00
|
|
|
sql="select 123 as intcol, 'abc' as strcol",
|
|
|
|
)
|
|
|
|
session.add(table)
|
|
|
|
session.commit()
|
|
|
|
|
2021-08-02 15:45:55 -04:00
|
|
|
table = self.get_table(name="dummy_sql_table")
|
2020-10-27 01:58:38 -04:00
|
|
|
url = f"/datasource/external_metadata/table/{table.id}/"
|
|
|
|
resp = self.get_json_resp(url)
|
2023-06-20 13:54:19 -04:00
|
|
|
assert {o.get("column_name") for o in resp} == {"intcol", "strcol"}
|
2020-10-27 01:58:38 -04:00
|
|
|
session.delete(table)
|
|
|
|
session.commit()
|
|
|
|
|
2021-08-02 04:55:31 -04:00
|
|
|
@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
|
|
|
|
def test_external_metadata_by_name_for_physical_table(self):
|
|
|
|
self.login(username="admin")
|
2021-08-02 15:45:55 -04:00
|
|
|
tbl = self.get_table(name="birth_names")
|
2021-08-13 08:56:42 -04:00
|
|
|
params = prison.dumps(
|
|
|
|
{
|
|
|
|
"datasource_type": "table",
|
|
|
|
"database_name": tbl.database.database_name,
|
|
|
|
"schema_name": tbl.schema,
|
|
|
|
"table_name": tbl.table_name,
|
|
|
|
}
|
2021-08-02 04:55:31 -04:00
|
|
|
)
|
2021-08-13 08:56:42 -04:00
|
|
|
url = f"/datasource/external_metadata_by_name/?q={params}"
|
2021-08-02 04:55:31 -04:00
|
|
|
resp = self.get_json_resp(url)
|
2023-06-20 13:54:19 -04:00
|
|
|
col_names = {o.get("column_name") for o in resp}
|
2021-08-02 04:55:31 -04:00
|
|
|
self.assertEqual(
|
|
|
|
col_names, {"num_boys", "num", "gender", "name", "ds", "state", "num_girls"}
|
|
|
|
)
|
|
|
|
|
|
|
|
def test_external_metadata_by_name_for_virtual_table(self):
|
|
|
|
self.login(username="admin")
|
|
|
|
session = db.session
|
|
|
|
table = SqlaTable(
|
|
|
|
table_name="dummy_sql_table",
|
|
|
|
database=get_example_database(),
|
2021-11-04 14:09:08 -04:00
|
|
|
schema=get_example_default_schema(),
|
2021-08-02 04:55:31 -04:00
|
|
|
sql="select 123 as intcol, 'abc' as strcol",
|
|
|
|
)
|
|
|
|
session.add(table)
|
|
|
|
session.commit()
|
|
|
|
|
2021-08-13 08:56:42 -04:00
|
|
|
tbl = self.get_table(name="dummy_sql_table")
|
|
|
|
params = prison.dumps(
|
|
|
|
{
|
|
|
|
"datasource_type": "table",
|
|
|
|
"database_name": tbl.database.database_name,
|
|
|
|
"schema_name": tbl.schema,
|
|
|
|
"table_name": tbl.table_name,
|
|
|
|
}
|
2021-08-02 04:55:31 -04:00
|
|
|
)
|
2021-08-13 08:56:42 -04:00
|
|
|
url = f"/datasource/external_metadata_by_name/?q={params}"
|
2021-08-02 04:55:31 -04:00
|
|
|
resp = self.get_json_resp(url)
|
2023-06-20 13:54:19 -04:00
|
|
|
assert {o.get("column_name") for o in resp} == {"intcol", "strcol"}
|
2021-08-13 08:56:42 -04:00
|
|
|
session.delete(tbl)
|
2021-08-02 04:55:31 -04:00
|
|
|
session.commit()
|
|
|
|
|
|
|
|
def test_external_metadata_by_name_from_sqla_inspector(self):
|
|
|
|
self.login(username="admin")
|
|
|
|
example_database = get_example_database()
|
|
|
|
with create_test_table_context(example_database):
|
2021-08-13 08:56:42 -04:00
|
|
|
params = prison.dumps(
|
|
|
|
{
|
|
|
|
"datasource_type": "table",
|
|
|
|
"database_name": example_database.database_name,
|
|
|
|
"table_name": "test_table",
|
2021-11-04 14:09:08 -04:00
|
|
|
"schema_name": get_example_default_schema(),
|
2021-08-13 08:56:42 -04:00
|
|
|
}
|
2021-08-02 04:55:31 -04:00
|
|
|
)
|
2021-08-13 08:56:42 -04:00
|
|
|
url = f"/datasource/external_metadata_by_name/?q={params}"
|
2021-08-02 04:55:31 -04:00
|
|
|
resp = self.get_json_resp(url)
|
2023-06-20 13:54:19 -04:00
|
|
|
col_names = {o.get("column_name") for o in resp}
|
2021-08-02 04:55:31 -04:00
|
|
|
self.assertEqual(col_names, {"first", "second"})
|
|
|
|
|
2021-08-13 08:56:42 -04:00
|
|
|
# No databases found
|
|
|
|
params = prison.dumps(
|
2022-03-29 13:03:09 -04:00
|
|
|
{
|
|
|
|
"datasource_type": "table",
|
|
|
|
"database_name": "foo",
|
|
|
|
"table_name": "bar",
|
|
|
|
}
|
2021-08-13 08:56:42 -04:00
|
|
|
)
|
|
|
|
url = f"/datasource/external_metadata_by_name/?q={params}"
|
|
|
|
resp = self.client.get(url)
|
|
|
|
self.assertEqual(resp.status_code, DatasetNotFoundError.status)
|
|
|
|
self.assertEqual(
|
|
|
|
json.loads(resp.data.decode("utf-8")).get("error"),
|
|
|
|
DatasetNotFoundError.message,
|
|
|
|
)
|
|
|
|
|
|
|
|
# No table found
|
|
|
|
params = prison.dumps(
|
|
|
|
{
|
|
|
|
"datasource_type": "table",
|
|
|
|
"database_name": example_database.database_name,
|
|
|
|
"table_name": "fooooooooobarrrrrr",
|
|
|
|
}
|
|
|
|
)
|
|
|
|
url = f"/datasource/external_metadata_by_name/?q={params}"
|
|
|
|
resp = self.client.get(url)
|
|
|
|
self.assertEqual(resp.status_code, DatasetNotFoundError.status)
|
|
|
|
self.assertEqual(
|
|
|
|
json.loads(resp.data.decode("utf-8")).get("error"),
|
|
|
|
DatasetNotFoundError.message,
|
2021-08-02 04:55:31 -04:00
|
|
|
)
|
2021-08-13 08:56:42 -04:00
|
|
|
|
|
|
|
# invalid query params
|
2022-03-29 13:03:09 -04:00
|
|
|
params = prison.dumps(
|
|
|
|
{
|
|
|
|
"datasource_type": "table",
|
|
|
|
}
|
|
|
|
)
|
2021-08-13 08:56:42 -04:00
|
|
|
url = f"/datasource/external_metadata_by_name/?q={params}"
|
|
|
|
resp = self.get_json_resp(url)
|
2021-08-02 04:55:31 -04:00
|
|
|
self.assertIn("error", resp)
|
|
|
|
|
2021-06-04 21:12:22 -04:00
|
|
|
def test_external_metadata_for_virtual_table_template_params(self):
|
|
|
|
self.login(username="admin")
|
|
|
|
session = db.session
|
|
|
|
table = SqlaTable(
|
|
|
|
table_name="dummy_sql_table_with_template_params",
|
|
|
|
database=get_example_database(),
|
2021-11-04 14:09:08 -04:00
|
|
|
schema=get_example_default_schema(),
|
2021-06-04 21:12:22 -04:00
|
|
|
sql="select {{ foo }} as intcol",
|
|
|
|
template_params=json.dumps({"foo": "123"}),
|
|
|
|
)
|
|
|
|
session.add(table)
|
|
|
|
session.commit()
|
|
|
|
|
2021-08-02 15:45:55 -04:00
|
|
|
table = self.get_table(name="dummy_sql_table_with_template_params")
|
2021-06-04 21:12:22 -04:00
|
|
|
url = f"/datasource/external_metadata/table/{table.id}/"
|
|
|
|
resp = self.get_json_resp(url)
|
2023-06-20 13:54:19 -04:00
|
|
|
assert {o.get("column_name") for o in resp} == {"intcol"}
|
2021-06-04 21:12:22 -04:00
|
|
|
session.delete(table)
|
|
|
|
session.commit()
|
|
|
|
|
2020-10-27 01:58:38 -04:00
|
|
|
def test_external_metadata_for_malicious_virtual_table(self):
|
|
|
|
self.login(username="admin")
|
|
|
|
table = SqlaTable(
|
|
|
|
table_name="malicious_sql_table",
|
|
|
|
database=get_example_database(),
|
2021-11-04 14:09:08 -04:00
|
|
|
schema=get_example_default_schema(),
|
2020-10-27 01:58:38 -04:00
|
|
|
sql="delete table birth_names",
|
|
|
|
)
|
2021-01-25 18:09:03 -05:00
|
|
|
with db_insert_temp_object(table):
|
|
|
|
url = f"/datasource/external_metadata/table/{table.id}/"
|
|
|
|
resp = self.get_json_resp(url)
|
|
|
|
self.assertEqual(resp["error"], "Only `SELECT` statements are allowed")
|
2020-10-27 01:58:38 -04:00
|
|
|
|
2023-01-25 18:35:08 -05:00
|
|
|
def test_external_metadata_for_multistatement_virtual_table(self):
|
2020-10-27 01:58:38 -04:00
|
|
|
self.login(username="admin")
|
|
|
|
table = SqlaTable(
|
|
|
|
table_name="multistatement_sql_table",
|
|
|
|
database=get_example_database(),
|
2021-11-04 14:09:08 -04:00
|
|
|
schema=get_example_default_schema(),
|
2020-10-27 01:58:38 -04:00
|
|
|
sql="select 123 as intcol, 'abc' as strcol;"
|
|
|
|
"select 123 as intcol, 'abc' as strcol",
|
|
|
|
)
|
2021-01-25 18:09:03 -05:00
|
|
|
with db_insert_temp_object(table):
|
|
|
|
url = f"/datasource/external_metadata/table/{table.id}/"
|
|
|
|
resp = self.get_json_resp(url)
|
|
|
|
self.assertEqual(resp["error"], "Only single queries supported")
|
2020-10-27 01:58:38 -04:00
|
|
|
|
2021-04-10 10:15:03 -04:00
|
|
|
@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
|
|
|
|
@mock.patch("superset.connectors.sqla.models.SqlaTable.external_metadata")
|
|
|
|
def test_external_metadata_error_return_400(self, mock_get_datasource):
|
|
|
|
self.login(username="admin")
|
2021-08-02 15:45:55 -04:00
|
|
|
tbl = self.get_table(name="birth_names")
|
2021-04-10 10:15:03 -04:00
|
|
|
url = f"/datasource/external_metadata/table/{tbl.id}/"
|
|
|
|
|
|
|
|
mock_get_datasource.side_effect = SupersetGenericDBErrorException("oops")
|
|
|
|
|
|
|
|
pytest.raises(
|
|
|
|
SupersetGenericDBErrorException,
|
2022-06-21 07:22:39 -04:00
|
|
|
lambda: db.session.query(SqlaTable)
|
|
|
|
.filter_by(id=tbl.id)
|
|
|
|
.one_or_none()
|
|
|
|
.external_metadata(),
|
2021-04-10 10:15:03 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
resp = self.client.get(url)
|
|
|
|
assert resp.status_code == 400
|
|
|
|
|
2018-08-06 18:30:13 -04:00
|
|
|
def compare_lists(self, l1, l2, key):
|
|
|
|
l2_lookup = {o.get(key): o for o in l2}
|
|
|
|
for obj1 in l1:
|
|
|
|
obj2 = l2_lookup.get(obj1.get(key))
|
|
|
|
for k in obj1:
|
2019-06-25 16:34:48 -04:00
|
|
|
if k not in "id" and obj1.get(k):
|
2019-10-21 10:49:12 -04:00
|
|
|
self.assertEqual(obj1.get(k), obj2.get(k))
|
2018-08-06 18:30:13 -04:00
|
|
|
|
|
|
|
def test_save(self):
|
2019-06-25 16:34:48 -04:00
|
|
|
self.login(username="admin")
|
2021-08-02 15:45:55 -04:00
|
|
|
tbl_id = self.get_table(name="birth_names").id
|
2021-01-11 08:57:55 -05:00
|
|
|
|
2021-08-02 15:45:55 -04:00
|
|
|
datasource_post = get_datasource_post()
|
2019-06-25 16:34:48 -04:00
|
|
|
datasource_post["id"] = tbl_id
|
2022-04-29 17:21:05 -04:00
|
|
|
datasource_post["owners"] = [1]
|
2018-08-06 18:30:13 -04:00
|
|
|
data = dict(data=json.dumps(datasource_post))
|
2019-06-25 16:34:48 -04:00
|
|
|
resp = self.get_json_resp("/datasource/save/", data)
|
2018-08-06 18:30:13 -04:00
|
|
|
for k in datasource_post:
|
2019-06-25 16:34:48 -04:00
|
|
|
if k == "columns":
|
|
|
|
self.compare_lists(datasource_post[k], resp[k], "column_name")
|
|
|
|
elif k == "metrics":
|
|
|
|
self.compare_lists(datasource_post[k], resp[k], "metric_name")
|
2020-03-10 12:20:37 -04:00
|
|
|
elif k == "database":
|
|
|
|
self.assertEqual(resp[k]["id"], datasource_post[k]["id"])
|
2022-07-20 14:56:08 -04:00
|
|
|
elif k == "owners":
|
|
|
|
self.assertEqual([o["id"] for o in resp[k]], datasource_post["owners"])
|
2018-08-06 18:30:13 -04:00
|
|
|
else:
|
2021-11-04 14:09:08 -04:00
|
|
|
print(k)
|
2019-10-21 10:49:12 -04:00
|
|
|
self.assertEqual(resp[k], datasource_post[k])
|
2019-02-20 17:32:33 -05:00
|
|
|
|
2022-11-07 05:33:24 -05:00
|
|
|
def test_save_default_endpoint_validation_fail(self):
|
|
|
|
self.login(username="admin")
|
|
|
|
tbl_id = self.get_table(name="birth_names").id
|
|
|
|
|
|
|
|
datasource_post = get_datasource_post()
|
|
|
|
datasource_post["id"] = tbl_id
|
|
|
|
datasource_post["owners"] = [1]
|
|
|
|
datasource_post["default_endpoint"] = "http://www.google.com"
|
|
|
|
data = dict(data=json.dumps(datasource_post))
|
|
|
|
resp = self.client.post("/datasource/save/", data=data)
|
|
|
|
assert resp.status_code == 400
|
|
|
|
|
|
|
|
def test_save_default_endpoint_validation_unsafe(self):
|
|
|
|
self.app.config["PREVENT_UNSAFE_DEFAULT_URLS_ON_DATASET"] = False
|
|
|
|
self.login(username="admin")
|
|
|
|
tbl_id = self.get_table(name="birth_names").id
|
|
|
|
|
|
|
|
datasource_post = get_datasource_post()
|
|
|
|
datasource_post["id"] = tbl_id
|
|
|
|
datasource_post["owners"] = [1]
|
|
|
|
datasource_post["default_endpoint"] = "http://www.google.com"
|
|
|
|
data = dict(data=json.dumps(datasource_post))
|
|
|
|
resp = self.client.post("/datasource/save/", data=data)
|
|
|
|
assert resp.status_code == 200
|
|
|
|
self.app.config["PREVENT_UNSAFE_DEFAULT_URLS_ON_DATASET"] = True
|
|
|
|
|
|
|
|
def test_save_default_endpoint_validation_success(self):
|
|
|
|
self.login(username="admin")
|
|
|
|
tbl_id = self.get_table(name="birth_names").id
|
|
|
|
|
|
|
|
datasource_post = get_datasource_post()
|
|
|
|
datasource_post["id"] = tbl_id
|
|
|
|
datasource_post["owners"] = [1]
|
|
|
|
datasource_post["default_endpoint"] = "http://localhost/superset/1"
|
|
|
|
data = dict(data=json.dumps(datasource_post))
|
|
|
|
resp = self.client.post("/datasource/save/", data=data)
|
|
|
|
assert resp.status_code == 200
|
|
|
|
|
2021-08-02 15:45:55 -04:00
|
|
|
def save_datasource_from_dict(self, datasource_post):
|
2020-03-10 12:20:37 -04:00
|
|
|
data = dict(data=json.dumps(datasource_post))
|
|
|
|
resp = self.get_json_resp("/datasource/save/", data)
|
|
|
|
return resp
|
|
|
|
|
2021-08-02 15:45:55 -04:00
|
|
|
@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
|
2020-03-10 12:20:37 -04:00
|
|
|
def test_change_database(self):
|
|
|
|
self.login(username="admin")
|
2022-04-29 17:21:05 -04:00
|
|
|
admin_user = self.get_user("admin")
|
|
|
|
|
2021-08-02 15:45:55 -04:00
|
|
|
tbl = self.get_table(name="birth_names")
|
2020-03-10 12:20:37 -04:00
|
|
|
tbl_id = tbl.id
|
|
|
|
db_id = tbl.database_id
|
2021-08-02 15:45:55 -04:00
|
|
|
datasource_post = get_datasource_post()
|
2020-03-10 12:20:37 -04:00
|
|
|
datasource_post["id"] = tbl_id
|
2022-04-29 17:21:05 -04:00
|
|
|
datasource_post["owners"] = [admin_user.id]
|
2020-03-10 12:20:37 -04:00
|
|
|
|
|
|
|
new_db = self.create_fake_db()
|
|
|
|
datasource_post["database"]["id"] = new_db.id
|
|
|
|
resp = self.save_datasource_from_dict(datasource_post)
|
2020-06-29 19:38:06 -04:00
|
|
|
self.assertEqual(resp["database"]["id"], new_db.id)
|
2020-03-10 12:20:37 -04:00
|
|
|
|
|
|
|
datasource_post["database"]["id"] = db_id
|
|
|
|
resp = self.save_datasource_from_dict(datasource_post)
|
2020-06-29 19:38:06 -04:00
|
|
|
self.assertEqual(resp["database"]["id"], db_id)
|
2020-03-10 12:20:37 -04:00
|
|
|
|
|
|
|
self.delete_fake_db()
|
|
|
|
|
2019-12-04 14:05:06 -05:00
|
|
|
def test_save_duplicate_key(self):
|
|
|
|
self.login(username="admin")
|
2022-04-29 17:21:05 -04:00
|
|
|
admin_user = self.get_user("admin")
|
2021-08-02 15:45:55 -04:00
|
|
|
tbl_id = self.get_table(name="birth_names").id
|
2021-01-11 08:57:55 -05:00
|
|
|
|
2021-08-02 15:45:55 -04:00
|
|
|
datasource_post = get_datasource_post()
|
|
|
|
datasource_post["id"] = tbl_id
|
2022-04-29 17:21:05 -04:00
|
|
|
datasource_post["owners"] = [admin_user.id]
|
2021-08-02 15:45:55 -04:00
|
|
|
datasource_post["columns"].extend(
|
2019-12-04 14:05:06 -05:00
|
|
|
[
|
|
|
|
{
|
|
|
|
"column_name": "<new column>",
|
|
|
|
"filterable": True,
|
|
|
|
"groupby": True,
|
|
|
|
"expression": "<enter SQL expression here>",
|
|
|
|
"id": "somerandomid",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"column_name": "<new column>",
|
|
|
|
"filterable": True,
|
|
|
|
"groupby": True,
|
|
|
|
"expression": "<enter SQL expression here>",
|
|
|
|
"id": "somerandomid2",
|
|
|
|
},
|
|
|
|
]
|
|
|
|
)
|
2021-08-02 15:45:55 -04:00
|
|
|
data = dict(data=json.dumps(datasource_post))
|
2019-12-04 14:05:06 -05:00
|
|
|
resp = self.get_json_resp("/datasource/save/", data, raise_on_error=False)
|
|
|
|
self.assertIn("Duplicate column name(s): <new column>", resp["error"])
|
|
|
|
|
2019-02-20 17:32:33 -05:00
|
|
|
def test_get_datasource(self):
|
2019-06-25 16:34:48 -04:00
|
|
|
self.login(username="admin")
|
2022-04-29 17:21:05 -04:00
|
|
|
admin_user = self.get_user("admin")
|
2021-08-02 15:45:55 -04:00
|
|
|
tbl = self.get_table(name="birth_names")
|
2021-01-11 08:57:55 -05:00
|
|
|
|
2021-08-02 15:45:55 -04:00
|
|
|
datasource_post = get_datasource_post()
|
2021-01-11 08:57:55 -05:00
|
|
|
datasource_post["id"] = tbl.id
|
2022-04-29 17:21:05 -04:00
|
|
|
datasource_post["owners"] = [admin_user.id]
|
2021-01-11 08:57:55 -05:00
|
|
|
data = dict(data=json.dumps(datasource_post))
|
|
|
|
self.get_json_resp("/datasource/save/", data)
|
2019-06-25 16:34:48 -04:00
|
|
|
url = f"/datasource/get/{tbl.type}/{tbl.id}/"
|
2019-02-20 17:32:33 -05:00
|
|
|
resp = self.get_json_resp(url)
|
2019-10-21 10:49:12 -04:00
|
|
|
self.assertEqual(resp.get("type"), "table")
|
2019-06-25 16:34:48 -04:00
|
|
|
col_names = {o.get("column_name") for o in resp["columns"]}
|
2019-10-21 10:49:12 -04:00
|
|
|
self.assertEqual(
|
2019-02-20 17:32:33 -05:00
|
|
|
col_names,
|
2019-06-25 16:34:48 -04:00
|
|
|
{
|
2021-01-08 17:13:20 -05:00
|
|
|
"num_boys",
|
2019-06-25 16:34:48 -04:00
|
|
|
"num",
|
|
|
|
"gender",
|
|
|
|
"name",
|
|
|
|
"ds",
|
|
|
|
"state",
|
2021-01-08 17:13:20 -05:00
|
|
|
"num_girls",
|
2019-06-25 16:34:48 -04:00
|
|
|
"num_california",
|
|
|
|
},
|
2019-02-20 17:32:33 -05:00
|
|
|
)
|
|
|
|
|
2020-12-15 21:12:06 -05:00
|
|
|
def test_get_datasource_with_health_check(self):
|
|
|
|
def my_check(datasource):
|
|
|
|
return "Warning message!"
|
|
|
|
|
|
|
|
app.config["DATASET_HEALTH_CHECK"] = my_check
|
|
|
|
self.login(username="admin")
|
2021-08-02 15:45:55 -04:00
|
|
|
tbl = self.get_table(name="birth_names")
|
2022-06-21 07:22:39 -04:00
|
|
|
datasource = db.session.query(SqlaTable).filter_by(id=tbl.id).one_or_none()
|
2021-04-08 23:21:58 -04:00
|
|
|
assert datasource.health_check_message == "Warning message!"
|
|
|
|
app.config["DATASET_HEALTH_CHECK"] = None
|
2020-12-15 21:12:06 -05:00
|
|
|
|
2019-02-20 17:32:33 -05:00
|
|
|
def test_get_datasource_failed(self):
|
2023-06-18 21:32:32 -04:00
|
|
|
from superset.daos.datasource import DatasourceDAO
|
2022-06-21 07:22:39 -04:00
|
|
|
|
2021-01-25 18:09:03 -05:00
|
|
|
pytest.raises(
|
2022-06-21 07:22:39 -04:00
|
|
|
DatasourceNotFound,
|
|
|
|
lambda: DatasourceDAO.get_datasource(db.session, "table", 9999999),
|
2021-01-25 18:09:03 -05:00
|
|
|
)
|
|
|
|
|
2019-06-25 16:34:48 -04:00
|
|
|
self.login(username="admin")
|
2022-06-21 07:22:39 -04:00
|
|
|
resp = self.get_json_resp("/datasource/get/table/500000/", raise_on_error=False)
|
|
|
|
self.assertEqual(resp.get("error"), "Datasource does not exist")
|
2021-01-25 18:09:03 -05:00
|
|
|
|
2022-06-21 07:22:39 -04:00
|
|
|
def test_get_datasource_invalid_datasource_failed(self):
|
2023-06-18 21:32:32 -04:00
|
|
|
from superset.daos.datasource import DatasourceDAO
|
2022-06-21 07:22:39 -04:00
|
|
|
|
|
|
|
pytest.raises(
|
|
|
|
DatasourceTypeNotSupportedError,
|
|
|
|
lambda: DatasourceDAO.get_datasource(db.session, "druid", 9999999),
|
2021-01-25 18:09:03 -05:00
|
|
|
)
|
2022-06-21 07:22:39 -04:00
|
|
|
|
|
|
|
self.login(username="admin")
|
|
|
|
resp = self.get_json_resp("/datasource/get/druid/500000/", raise_on_error=False)
|
|
|
|
self.assertEqual(resp.get("error"), "'druid' is not a valid DatasourceType")
|
2022-07-22 08:14:42 -04:00
|
|
|
|
|
|
|
|
|
|
|
def test_get_samples(test_client, login_as_admin, virtual_dataset):
|
|
|
|
"""
|
|
|
|
Dataset API: Test get dataset samples
|
|
|
|
"""
|
|
|
|
# 1. should cache data
|
|
|
|
uri = (
|
|
|
|
f"/datasource/samples?datasource_id={virtual_dataset.id}&datasource_type=table"
|
|
|
|
)
|
|
|
|
# feeds data
|
2023-01-09 05:10:31 -05:00
|
|
|
test_client.post(uri, json={})
|
2022-07-22 08:14:42 -04:00
|
|
|
# get from cache
|
2023-01-09 05:10:31 -05:00
|
|
|
rv = test_client.post(uri, json={})
|
2022-07-22 08:14:42 -04:00
|
|
|
assert rv.status_code == 200
|
2022-08-08 10:42:14 -04:00
|
|
|
assert len(rv.json["result"]["data"]) == 10
|
2022-07-22 08:14:42 -04:00
|
|
|
assert QueryCacheManager.has(
|
2022-08-08 10:42:14 -04:00
|
|
|
rv.json["result"]["cache_key"],
|
2022-07-22 08:14:42 -04:00
|
|
|
region=CacheRegion.DATA,
|
|
|
|
)
|
2022-08-08 10:42:14 -04:00
|
|
|
assert rv.json["result"]["is_cached"]
|
2022-07-22 08:14:42 -04:00
|
|
|
|
|
|
|
# 2. should read through cache data
|
|
|
|
uri2 = f"/datasource/samples?datasource_id={virtual_dataset.id}&datasource_type=table&force=true"
|
|
|
|
# feeds data
|
2023-01-09 05:10:31 -05:00
|
|
|
test_client.post(uri2, json={})
|
2022-07-22 08:14:42 -04:00
|
|
|
# force query
|
2023-01-09 05:10:31 -05:00
|
|
|
rv2 = test_client.post(uri2, json={})
|
2022-07-22 08:14:42 -04:00
|
|
|
assert rv2.status_code == 200
|
2022-08-08 10:42:14 -04:00
|
|
|
assert len(rv2.json["result"]["data"]) == 10
|
2022-07-22 08:14:42 -04:00
|
|
|
assert QueryCacheManager.has(
|
2022-08-08 10:42:14 -04:00
|
|
|
rv2.json["result"]["cache_key"],
|
2022-07-22 08:14:42 -04:00
|
|
|
region=CacheRegion.DATA,
|
|
|
|
)
|
2022-08-08 10:42:14 -04:00
|
|
|
assert not rv2.json["result"]["is_cached"]
|
2022-07-22 08:14:42 -04:00
|
|
|
|
|
|
|
# 3. data precision
|
2022-08-08 10:42:14 -04:00
|
|
|
assert "colnames" in rv2.json["result"]
|
|
|
|
assert "coltypes" in rv2.json["result"]
|
|
|
|
assert "data" in rv2.json["result"]
|
2022-07-22 08:14:42 -04:00
|
|
|
|
|
|
|
eager_samples = virtual_dataset.database.get_df(
|
|
|
|
f"select * from ({virtual_dataset.sql}) as tbl"
|
|
|
|
f' limit {app.config["SAMPLES_ROW_LIMIT"]}'
|
|
|
|
)
|
|
|
|
# the col3 is Decimal
|
|
|
|
eager_samples["col3"] = eager_samples["col3"].apply(float)
|
|
|
|
eager_samples = eager_samples.to_dict(orient="records")
|
2022-08-08 10:42:14 -04:00
|
|
|
assert eager_samples == rv2.json["result"]["data"]
|
2022-07-22 08:14:42 -04:00
|
|
|
|
|
|
|
|
|
|
|
def test_get_samples_with_incorrect_cc(test_client, login_as_admin, virtual_dataset):
|
|
|
|
TableColumn(
|
|
|
|
column_name="DUMMY CC",
|
|
|
|
type="VARCHAR(255)",
|
|
|
|
table=virtual_dataset,
|
|
|
|
expression="INCORRECT SQL",
|
|
|
|
)
|
|
|
|
db.session.merge(virtual_dataset)
|
|
|
|
|
|
|
|
uri = (
|
|
|
|
f"/datasource/samples?datasource_id={virtual_dataset.id}&datasource_type=table"
|
|
|
|
)
|
2023-01-09 05:10:31 -05:00
|
|
|
rv = test_client.post(uri, json={})
|
2022-07-22 08:14:42 -04:00
|
|
|
assert rv.status_code == 422
|
|
|
|
|
2022-08-08 10:42:14 -04:00
|
|
|
assert "error" in rv.json
|
2022-07-22 08:14:42 -04:00
|
|
|
if virtual_dataset.database.db_engine_spec.engine_name == "PostgreSQL":
|
2022-08-08 10:42:14 -04:00
|
|
|
assert "INCORRECT SQL" in rv.json.get("error")
|
2022-07-22 08:14:42 -04:00
|
|
|
|
|
|
|
|
|
|
|
def test_get_samples_on_physical_dataset(test_client, login_as_admin, physical_dataset):
|
|
|
|
uri = (
|
|
|
|
f"/datasource/samples?datasource_id={physical_dataset.id}&datasource_type=table"
|
|
|
|
)
|
2023-01-09 05:10:31 -05:00
|
|
|
rv = test_client.post(uri, json={})
|
2022-07-22 08:14:42 -04:00
|
|
|
assert rv.status_code == 200
|
|
|
|
assert QueryCacheManager.has(
|
2022-08-08 10:42:14 -04:00
|
|
|
rv.json["result"]["cache_key"], region=CacheRegion.DATA
|
2022-07-22 08:14:42 -04:00
|
|
|
)
|
2022-08-08 10:42:14 -04:00
|
|
|
assert len(rv.json["result"]["data"]) == 10
|
2022-07-22 08:14:42 -04:00
|
|
|
|
|
|
|
|
|
|
|
def test_get_samples_with_filters(test_client, login_as_admin, virtual_dataset):
|
|
|
|
uri = (
|
|
|
|
f"/datasource/samples?datasource_id={virtual_dataset.id}&datasource_type=table"
|
|
|
|
)
|
|
|
|
rv = test_client.post(uri, json=None)
|
2023-05-12 10:01:30 -04:00
|
|
|
assert rv.status_code == 415
|
2022-07-22 08:14:42 -04:00
|
|
|
|
|
|
|
rv = test_client.post(uri, json={})
|
|
|
|
assert rv.status_code == 200
|
|
|
|
|
|
|
|
rv = test_client.post(uri, json={"foo": "bar"})
|
|
|
|
assert rv.status_code == 400
|
|
|
|
|
|
|
|
rv = test_client.post(
|
|
|
|
uri, json={"filters": [{"col": "col1", "op": "INVALID", "val": 0}]}
|
|
|
|
)
|
|
|
|
assert rv.status_code == 400
|
|
|
|
|
|
|
|
rv = test_client.post(
|
|
|
|
uri,
|
|
|
|
json={
|
|
|
|
"filters": [
|
|
|
|
{"col": "col2", "op": "==", "val": "a"},
|
|
|
|
{"col": "col1", "op": "==", "val": 0},
|
|
|
|
]
|
|
|
|
},
|
|
|
|
)
|
|
|
|
assert rv.status_code == 200
|
2022-08-08 10:42:14 -04:00
|
|
|
assert rv.json["result"]["colnames"] == ["col1", "col2", "col3", "col4", "col5"]
|
|
|
|
assert rv.json["result"]["rowcount"] == 1
|
2022-07-22 08:14:42 -04:00
|
|
|
|
|
|
|
# empty results
|
|
|
|
rv = test_client.post(
|
|
|
|
uri,
|
|
|
|
json={
|
|
|
|
"filters": [
|
|
|
|
{"col": "col2", "op": "==", "val": "x"},
|
|
|
|
]
|
|
|
|
},
|
|
|
|
)
|
|
|
|
assert rv.status_code == 200
|
2022-08-08 10:42:14 -04:00
|
|
|
assert rv.json["result"]["colnames"] == []
|
|
|
|
assert rv.json["result"]["rowcount"] == 0
|
|
|
|
|
|
|
|
|
|
|
|
def test_get_samples_with_time_filter(test_client, login_as_admin, physical_dataset):
|
|
|
|
uri = (
|
|
|
|
f"/datasource/samples?datasource_id={physical_dataset.id}&datasource_type=table"
|
|
|
|
)
|
|
|
|
payload = {
|
|
|
|
"granularity": "col5",
|
|
|
|
"time_range": "2000-01-02 : 2000-01-04",
|
|
|
|
}
|
|
|
|
rv = test_client.post(uri, json=payload)
|
|
|
|
assert len(rv.json["result"]["data"]) == 2
|
|
|
|
if physical_dataset.database.backend != "sqlite":
|
|
|
|
assert [row["col5"] for row in rv.json["result"]["data"]] == [
|
|
|
|
946771200000.0, # 2000-01-02 00:00:00
|
|
|
|
946857600000.0, # 2000-01-03 00:00:00
|
|
|
|
]
|
|
|
|
assert rv.json["result"]["page"] == 1
|
|
|
|
assert rv.json["result"]["per_page"] == app.config["SAMPLES_ROW_LIMIT"]
|
|
|
|
assert rv.json["result"]["total_count"] == 2
|
|
|
|
|
|
|
|
|
|
|
|
def test_get_samples_with_multiple_filters(
|
|
|
|
test_client, login_as_admin, physical_dataset
|
|
|
|
):
|
|
|
|
# 1. empty response
|
|
|
|
uri = (
|
|
|
|
f"/datasource/samples?datasource_id={physical_dataset.id}&datasource_type=table"
|
|
|
|
)
|
|
|
|
payload = {
|
|
|
|
"granularity": "col5",
|
|
|
|
"time_range": "2000-01-02 : 2000-01-04",
|
|
|
|
"filters": [
|
|
|
|
{"col": "col4", "op": "IS NOT NULL"},
|
|
|
|
],
|
|
|
|
}
|
|
|
|
rv = test_client.post(uri, json=payload)
|
|
|
|
assert len(rv.json["result"]["data"]) == 0
|
|
|
|
|
|
|
|
# 2. adhoc filters, time filters, and custom where
|
|
|
|
payload = {
|
|
|
|
"granularity": "col5",
|
|
|
|
"time_range": "2000-01-02 : 2000-01-04",
|
|
|
|
"filters": [
|
|
|
|
{"col": "col2", "op": "==", "val": "c"},
|
|
|
|
],
|
|
|
|
"extras": {"where": "col3 = 1.2 and col4 is null"},
|
|
|
|
}
|
|
|
|
rv = test_client.post(uri, json=payload)
|
|
|
|
assert len(rv.json["result"]["data"]) == 1
|
|
|
|
assert rv.json["result"]["total_count"] == 1
|
|
|
|
assert "2000-01-02" in rv.json["result"]["query"]
|
|
|
|
assert "2000-01-04" in rv.json["result"]["query"]
|
|
|
|
assert "col3 = 1.2" in rv.json["result"]["query"]
|
|
|
|
assert "col4 is null" in rv.json["result"]["query"]
|
|
|
|
assert "col2 = 'c'" in rv.json["result"]["query"]
|
2022-07-22 08:14:42 -04:00
|
|
|
|
|
|
|
|
|
|
|
def test_get_samples_pagination(test_client, login_as_admin, virtual_dataset):
|
|
|
|
# 1. default page, per_page and total_count
|
|
|
|
uri = (
|
|
|
|
f"/datasource/samples?datasource_id={virtual_dataset.id}&datasource_type=table"
|
|
|
|
)
|
2023-01-09 05:10:31 -05:00
|
|
|
rv = test_client.post(uri, json={})
|
2022-08-08 10:42:14 -04:00
|
|
|
assert rv.json["result"]["page"] == 1
|
|
|
|
assert rv.json["result"]["per_page"] == app.config["SAMPLES_ROW_LIMIT"]
|
|
|
|
assert rv.json["result"]["total_count"] == 10
|
2022-07-22 08:14:42 -04:00
|
|
|
|
|
|
|
# 2. incorrect per_page
|
|
|
|
per_pages = (app.config["SAMPLES_ROW_LIMIT"] + 1, 0, "xx")
|
|
|
|
for per_page in per_pages:
|
|
|
|
uri = f"/datasource/samples?datasource_id={virtual_dataset.id}&datasource_type=table&per_page={per_page}"
|
2023-01-09 05:10:31 -05:00
|
|
|
rv = test_client.post(uri, json={})
|
2022-07-22 08:14:42 -04:00
|
|
|
assert rv.status_code == 400
|
|
|
|
|
|
|
|
# 3. incorrect page or datasource_type
|
|
|
|
uri = f"/datasource/samples?datasource_id={virtual_dataset.id}&datasource_type=table&page=xx"
|
2023-01-09 05:10:31 -05:00
|
|
|
rv = test_client.post(uri, json={})
|
2022-07-22 08:14:42 -04:00
|
|
|
assert rv.status_code == 400
|
|
|
|
|
|
|
|
uri = f"/datasource/samples?datasource_id={virtual_dataset.id}&datasource_type=xx"
|
2023-01-09 05:10:31 -05:00
|
|
|
rv = test_client.post(uri, json={})
|
2022-07-22 08:14:42 -04:00
|
|
|
assert rv.status_code == 400
|
|
|
|
|
|
|
|
# 4. turning pages
|
|
|
|
uri = f"/datasource/samples?datasource_id={virtual_dataset.id}&datasource_type=table&per_page=2&page=1"
|
2023-01-09 05:10:31 -05:00
|
|
|
rv = test_client.post(uri, json={})
|
2022-08-08 10:42:14 -04:00
|
|
|
assert rv.json["result"]["page"] == 1
|
|
|
|
assert rv.json["result"]["per_page"] == 2
|
|
|
|
assert rv.json["result"]["total_count"] == 10
|
|
|
|
assert [row["col1"] for row in rv.json["result"]["data"]] == [0, 1]
|
2022-07-22 08:14:42 -04:00
|
|
|
|
|
|
|
uri = f"/datasource/samples?datasource_id={virtual_dataset.id}&datasource_type=table&per_page=2&page=2"
|
2023-01-09 05:10:31 -05:00
|
|
|
rv = test_client.post(uri, json={})
|
2022-08-08 10:42:14 -04:00
|
|
|
assert rv.json["result"]["page"] == 2
|
|
|
|
assert rv.json["result"]["per_page"] == 2
|
|
|
|
assert rv.json["result"]["total_count"] == 10
|
|
|
|
assert [row["col1"] for row in rv.json["result"]["data"]] == [2, 3]
|
2022-07-22 08:14:42 -04:00
|
|
|
|
|
|
|
# 5. Exceeding the maximum pages
|
|
|
|
uri = f"/datasource/samples?datasource_id={virtual_dataset.id}&datasource_type=table&per_page=2&page=6"
|
2023-01-09 05:10:31 -05:00
|
|
|
rv = test_client.post(uri, json={})
|
2022-08-08 10:42:14 -04:00
|
|
|
assert rv.json["result"]["page"] == 6
|
|
|
|
assert rv.json["result"]["per_page"] == 2
|
|
|
|
assert rv.json["result"]["total_count"] == 10
|
|
|
|
assert [row["col1"] for row in rv.json["result"]["data"]] == []
|