chore: remove sl_ tables (#28704)
This commit is contained in:
parent
8d57a35531
commit
7dd28a9003
|
|
@ -1,115 +0,0 @@
|
||||||
# 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.
|
|
||||||
"""
|
|
||||||
Column model.
|
|
||||||
|
|
||||||
This model was introduced in SIP-68 (https://github.com/apache/superset/issues/14909),
|
|
||||||
and represents a "column" in a table or dataset. In addition to a column, new models for
|
|
||||||
tables, metrics, and datasets were also introduced.
|
|
||||||
|
|
||||||
These models are not fully implemented, and shouldn't be used yet.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sqlalchemy as sa
|
|
||||||
from flask_appbuilder import Model
|
|
||||||
|
|
||||||
from superset.models.helpers import (
|
|
||||||
AuditMixinNullable,
|
|
||||||
ExtraJSONMixin,
|
|
||||||
ImportExportMixin,
|
|
||||||
)
|
|
||||||
|
|
||||||
UNKNOWN_TYPE = "UNKNOWN"
|
|
||||||
|
|
||||||
|
|
||||||
class Column(
|
|
||||||
Model,
|
|
||||||
AuditMixinNullable,
|
|
||||||
ExtraJSONMixin,
|
|
||||||
ImportExportMixin,
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
A "column".
|
|
||||||
|
|
||||||
The definition of column here is overloaded: it can represent a physical column in a
|
|
||||||
database relation (table or view); a computed/derived column on a dataset; or an
|
|
||||||
aggregation expression representing a metric.
|
|
||||||
"""
|
|
||||||
|
|
||||||
__tablename__ = "sl_columns"
|
|
||||||
|
|
||||||
id = sa.Column(sa.Integer, primary_key=True)
|
|
||||||
|
|
||||||
# Assuming the column is an aggregation, is it additive? Useful for determining which
|
|
||||||
# aggregations can be done on the metric. Eg, ``COUNT(DISTINCT user_id)`` is not
|
|
||||||
# additive, so it shouldn't be used in a ``SUM``.
|
|
||||||
is_additive = sa.Column(sa.Boolean, default=False)
|
|
||||||
|
|
||||||
# Is this column an aggregation (metric)?
|
|
||||||
is_aggregation = sa.Column(sa.Boolean, default=False)
|
|
||||||
|
|
||||||
is_filterable = sa.Column(sa.Boolean, nullable=False, default=True)
|
|
||||||
is_dimensional = sa.Column(sa.Boolean, nullable=False, default=False)
|
|
||||||
|
|
||||||
# Is an increase desired? Useful for displaying the results of A/B tests, or setting
|
|
||||||
# up alerts. Eg, this is true for "revenue", but false for "latency".
|
|
||||||
is_increase_desired = sa.Column(sa.Boolean, default=True)
|
|
||||||
|
|
||||||
# Column is managed externally and should be read-only inside Superset
|
|
||||||
is_managed_externally = sa.Column(sa.Boolean, nullable=False, default=False)
|
|
||||||
|
|
||||||
# Is this column a partition? Useful for scheduling queries and previewing the latest
|
|
||||||
# data.
|
|
||||||
is_partition = sa.Column(sa.Boolean, default=False)
|
|
||||||
|
|
||||||
# Does the expression point directly to a physical column?
|
|
||||||
is_physical = sa.Column(sa.Boolean, default=True)
|
|
||||||
|
|
||||||
# Is this a spatial column? This could be leveraged in the future for spatial
|
|
||||||
# visualizations.
|
|
||||||
is_spatial = sa.Column(sa.Boolean, default=False)
|
|
||||||
|
|
||||||
# Is this a time column? Useful for plotting time series.
|
|
||||||
is_temporal = sa.Column(sa.Boolean, default=False)
|
|
||||||
|
|
||||||
# We use ``sa.Text`` for these attributes because (1) in modern databases the
|
|
||||||
# performance is the same as ``VARCHAR``[1] and (2) because some table names can be
|
|
||||||
# **really** long (eg, Google Sheets URLs).
|
|
||||||
#
|
|
||||||
# [1] https://www.postgresql.org/docs/9.1/datatype-character.html
|
|
||||||
name = sa.Column(sa.Text)
|
|
||||||
# Raw type as returned and used by db engine.
|
|
||||||
type = sa.Column(sa.Text, default=UNKNOWN_TYPE)
|
|
||||||
|
|
||||||
# Assigns column advanced type to determine custom behavior
|
|
||||||
# does nothing unless feature flag ENABLE_ADVANCED_DATA_TYPES in true
|
|
||||||
advanced_data_type = sa.Column(sa.Text)
|
|
||||||
|
|
||||||
# Columns are defined by expressions. For tables, these are the actual columns names,
|
|
||||||
# and should match the ``name`` attribute. For datasets, these can be any valid SQL
|
|
||||||
# expression. If the SQL expression is an aggregation the column is a metric,
|
|
||||||
# otherwise it's a computed column.
|
|
||||||
expression = sa.Column(sa.Text)
|
|
||||||
unit = sa.Column(sa.Text)
|
|
||||||
|
|
||||||
# Additional metadata describing the column.
|
|
||||||
description = sa.Column(sa.Text)
|
|
||||||
warning_text = sa.Column(sa.Text)
|
|
||||||
external_url = sa.Column(sa.Text, nullable=True)
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
|
||||||
return f"<Column id={self.id}>"
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
# 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.
|
|
||||||
"""
|
|
||||||
Schema for the column model.
|
|
||||||
|
|
||||||
This model was introduced in SIP-68 (https://github.com/apache/superset/issues/14909),
|
|
||||||
and represents a "column" in a table or dataset. In addition to a column, new models for
|
|
||||||
tables, metrics, and datasets were also introduced.
|
|
||||||
|
|
||||||
These models are not fully implemented, and shouldn't be used yet.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from marshmallow_sqlalchemy import SQLAlchemyAutoSchema
|
|
||||||
|
|
||||||
from superset.columns.models import Column
|
|
||||||
|
|
||||||
|
|
||||||
class ColumnSchema(SQLAlchemyAutoSchema):
|
|
||||||
"""
|
|
||||||
Schema for the ``Column`` model.
|
|
||||||
"""
|
|
||||||
|
|
||||||
class Meta: # pylint: disable=too-few-public-methods
|
|
||||||
model = Column
|
|
||||||
load_instance = True
|
|
||||||
include_relationships = True
|
|
||||||
|
|
@ -22,14 +22,12 @@ from superset import db
|
||||||
from superset.connectors.sqla.models import SqlaTable
|
from superset.connectors.sqla.models import SqlaTable
|
||||||
from superset.daos.base import BaseDAO
|
from superset.daos.base import BaseDAO
|
||||||
from superset.daos.exceptions import DatasourceNotFound, DatasourceTypeNotSupportedError
|
from superset.daos.exceptions import DatasourceNotFound, DatasourceTypeNotSupportedError
|
||||||
from superset.datasets.models import Dataset
|
|
||||||
from superset.models.sql_lab import Query, SavedQuery
|
from superset.models.sql_lab import Query, SavedQuery
|
||||||
from superset.tables.models import Table
|
|
||||||
from superset.utils.core import DatasourceType
|
from superset.utils.core import DatasourceType
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
Datasource = Union[Dataset, SqlaTable, Table, Query, SavedQuery]
|
Datasource = Union[SqlaTable, Query, SavedQuery]
|
||||||
|
|
||||||
|
|
||||||
class DatasourceDAO(BaseDAO[Datasource]):
|
class DatasourceDAO(BaseDAO[Datasource]):
|
||||||
|
|
@ -37,8 +35,6 @@ class DatasourceDAO(BaseDAO[Datasource]):
|
||||||
DatasourceType.TABLE: SqlaTable,
|
DatasourceType.TABLE: SqlaTable,
|
||||||
DatasourceType.QUERY: Query,
|
DatasourceType.QUERY: Query,
|
||||||
DatasourceType.SAVEDQUERY: SavedQuery,
|
DatasourceType.SAVEDQUERY: SavedQuery,
|
||||||
DatasourceType.DATASET: Dataset,
|
|
||||||
DatasourceType.SLTABLE: Table,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
|
||||||
|
|
@ -1,118 +0,0 @@
|
||||||
# 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.
|
|
||||||
"""
|
|
||||||
Dataset model.
|
|
||||||
|
|
||||||
This model was introduced in SIP-68 (https://github.com/apache/superset/issues/14909),
|
|
||||||
and represents a "dataset" -- either a physical table or a virtual. In addition to a
|
|
||||||
dataset, new models for columns, metrics, and tables were also introduced.
|
|
||||||
|
|
||||||
These models are not fully implemented, and shouldn't be used yet.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sqlalchemy as sa
|
|
||||||
from flask_appbuilder import Model
|
|
||||||
from sqlalchemy.orm import backref, relationship
|
|
||||||
|
|
||||||
from superset import security_manager
|
|
||||||
from superset.columns.models import Column
|
|
||||||
from superset.models.core import Database
|
|
||||||
from superset.models.helpers import (
|
|
||||||
AuditMixinNullable,
|
|
||||||
ExtraJSONMixin,
|
|
||||||
ImportExportMixin,
|
|
||||||
)
|
|
||||||
from superset.tables.models import Table
|
|
||||||
|
|
||||||
dataset_column_association_table = sa.Table(
|
|
||||||
"sl_dataset_columns",
|
|
||||||
Model.metadata, # pylint: disable=no-member
|
|
||||||
sa.Column(
|
|
||||||
"dataset_id",
|
|
||||||
sa.ForeignKey("sl_datasets.id"),
|
|
||||||
primary_key=True,
|
|
||||||
),
|
|
||||||
sa.Column(
|
|
||||||
"column_id",
|
|
||||||
sa.ForeignKey("sl_columns.id"),
|
|
||||||
primary_key=True,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
dataset_table_association_table = sa.Table(
|
|
||||||
"sl_dataset_tables",
|
|
||||||
Model.metadata, # pylint: disable=no-member
|
|
||||||
sa.Column("dataset_id", sa.ForeignKey("sl_datasets.id"), primary_key=True),
|
|
||||||
sa.Column("table_id", sa.ForeignKey("sl_tables.id"), primary_key=True),
|
|
||||||
)
|
|
||||||
|
|
||||||
dataset_user_association_table = sa.Table(
|
|
||||||
"sl_dataset_users",
|
|
||||||
Model.metadata, # pylint: disable=no-member
|
|
||||||
sa.Column("dataset_id", sa.ForeignKey("sl_datasets.id"), primary_key=True),
|
|
||||||
sa.Column("user_id", sa.ForeignKey("ab_user.id"), primary_key=True),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Dataset(AuditMixinNullable, ExtraJSONMixin, ImportExportMixin, Model):
|
|
||||||
"""
|
|
||||||
A table/view in a database.
|
|
||||||
"""
|
|
||||||
|
|
||||||
__tablename__ = "sl_datasets"
|
|
||||||
|
|
||||||
id = sa.Column(sa.Integer, primary_key=True)
|
|
||||||
database_id = sa.Column(sa.Integer, sa.ForeignKey("dbs.id"), nullable=False)
|
|
||||||
database: Database = relationship(
|
|
||||||
"Database",
|
|
||||||
backref=backref("datasets", cascade="all, delete-orphan"),
|
|
||||||
foreign_keys=[database_id],
|
|
||||||
)
|
|
||||||
# The relationship between datasets and columns is 1:n, but we use a
|
|
||||||
# many-to-many association table to avoid adding two mutually exclusive
|
|
||||||
# columns(dataset_id and table_id) to Column
|
|
||||||
columns: list[Column] = relationship(
|
|
||||||
"Column",
|
|
||||||
secondary=dataset_column_association_table,
|
|
||||||
cascade="all, delete-orphan",
|
|
||||||
single_parent=True,
|
|
||||||
backref="datasets",
|
|
||||||
)
|
|
||||||
owners = relationship(
|
|
||||||
security_manager.user_model, secondary=dataset_user_association_table
|
|
||||||
)
|
|
||||||
tables: list[Table] = relationship(
|
|
||||||
"Table", secondary=dataset_table_association_table, backref="datasets"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Does the dataset point directly to a ``Table``?
|
|
||||||
is_physical = sa.Column(sa.Boolean, default=False)
|
|
||||||
|
|
||||||
# Column is managed externally and should be read-only inside Superset
|
|
||||||
is_managed_externally = sa.Column(sa.Boolean, nullable=False, default=False)
|
|
||||||
|
|
||||||
# We use ``sa.Text`` for these attributes because (1) in modern databases the
|
|
||||||
# performance is the same as ``VARCHAR``[1] and (2) because some table names can be
|
|
||||||
# **really** long (eg, Google Sheets URLs).
|
|
||||||
#
|
|
||||||
# [1] https://www.postgresql.org/docs/9.1/datatype-character.html
|
|
||||||
name = sa.Column(sa.Text)
|
|
||||||
expression = sa.Column(sa.Text)
|
|
||||||
external_url = sa.Column(sa.Text, nullable=True)
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
|
||||||
return f"<Dataset id={self.id} database_id={self.database_id} {self.name}>"
|
|
||||||
|
|
@ -21,9 +21,7 @@ from dateutil.parser import isoparse
|
||||||
from flask_babel import lazy_gettext as _
|
from flask_babel import lazy_gettext as _
|
||||||
from marshmallow import fields, pre_load, Schema, ValidationError
|
from marshmallow import fields, pre_load, Schema, ValidationError
|
||||||
from marshmallow.validate import Length
|
from marshmallow.validate import Length
|
||||||
from marshmallow_sqlalchemy import SQLAlchemyAutoSchema
|
|
||||||
|
|
||||||
from superset.datasets.models import Dataset
|
|
||||||
from superset.exceptions import SupersetMarshmallowValidationError
|
from superset.exceptions import SupersetMarshmallowValidationError
|
||||||
from superset.utils import json
|
from superset.utils import json
|
||||||
|
|
||||||
|
|
@ -291,17 +289,6 @@ class GetOrCreateDatasetSchema(Schema):
|
||||||
always_filter_main_dttm = fields.Boolean(load_default=False)
|
always_filter_main_dttm = fields.Boolean(load_default=False)
|
||||||
|
|
||||||
|
|
||||||
class DatasetSchema(SQLAlchemyAutoSchema):
|
|
||||||
"""
|
|
||||||
Schema for the ``Dataset`` model.
|
|
||||||
"""
|
|
||||||
|
|
||||||
class Meta: # pylint: disable=too-few-public-methods
|
|
||||||
model = Dataset
|
|
||||||
load_instance = True
|
|
||||||
include_relationships = True
|
|
||||||
|
|
||||||
|
|
||||||
class DatasetCacheWarmUpRequestSchema(Schema):
|
class DatasetCacheWarmUpRequestSchema(Schema):
|
||||||
db_name = fields.String(
|
db_name = fields.String(
|
||||||
required=True,
|
required=True,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,197 @@
|
||||||
|
# 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.
|
||||||
|
"""remove sl_ tables
|
||||||
|
|
||||||
|
Revision ID: 02f4f7811799
|
||||||
|
Revises: f7b6750b67e8
|
||||||
|
Create Date: 2024-05-24 11:31:57.115586
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from alembic import op
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = "02f4f7811799"
|
||||||
|
down_revision = "f7b6750b67e8"
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
op.drop_table("sl_dataset_columns")
|
||||||
|
op.drop_table("sl_table_columns")
|
||||||
|
op.drop_table("sl_dataset_tables")
|
||||||
|
op.drop_table("sl_columns")
|
||||||
|
op.drop_table("sl_tables")
|
||||||
|
op.drop_table("sl_dataset_users")
|
||||||
|
op.drop_table("sl_datasets")
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
op.create_table(
|
||||||
|
"sl_dataset_users",
|
||||||
|
sa.Column("dataset_id", sa.INTEGER(), nullable=False),
|
||||||
|
sa.Column("user_id", sa.INTEGER(), nullable=False),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
["dataset_id"],
|
||||||
|
["sl_datasets.id"],
|
||||||
|
),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
["user_id"],
|
||||||
|
["ab_user.id"],
|
||||||
|
),
|
||||||
|
sa.PrimaryKeyConstraint("dataset_id", "user_id"),
|
||||||
|
)
|
||||||
|
op.create_table(
|
||||||
|
"sl_datasets",
|
||||||
|
sa.Column("uuid", sa.NUMERIC(precision=16), nullable=True),
|
||||||
|
sa.Column("created_on", sa.DATETIME(), nullable=True),
|
||||||
|
sa.Column("changed_on", sa.DATETIME(), nullable=True),
|
||||||
|
sa.Column("id", sa.INTEGER(), nullable=False),
|
||||||
|
sa.Column("database_id", sa.INTEGER(), nullable=False),
|
||||||
|
sa.Column("is_physical", sa.BOOLEAN(), nullable=True),
|
||||||
|
sa.Column("is_managed_externally", sa.BOOLEAN(), nullable=False),
|
||||||
|
sa.Column("name", sa.TEXT(), nullable=True),
|
||||||
|
sa.Column("expression", sa.TEXT(), nullable=True),
|
||||||
|
sa.Column("external_url", sa.TEXT(), nullable=True),
|
||||||
|
sa.Column("extra_json", sa.TEXT(), nullable=True),
|
||||||
|
sa.Column("created_by_fk", sa.INTEGER(), nullable=True),
|
||||||
|
sa.Column("changed_by_fk", sa.INTEGER(), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
["changed_by_fk"],
|
||||||
|
["ab_user.id"],
|
||||||
|
),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
["created_by_fk"],
|
||||||
|
["ab_user.id"],
|
||||||
|
),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
["database_id"],
|
||||||
|
["dbs.id"],
|
||||||
|
),
|
||||||
|
sa.PrimaryKeyConstraint("id"),
|
||||||
|
sa.UniqueConstraint("uuid"),
|
||||||
|
)
|
||||||
|
op.create_table(
|
||||||
|
"sl_tables",
|
||||||
|
sa.Column("uuid", sa.NUMERIC(precision=16), nullable=True),
|
||||||
|
sa.Column("created_on", sa.DATETIME(), nullable=True),
|
||||||
|
sa.Column("changed_on", sa.DATETIME(), nullable=True),
|
||||||
|
sa.Column("id", sa.INTEGER(), nullable=False),
|
||||||
|
sa.Column("database_id", sa.INTEGER(), nullable=False),
|
||||||
|
sa.Column("is_managed_externally", sa.BOOLEAN(), nullable=False),
|
||||||
|
sa.Column("catalog", sa.TEXT(), nullable=True),
|
||||||
|
sa.Column("schema", sa.TEXT(), nullable=True),
|
||||||
|
sa.Column("name", sa.TEXT(), nullable=True),
|
||||||
|
sa.Column("external_url", sa.TEXT(), nullable=True),
|
||||||
|
sa.Column("extra_json", sa.TEXT(), nullable=True),
|
||||||
|
sa.Column("created_by_fk", sa.INTEGER(), nullable=True),
|
||||||
|
sa.Column("changed_by_fk", sa.INTEGER(), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
["changed_by_fk"],
|
||||||
|
["ab_user.id"],
|
||||||
|
),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
["created_by_fk"],
|
||||||
|
["ab_user.id"],
|
||||||
|
),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
["database_id"],
|
||||||
|
["dbs.id"],
|
||||||
|
),
|
||||||
|
sa.PrimaryKeyConstraint("id"),
|
||||||
|
sa.UniqueConstraint("uuid"),
|
||||||
|
)
|
||||||
|
op.create_table(
|
||||||
|
"sl_dataset_columns",
|
||||||
|
sa.Column("dataset_id", sa.INTEGER(), nullable=False),
|
||||||
|
sa.Column("column_id", sa.INTEGER(), nullable=False),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
["column_id"],
|
||||||
|
["sl_columns.id"],
|
||||||
|
),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
["dataset_id"],
|
||||||
|
["sl_datasets.id"],
|
||||||
|
),
|
||||||
|
sa.PrimaryKeyConstraint("dataset_id", "column_id"),
|
||||||
|
)
|
||||||
|
op.create_table(
|
||||||
|
"sl_dataset_tables",
|
||||||
|
sa.Column("dataset_id", sa.INTEGER(), nullable=False),
|
||||||
|
sa.Column("table_id", sa.INTEGER(), nullable=False),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
["dataset_id"],
|
||||||
|
["sl_datasets.id"],
|
||||||
|
),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
["table_id"],
|
||||||
|
["sl_tables.id"],
|
||||||
|
),
|
||||||
|
sa.PrimaryKeyConstraint("dataset_id", "table_id"),
|
||||||
|
)
|
||||||
|
op.create_table(
|
||||||
|
"sl_table_columns",
|
||||||
|
sa.Column("table_id", sa.INTEGER(), nullable=False),
|
||||||
|
sa.Column("column_id", sa.INTEGER(), nullable=False),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
["column_id"],
|
||||||
|
["sl_columns.id"],
|
||||||
|
),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
["table_id"],
|
||||||
|
["sl_tables.id"],
|
||||||
|
),
|
||||||
|
sa.PrimaryKeyConstraint("table_id", "column_id"),
|
||||||
|
)
|
||||||
|
op.create_table(
|
||||||
|
"sl_columns",
|
||||||
|
sa.Column("uuid", sa.NUMERIC(precision=16), nullable=True),
|
||||||
|
sa.Column("created_on", sa.DATETIME(), nullable=True),
|
||||||
|
sa.Column("changed_on", sa.DATETIME(), nullable=True),
|
||||||
|
sa.Column("id", sa.INTEGER(), nullable=False),
|
||||||
|
sa.Column("is_aggregation", sa.BOOLEAN(), nullable=False),
|
||||||
|
sa.Column("is_additive", sa.BOOLEAN(), nullable=False),
|
||||||
|
sa.Column("is_dimensional", sa.BOOLEAN(), nullable=False),
|
||||||
|
sa.Column("is_filterable", sa.BOOLEAN(), nullable=False),
|
||||||
|
sa.Column("is_increase_desired", sa.BOOLEAN(), nullable=False),
|
||||||
|
sa.Column("is_managed_externally", sa.BOOLEAN(), nullable=False),
|
||||||
|
sa.Column("is_partition", sa.BOOLEAN(), nullable=False),
|
||||||
|
sa.Column("is_physical", sa.BOOLEAN(), nullable=False),
|
||||||
|
sa.Column("is_temporal", sa.BOOLEAN(), nullable=False),
|
||||||
|
sa.Column("is_spatial", sa.BOOLEAN(), nullable=False),
|
||||||
|
sa.Column("name", sa.TEXT(), nullable=True),
|
||||||
|
sa.Column("type", sa.TEXT(), nullable=True),
|
||||||
|
sa.Column("unit", sa.TEXT(), nullable=True),
|
||||||
|
sa.Column("expression", sa.TEXT(), nullable=True),
|
||||||
|
sa.Column("description", sa.TEXT(), nullable=True),
|
||||||
|
sa.Column("warning_text", sa.TEXT(), nullable=True),
|
||||||
|
sa.Column("external_url", sa.TEXT(), nullable=True),
|
||||||
|
sa.Column("extra_json", sa.TEXT(), nullable=True),
|
||||||
|
sa.Column("created_by_fk", sa.INTEGER(), nullable=True),
|
||||||
|
sa.Column("changed_by_fk", sa.INTEGER(), nullable=True),
|
||||||
|
sa.Column("advanced_data_type", sa.TEXT(), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
["changed_by_fk"],
|
||||||
|
["ab_user.id"],
|
||||||
|
),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
["created_by_fk"],
|
||||||
|
["ab_user.id"],
|
||||||
|
),
|
||||||
|
sa.PrimaryKeyConstraint("id"),
|
||||||
|
sa.UniqueConstraint("uuid"),
|
||||||
|
)
|
||||||
|
|
@ -1,206 +0,0 @@
|
||||||
# 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.
|
|
||||||
"""
|
|
||||||
Table model.
|
|
||||||
|
|
||||||
This model was introduced in SIP-68 (https://github.com/apache/superset/issues/14909),
|
|
||||||
and represents a "table" in a given database -- either a physical table or a view. In
|
|
||||||
addition to a table, new models for columns, metrics, and datasets were also introduced.
|
|
||||||
|
|
||||||
These models are not fully implemented, and shouldn't be used yet.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from collections.abc import Iterable
|
|
||||||
from typing import Any, Optional, TYPE_CHECKING
|
|
||||||
|
|
||||||
import sqlalchemy as sa
|
|
||||||
from flask_appbuilder import Model
|
|
||||||
from sqlalchemy import inspect
|
|
||||||
from sqlalchemy.orm import backref, relationship, Session
|
|
||||||
from sqlalchemy.schema import UniqueConstraint
|
|
||||||
from sqlalchemy.sql import and_, or_
|
|
||||||
|
|
||||||
from superset.columns.models import Column
|
|
||||||
from superset.connectors.sqla.utils import get_physical_table_metadata
|
|
||||||
from superset.models.core import Database
|
|
||||||
from superset.models.helpers import (
|
|
||||||
AuditMixinNullable,
|
|
||||||
ExtraJSONMixin,
|
|
||||||
ImportExportMixin,
|
|
||||||
)
|
|
||||||
from superset.sql_parse import Table as TableName
|
|
||||||
from superset.superset_typing import ResultSetColumnType
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from superset.datasets.models import Dataset
|
|
||||||
|
|
||||||
table_column_association_table = sa.Table(
|
|
||||||
"sl_table_columns",
|
|
||||||
Model.metadata, # pylint: disable=no-member
|
|
||||||
sa.Column(
|
|
||||||
"table_id",
|
|
||||||
sa.ForeignKey("sl_tables.id", ondelete="CASCADE"),
|
|
||||||
primary_key=True,
|
|
||||||
),
|
|
||||||
sa.Column(
|
|
||||||
"column_id",
|
|
||||||
sa.ForeignKey("sl_columns.id", ondelete="CASCADE"),
|
|
||||||
primary_key=True,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Table(AuditMixinNullable, ExtraJSONMixin, ImportExportMixin, Model):
|
|
||||||
"""
|
|
||||||
A table/view in a database.
|
|
||||||
"""
|
|
||||||
|
|
||||||
__tablename__ = "sl_tables"
|
|
||||||
|
|
||||||
# Note this uniqueness constraint is not part of the physical schema, i.e., it does
|
|
||||||
# not exist in the migrations. The reason it does not physically exist is MySQL,
|
|
||||||
# PostgreSQL, etc. have a different interpretation of uniqueness when it comes to NULL
|
|
||||||
# which is problematic given the catalog and schema are optional.
|
|
||||||
__table_args__ = (UniqueConstraint("database_id", "catalog", "schema", "name"),)
|
|
||||||
|
|
||||||
id = sa.Column(sa.Integer, primary_key=True)
|
|
||||||
database_id = sa.Column(sa.Integer, sa.ForeignKey("dbs.id"), nullable=False)
|
|
||||||
database: Database = relationship(
|
|
||||||
"Database",
|
|
||||||
# TODO (betodealmeida): rename the backref to ``tables`` once we get rid of the
|
|
||||||
# old models.
|
|
||||||
backref=backref("new_tables", cascade="all, delete-orphan"),
|
|
||||||
foreign_keys=[database_id],
|
|
||||||
)
|
|
||||||
# The relationship between datasets and columns is 1:n, but we use a
|
|
||||||
# many-to-many association table to avoid adding two mutually exclusive
|
|
||||||
# columns(dataset_id and table_id) to Column
|
|
||||||
columns: list[Column] = relationship(
|
|
||||||
"Column",
|
|
||||||
secondary=table_column_association_table,
|
|
||||||
cascade="all, delete-orphan",
|
|
||||||
single_parent=True,
|
|
||||||
# backref is needed for session to skip detaching `dataset` if only `column`
|
|
||||||
# is loaded.
|
|
||||||
backref="tables",
|
|
||||||
)
|
|
||||||
datasets: list["Dataset"] # will be populated by Dataset.tables backref
|
|
||||||
|
|
||||||
# We use ``sa.Text`` for these attributes because (1) in modern databases the
|
|
||||||
# performance is the same as ``VARCHAR``[1] and (2) because some table names can be
|
|
||||||
# **really** long (eg, Google Sheets URLs).
|
|
||||||
#
|
|
||||||
# [1] https://www.postgresql.org/docs/9.1/datatype-character.html
|
|
||||||
catalog = sa.Column(sa.Text)
|
|
||||||
schema = sa.Column(sa.Text)
|
|
||||||
name = sa.Column(sa.Text)
|
|
||||||
|
|
||||||
# Column is managed externally and should be read-only inside Superset
|
|
||||||
is_managed_externally = sa.Column(sa.Boolean, nullable=False, default=False)
|
|
||||||
external_url = sa.Column(sa.Text, nullable=True)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def fullname(self) -> str:
|
|
||||||
return str(TableName(table=self.name, schema=self.schema, catalog=self.catalog))
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
|
||||||
return f"<Table id={self.id} database_id={self.database_id} {self.fullname}>"
|
|
||||||
|
|
||||||
def sync_columns(self) -> None:
|
|
||||||
"""Sync table columns with the database. Keep metadata for existing columns"""
|
|
||||||
try:
|
|
||||||
column_metadata = get_physical_table_metadata(
|
|
||||||
self.database, self.name, self.schema
|
|
||||||
)
|
|
||||||
except Exception: # pylint: disable=broad-except
|
|
||||||
column_metadata = []
|
|
||||||
|
|
||||||
existing_columns = {column.name: column for column in self.columns}
|
|
||||||
quote_identifier = self.database.quote_identifier
|
|
||||||
|
|
||||||
def update_or_create_column(column_meta: ResultSetColumnType) -> Column:
|
|
||||||
column_name: str = column_meta["column_name"]
|
|
||||||
if column_name in existing_columns:
|
|
||||||
column = existing_columns[column_name]
|
|
||||||
else:
|
|
||||||
column = Column(name=column_name)
|
|
||||||
column.type = column_meta["type"]
|
|
||||||
column.is_temporal = column_meta["is_dttm"]
|
|
||||||
column.expression = quote_identifier(column_name)
|
|
||||||
column.is_aggregation = False
|
|
||||||
column.is_physical = True
|
|
||||||
column.is_spatial = False
|
|
||||||
column.is_partition = False # TODO: update with accurate is_partition
|
|
||||||
return column
|
|
||||||
|
|
||||||
self.columns = [update_or_create_column(col) for col in column_metadata]
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def bulk_load_or_create(
|
|
||||||
database: Database,
|
|
||||||
table_names: Iterable[TableName],
|
|
||||||
default_schema: Optional[str] = None,
|
|
||||||
sync_columns: Optional[bool] = False,
|
|
||||||
default_props: Optional[dict[str, Any]] = None,
|
|
||||||
) -> list["Table"]:
|
|
||||||
"""
|
|
||||||
Load or create multiple Table instances.
|
|
||||||
"""
|
|
||||||
if not table_names:
|
|
||||||
return []
|
|
||||||
|
|
||||||
if not database.id:
|
|
||||||
raise Exception( # pylint: disable=broad-exception-raised
|
|
||||||
"Database must be already saved to metastore"
|
|
||||||
)
|
|
||||||
|
|
||||||
default_props = default_props or {}
|
|
||||||
session: Session = inspect(database).session # pylint: disable=disallowed-name
|
|
||||||
# load existing tables
|
|
||||||
predicate = or_(
|
|
||||||
*[
|
|
||||||
and_(
|
|
||||||
Table.database_id == database.id,
|
|
||||||
Table.schema == (table.schema or default_schema),
|
|
||||||
Table.name == table.table,
|
|
||||||
)
|
|
||||||
for table in table_names
|
|
||||||
]
|
|
||||||
)
|
|
||||||
all_tables = session.query(Table).filter(predicate).order_by(Table.id).all()
|
|
||||||
|
|
||||||
# add missing tables and pull its columns
|
|
||||||
existing = {(table.schema, table.name) for table in all_tables}
|
|
||||||
for table in table_names:
|
|
||||||
schema = table.schema or default_schema
|
|
||||||
name = table.table
|
|
||||||
if (schema, name) not in existing:
|
|
||||||
new_table = Table(
|
|
||||||
database=database,
|
|
||||||
database_id=database.id,
|
|
||||||
name=name,
|
|
||||||
schema=schema,
|
|
||||||
catalog=None,
|
|
||||||
**default_props,
|
|
||||||
)
|
|
||||||
if sync_columns:
|
|
||||||
new_table.sync_columns()
|
|
||||||
all_tables.append(new_table)
|
|
||||||
existing.add((schema, name))
|
|
||||||
session.add(new_table)
|
|
||||||
|
|
||||||
return all_tables
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
# 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.
|
|
||||||
"""
|
|
||||||
Schema for table model.
|
|
||||||
|
|
||||||
This model was introduced in SIP-68 (https://github.com/apache/superset/issues/14909),
|
|
||||||
and represents a "table" in a given database -- either a physical table or a view. In
|
|
||||||
addition to a table, new models for columns, metrics, and datasets were also introduced.
|
|
||||||
|
|
||||||
These models are not fully implemented, and shouldn't be used yet.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from marshmallow_sqlalchemy import SQLAlchemyAutoSchema
|
|
||||||
|
|
||||||
from superset.tables.models import Table
|
|
||||||
|
|
||||||
|
|
||||||
class TableSchema(SQLAlchemyAutoSchema):
|
|
||||||
"""
|
|
||||||
Schema for the ``Table`` model.
|
|
||||||
"""
|
|
||||||
|
|
||||||
class Meta: # pylint: disable=too-few-public-methods
|
|
||||||
model = Table
|
|
||||||
load_instance = True
|
|
||||||
include_relationships = True
|
|
||||||
|
|
@ -147,7 +147,6 @@ class GenericDataType(IntEnum):
|
||||||
|
|
||||||
|
|
||||||
class DatasourceType(StrEnum):
|
class DatasourceType(StrEnum):
|
||||||
SLTABLE = "sl_table"
|
|
||||||
TABLE = "table"
|
TABLE = "table"
|
||||||
DATASET = "dataset"
|
DATASET = "dataset"
|
||||||
QUERY = "query"
|
QUERY = "query"
|
||||||
|
|
|
||||||
|
|
@ -600,7 +600,7 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
|
||||||
{
|
{
|
||||||
"message": {
|
"message": {
|
||||||
"datasource_type": [
|
"datasource_type": [
|
||||||
"Must be one of: sl_table, table, dataset, query, saved_query, view."
|
"Must be one of: table, dataset, query, saved_query, view."
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -961,7 +961,7 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
|
||||||
{
|
{
|
||||||
"message": {
|
"message": {
|
||||||
"datasource_type": [
|
"datasource_type": [
|
||||||
"Must be one of: sl_table, table, dataset, query, saved_query, view."
|
"Must be one of: table, dataset, query, saved_query, view."
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,6 @@ from superset.daos.exceptions import (
|
||||||
DAODeleteFailedError,
|
DAODeleteFailedError,
|
||||||
DAOUpdateFailedError,
|
DAOUpdateFailedError,
|
||||||
)
|
)
|
||||||
from superset.datasets.models import Dataset # noqa: F401
|
|
||||||
from superset.extensions import db, security_manager
|
from superset.extensions import db, security_manager
|
||||||
from superset.models.core import Database
|
from superset.models.core import Database
|
||||||
from superset.models.slice import Slice
|
from superset.models.slice import Slice
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,8 @@
|
||||||
# KIND, either express or implied. See the License for the
|
# KIND, either express or implied. See the License for the
|
||||||
# specific language governing permissions and limitations
|
# specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
from unittest.mock import ANY, Mock, patch
|
|
||||||
|
from unittest.mock import ANY, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
@ -132,21 +133,6 @@ class TestDatasourceApi(SupersetTestCase):
|
||||||
"database or `all_datasource_access` permission",
|
"database or `all_datasource_access` permission",
|
||||||
)
|
)
|
||||||
|
|
||||||
@patch("superset.datasource.api.DatasourceDAO.get_datasource")
|
|
||||||
def test_get_column_values_not_implemented_error(self, get_datasource_mock):
|
|
||||||
datasource = Mock()
|
|
||||||
datasource.values_for_column.side_effect = NotImplementedError
|
|
||||||
get_datasource_mock.return_value = datasource
|
|
||||||
|
|
||||||
self.login(ADMIN_USERNAME)
|
|
||||||
rv = self.client.get("api/v1/datasource/sl_table/1/column/col1/values/")
|
|
||||||
self.assertEqual(rv.status_code, 400)
|
|
||||||
response = json.loads(rv.data.decode("utf-8"))
|
|
||||||
self.assertEqual(
|
|
||||||
response["message"],
|
|
||||||
"Unable to get column values for datasource type: sl_table",
|
|
||||||
)
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("app_context", "virtual_dataset")
|
@pytest.mark.usefixtures("app_context", "virtual_dataset")
|
||||||
@patch("superset.models.helpers.ExploreMixin.values_for_column")
|
@patch("superset.models.helpers.ExploreMixin.values_for_column")
|
||||||
def test_get_column_values_normalize_columns_enabled(self, values_for_column_mock):
|
def test_get_column_values_normalize_columns_enabled(self, values_for_column_mock):
|
||||||
|
|
|
||||||
|
|
@ -23,11 +23,9 @@ import pytest
|
||||||
from sqlalchemy import Column, create_engine, Date, Integer, MetaData, String, Table
|
from sqlalchemy import Column, create_engine, Date, Integer, MetaData, String, Table
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
|
|
||||||
from superset.columns.models import Column as Sl_Column # noqa: F401
|
|
||||||
from superset.connectors.sqla.models import SqlaTable, TableColumn
|
from superset.connectors.sqla.models import SqlaTable, TableColumn
|
||||||
from superset.extensions import db
|
from superset.extensions import db
|
||||||
from superset.models.core import Database
|
from superset.models.core import Database
|
||||||
from superset.tables.models import Table as Sl_Table # noqa: F401
|
|
||||||
from superset.utils.core import get_example_default_schema
|
from superset.utils.core import get_example_default_schema
|
||||||
from superset.utils.database import get_example_database # noqa: F401
|
from superset.utils.database import get_example_database # noqa: F401
|
||||||
from tests.integration_tests.test_app import app
|
from tests.integration_tests.test_app import app
|
||||||
|
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
# 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=import-outside-toplevel, unused-argument
|
|
||||||
|
|
||||||
from sqlalchemy.orm.session import Session
|
|
||||||
|
|
||||||
|
|
||||||
def test_column_model(session: Session) -> None:
|
|
||||||
"""
|
|
||||||
Test basic attributes of a ``Column``.
|
|
||||||
"""
|
|
||||||
from superset import db
|
|
||||||
from superset.columns.models import Column
|
|
||||||
|
|
||||||
engine = db.session.get_bind()
|
|
||||||
Column.metadata.create_all(engine) # pylint: disable=no-member
|
|
||||||
|
|
||||||
column = Column(
|
|
||||||
name="ds",
|
|
||||||
type="TIMESTAMP",
|
|
||||||
expression="ds",
|
|
||||||
)
|
|
||||||
|
|
||||||
db.session.add(column)
|
|
||||||
db.session.flush()
|
|
||||||
|
|
||||||
assert column.id == 1
|
|
||||||
assert column.uuid is not None
|
|
||||||
|
|
||||||
assert column.name == "ds"
|
|
||||||
assert column.type == "TIMESTAMP"
|
|
||||||
assert column.expression == "ds"
|
|
||||||
|
|
||||||
# test that default values are set correctly
|
|
||||||
assert column.description is None
|
|
||||||
assert column.warning_text is None
|
|
||||||
assert column.unit is None
|
|
||||||
assert column.is_temporal is False
|
|
||||||
assert column.is_spatial is False
|
|
||||||
assert column.is_partition is False
|
|
||||||
assert column.is_aggregation is False
|
|
||||||
assert column.is_additive is False
|
|
||||||
assert column.is_increase_desired is True
|
|
||||||
|
|
@ -25,12 +25,9 @@ from superset.utils.core import DatasourceType
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def session_with_data(session: Session) -> Iterator[Session]:
|
def session_with_data(session: Session) -> Iterator[Session]:
|
||||||
from superset.columns.models import Column
|
|
||||||
from superset.connectors.sqla.models import SqlaTable, TableColumn
|
from superset.connectors.sqla.models import SqlaTable, TableColumn
|
||||||
from superset.datasets.models import Dataset
|
|
||||||
from superset.models.core import Database
|
from superset.models.core import Database
|
||||||
from superset.models.sql_lab import Query, SavedQuery
|
from superset.models.sql_lab import Query, SavedQuery
|
||||||
from superset.tables.models import Table
|
|
||||||
|
|
||||||
engine = session.get_bind()
|
engine = session.get_bind()
|
||||||
SqlaTable.metadata.create_all(engine) # pylint: disable=no-member
|
SqlaTable.metadata.create_all(engine) # pylint: disable=no-member
|
||||||
|
|
@ -65,32 +62,6 @@ def session_with_data(session: Session) -> Iterator[Session]:
|
||||||
|
|
||||||
saved_query = SavedQuery(database=database, sql="select * from foo")
|
saved_query = SavedQuery(database=database, sql="select * from foo")
|
||||||
|
|
||||||
table = Table(
|
|
||||||
name="my_table",
|
|
||||||
schema="my_schema",
|
|
||||||
catalog="my_catalog",
|
|
||||||
database=database,
|
|
||||||
columns=[],
|
|
||||||
)
|
|
||||||
|
|
||||||
dataset = Dataset(
|
|
||||||
database=table.database,
|
|
||||||
name="positions",
|
|
||||||
expression="""
|
|
||||||
SELECT array_agg(array[longitude,latitude]) AS position
|
|
||||||
FROM my_catalog.my_schema.my_table
|
|
||||||
""",
|
|
||||||
tables=[table],
|
|
||||||
columns=[
|
|
||||||
Column(
|
|
||||||
name="position",
|
|
||||||
expression="array_agg(array[longitude,latitude])",
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
session.add(dataset)
|
|
||||||
session.add(table)
|
|
||||||
session.add(saved_query)
|
session.add(saved_query)
|
||||||
session.add(query_obj)
|
session.add(query_obj)
|
||||||
session.add(database)
|
session.add(database)
|
||||||
|
|
@ -138,36 +109,9 @@ def test_get_datasource_saved_query(session_with_data: Session) -> None:
|
||||||
assert isinstance(result, SavedQuery)
|
assert isinstance(result, SavedQuery)
|
||||||
|
|
||||||
|
|
||||||
def test_get_datasource_sl_table(session_with_data: Session) -> None:
|
|
||||||
from superset.daos.datasource import DatasourceDAO
|
|
||||||
from superset.tables.models import Table
|
|
||||||
|
|
||||||
result = DatasourceDAO.get_datasource(
|
|
||||||
datasource_type=DatasourceType.SLTABLE,
|
|
||||||
datasource_id=1,
|
|
||||||
)
|
|
||||||
|
|
||||||
assert result.id == 1
|
|
||||||
assert isinstance(result, Table)
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_datasource_sl_dataset(session_with_data: Session) -> None:
|
|
||||||
from superset.daos.datasource import DatasourceDAO
|
|
||||||
from superset.datasets.models import Dataset
|
|
||||||
|
|
||||||
result = DatasourceDAO.get_datasource(
|
|
||||||
datasource_type=DatasourceType.DATASET,
|
|
||||||
datasource_id=1,
|
|
||||||
)
|
|
||||||
|
|
||||||
assert result.id == 1
|
|
||||||
assert isinstance(result, Dataset)
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_datasource_w_str_param(session_with_data: Session) -> None:
|
def test_get_datasource_w_str_param(session_with_data: Session) -> None:
|
||||||
from superset.connectors.sqla.models import SqlaTable
|
from superset.connectors.sqla.models import SqlaTable
|
||||||
from superset.daos.datasource import DatasourceDAO
|
from superset.daos.datasource import DatasourceDAO
|
||||||
from superset.tables.models import Table
|
|
||||||
|
|
||||||
assert isinstance(
|
assert isinstance(
|
||||||
DatasourceDAO.get_datasource(
|
DatasourceDAO.get_datasource(
|
||||||
|
|
@ -177,14 +121,6 @@ def test_get_datasource_w_str_param(session_with_data: Session) -> None:
|
||||||
SqlaTable,
|
SqlaTable,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert isinstance(
|
|
||||||
DatasourceDAO.get_datasource(
|
|
||||||
datasource_type="sl_table",
|
|
||||||
datasource_id=1,
|
|
||||||
),
|
|
||||||
Table,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_all_datasources(session_with_data: Session) -> None:
|
def test_get_all_datasources(session_with_data: Session) -> None:
|
||||||
from superset.connectors.sqla.models import SqlaTable
|
from superset.connectors.sqla.models import SqlaTable
|
||||||
|
|
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
# 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=import-outside-toplevel, unused-argument
|
|
||||||
from sqlalchemy.orm.session import Session
|
|
||||||
|
|
||||||
from superset import db
|
|
||||||
|
|
||||||
|
|
||||||
def test_table_model(session: Session) -> None:
|
|
||||||
"""
|
|
||||||
Test basic attributes of a ``Table``.
|
|
||||||
"""
|
|
||||||
from superset.columns.models import Column
|
|
||||||
from superset.models.core import Database
|
|
||||||
from superset.tables.models import Table
|
|
||||||
|
|
||||||
engine = db.session.get_bind()
|
|
||||||
Table.metadata.create_all(engine) # pylint: disable=no-member
|
|
||||||
|
|
||||||
table = Table(
|
|
||||||
name="my_table",
|
|
||||||
schema="my_schema",
|
|
||||||
catalog="my_catalog",
|
|
||||||
database=Database(database_name="my_database", sqlalchemy_uri="test://"),
|
|
||||||
columns=[
|
|
||||||
Column(
|
|
||||||
name="ds",
|
|
||||||
type="TIMESTAMP",
|
|
||||||
expression="ds",
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)
|
|
||||||
db.session.add(table)
|
|
||||||
db.session.flush()
|
|
||||||
|
|
||||||
assert table.id == 1
|
|
||||||
assert table.uuid is not None
|
|
||||||
assert table.database_id == 1
|
|
||||||
assert table.catalog == "my_catalog"
|
|
||||||
assert table.schema == "my_schema"
|
|
||||||
assert table.name == "my_table"
|
|
||||||
assert [column.name for column in table.columns] == ["ds"]
|
|
||||||
Loading…
Reference in New Issue