mirror of
https://github.com/d3vyce/sqlalchemy-pgview.git
synced 2026-03-01 19:50:46 +01:00
328 lines
11 KiB
Python
328 lines
11 KiB
Python
"""Tests for view dependency tracking."""
|
|
|
|
from sqlalchemy import Table, func, select
|
|
from sqlalchemy.engine import Engine
|
|
|
|
from sqlalchemy_pgview import (
|
|
CreateMaterializedView,
|
|
CreateView,
|
|
DropMaterializedView,
|
|
DropView,
|
|
MaterializedView,
|
|
View,
|
|
get_all_views,
|
|
get_view_definition,
|
|
)
|
|
from sqlalchemy_pgview.dependencies import (
|
|
ViewDependency,
|
|
ViewInfo,
|
|
get_dependency_order,
|
|
get_reverse_dependencies,
|
|
get_view_dependencies,
|
|
)
|
|
|
|
|
|
class TestViewInfo:
|
|
"""Tests for ViewInfo dataclass."""
|
|
|
|
def test_view_info_fullname_without_schema(self) -> None:
|
|
"""Test ViewInfo.fullname without schema."""
|
|
info = ViewInfo(
|
|
name="my_view",
|
|
schema=None,
|
|
definition="SELECT 1",
|
|
is_materialized=False,
|
|
)
|
|
assert info.fullname == "my_view"
|
|
|
|
def test_view_info_fullname_with_schema(self) -> None:
|
|
"""Test ViewInfo.fullname with schema."""
|
|
info = ViewInfo(
|
|
name="my_view",
|
|
schema="analytics",
|
|
definition="SELECT 1",
|
|
is_materialized=False,
|
|
)
|
|
assert info.fullname == "analytics.my_view"
|
|
|
|
|
|
class TestViewDependency:
|
|
"""Tests for ViewDependency dataclass."""
|
|
|
|
def test_view_dependency_fullnames(self) -> None:
|
|
"""Test ViewDependency fullname properties."""
|
|
dep = ViewDependency(
|
|
dependent_view="child_view",
|
|
dependent_schema="public",
|
|
referenced_view="parent_view",
|
|
referenced_schema="analytics",
|
|
)
|
|
assert dep.dependent_fullname == "public.child_view"
|
|
assert dep.referenced_fullname == "analytics.parent_view"
|
|
|
|
|
|
class TestGetAllViews:
|
|
"""Tests for get_all_views function."""
|
|
|
|
def test_get_all_views(
|
|
self, pg_engine: Engine, pg_one_to_many_tables: dict[str, Table]
|
|
) -> None:
|
|
"""Test getting all views from database."""
|
|
authors = pg_one_to_many_tables["authors"]
|
|
books = pg_one_to_many_tables["books"]
|
|
|
|
view1 = View(
|
|
"test_view_deps_1",
|
|
select(authors.c.id, authors.c.name),
|
|
)
|
|
|
|
mview1 = MaterializedView(
|
|
"test_mview_deps_1",
|
|
select(func.count(books.c.id).label("count")),
|
|
with_data=True,
|
|
)
|
|
|
|
with pg_engine.begin() as conn:
|
|
conn.execute(CreateView(view1, or_replace=True))
|
|
conn.execute(CreateMaterializedView(mview1, if_not_exists=True))
|
|
|
|
views = get_all_views(conn)
|
|
view_names = [v.name for v in views]
|
|
|
|
assert "test_view_deps_1" in view_names
|
|
assert "test_mview_deps_1" in view_names
|
|
|
|
# Check materialized flag
|
|
mview = next(v for v in views if v.name == "test_mview_deps_1")
|
|
assert mview.is_materialized is True
|
|
|
|
regular = next(v for v in views if v.name == "test_view_deps_1")
|
|
assert regular.is_materialized is False
|
|
|
|
conn.execute(DropView(view1, if_exists=True))
|
|
conn.execute(DropMaterializedView(mview1, if_exists=True))
|
|
|
|
def test_get_all_views_with_schema_filter(
|
|
self, pg_engine: Engine, pg_one_to_many_tables: dict[str, Table]
|
|
) -> None:
|
|
"""Test getting views filtered by schema."""
|
|
authors = pg_one_to_many_tables["authors"]
|
|
|
|
view = View(
|
|
"test_view_schema_filter",
|
|
select(authors.c.id),
|
|
)
|
|
|
|
with pg_engine.begin() as conn:
|
|
conn.execute(CreateView(view, or_replace=True))
|
|
|
|
# Filter by public schema
|
|
views = get_all_views(conn, schema="public")
|
|
view_names = [v.name for v in views]
|
|
|
|
assert "test_view_schema_filter" in view_names
|
|
|
|
# Filter by non-existent schema
|
|
views = get_all_views(conn, schema="nonexistent")
|
|
assert len(views) == 0
|
|
|
|
conn.execute(DropView(view, if_exists=True))
|
|
|
|
def test_get_all_views_exclude_materialized(
|
|
self, pg_engine: Engine, pg_one_to_many_tables: dict[str, Table]
|
|
) -> None:
|
|
"""Test getting views excluding materialized views."""
|
|
authors = pg_one_to_many_tables["authors"]
|
|
|
|
view = View("test_view_excl", select(authors.c.id))
|
|
mview = MaterializedView(
|
|
"test_mview_excl",
|
|
select(func.count(authors.c.id).label("cnt")),
|
|
with_data=True,
|
|
)
|
|
|
|
with pg_engine.begin() as conn:
|
|
conn.execute(CreateView(view, or_replace=True))
|
|
conn.execute(CreateMaterializedView(mview, if_not_exists=True))
|
|
|
|
# Include materialized
|
|
views = get_all_views(conn, include_materialized=True)
|
|
names = [v.name for v in views]
|
|
assert "test_view_excl" in names
|
|
assert "test_mview_excl" in names
|
|
|
|
# Exclude materialized
|
|
views = get_all_views(conn, include_materialized=False)
|
|
names = [v.name for v in views]
|
|
assert "test_view_excl" in names
|
|
assert "test_mview_excl" not in names
|
|
|
|
conn.execute(DropView(view, if_exists=True))
|
|
conn.execute(DropMaterializedView(mview, if_exists=True))
|
|
|
|
|
|
class TestGetViewDefinition:
|
|
"""Tests for get_view_definition function."""
|
|
|
|
def test_get_view_definition(
|
|
self, pg_engine: Engine, pg_one_to_many_tables: dict[str, Table]
|
|
) -> None:
|
|
"""Test getting view definition."""
|
|
authors = pg_one_to_many_tables["authors"]
|
|
|
|
view = View(
|
|
"test_view_def",
|
|
select(authors.c.id, authors.c.name),
|
|
)
|
|
|
|
with pg_engine.begin() as conn:
|
|
conn.execute(CreateView(view, or_replace=True))
|
|
|
|
definition = get_view_definition(conn, "test_view_def")
|
|
|
|
assert definition is not None
|
|
assert "authors" in definition.lower()
|
|
|
|
conn.execute(DropView(view, if_exists=True))
|
|
|
|
def test_get_view_definition_not_found(self, pg_engine: Engine) -> None:
|
|
"""Test getting definition for non-existent view."""
|
|
with pg_engine.connect() as conn:
|
|
definition = get_view_definition(conn, "nonexistent_view_xyz")
|
|
assert definition is None
|
|
|
|
|
|
class TestGetViewDependencies:
|
|
"""Tests for get_view_dependencies function."""
|
|
|
|
def test_get_view_dependencies(
|
|
self, pg_engine: Engine, pg_one_to_many_tables: dict[str, Table]
|
|
) -> None:
|
|
"""Test getting view dependencies."""
|
|
authors = pg_one_to_many_tables["authors"]
|
|
|
|
# Create base view
|
|
base_view = View(
|
|
"test_base_view_deps",
|
|
select(authors.c.id, authors.c.name),
|
|
)
|
|
|
|
with pg_engine.begin() as conn:
|
|
conn.execute(CreateView(base_view, or_replace=True))
|
|
|
|
# Create dependent view that references base view
|
|
dependent_view = View(
|
|
"test_dependent_view_deps",
|
|
select(base_view.as_table().c.id),
|
|
)
|
|
conn.execute(CreateView(dependent_view, or_replace=True))
|
|
|
|
# Get dependencies
|
|
deps = get_view_dependencies(conn)
|
|
|
|
# Find our dependency
|
|
our_deps = [
|
|
d for d in deps if d.dependent_view == "test_dependent_view_deps"
|
|
]
|
|
|
|
assert len(our_deps) > 0
|
|
# Should reference the base view
|
|
ref_names = [d.referenced_view for d in our_deps]
|
|
assert "test_base_view_deps" in ref_names
|
|
|
|
conn.execute(DropView(dependent_view, if_exists=True, cascade=True))
|
|
conn.execute(DropView(base_view, if_exists=True, cascade=True))
|
|
|
|
|
|
class TestGetDependencyOrder:
|
|
"""Tests for get_dependency_order function."""
|
|
|
|
def test_get_dependency_order(
|
|
self, pg_engine: Engine, pg_one_to_many_tables: dict[str, Table]
|
|
) -> None:
|
|
"""Test getting views in dependency order."""
|
|
authors = pg_one_to_many_tables["authors"]
|
|
|
|
# Create views with dependencies
|
|
view_a = View(
|
|
"test_order_a",
|
|
select(authors.c.id, authors.c.name),
|
|
)
|
|
|
|
with pg_engine.begin() as conn:
|
|
conn.execute(CreateView(view_a, or_replace=True))
|
|
|
|
view_b = View(
|
|
"test_order_b",
|
|
select(view_a.as_table().c.id),
|
|
)
|
|
conn.execute(CreateView(view_b, or_replace=True))
|
|
|
|
# Get dependency order
|
|
ordered = get_dependency_order(conn, schema="public")
|
|
names = [v.name for v in ordered]
|
|
|
|
# view_a should come before view_b (dependencies first)
|
|
if "test_order_a" in names and "test_order_b" in names:
|
|
idx_a = names.index("test_order_a")
|
|
idx_b = names.index("test_order_b")
|
|
assert idx_a < idx_b
|
|
|
|
conn.execute(DropView(view_b, if_exists=True, cascade=True))
|
|
conn.execute(DropView(view_a, if_exists=True, cascade=True))
|
|
|
|
|
|
class TestGetReverseDependencies:
|
|
"""Tests for get_reverse_dependencies function."""
|
|
|
|
def test_get_reverse_dependencies(
|
|
self, pg_engine: Engine, pg_one_to_many_tables: dict[str, Table]
|
|
) -> None:
|
|
"""Test getting reverse dependencies (views that depend on a view)."""
|
|
authors = pg_one_to_many_tables["authors"]
|
|
|
|
# Create base view
|
|
base_view = View(
|
|
"test_reverse_base",
|
|
select(authors.c.id, authors.c.name),
|
|
)
|
|
|
|
with pg_engine.begin() as conn:
|
|
conn.execute(CreateView(base_view, or_replace=True))
|
|
|
|
# Create dependent view
|
|
dep_view = View(
|
|
"test_reverse_dep",
|
|
select(base_view.as_table().c.id),
|
|
)
|
|
conn.execute(CreateView(dep_view, or_replace=True))
|
|
|
|
# Get reverse dependencies of base view
|
|
dependents = get_reverse_dependencies(conn, "test_reverse_base")
|
|
dep_names = [v.name for v in dependents]
|
|
|
|
assert "test_reverse_dep" in dep_names
|
|
|
|
conn.execute(DropView(dep_view, if_exists=True, cascade=True))
|
|
conn.execute(DropView(base_view, if_exists=True, cascade=True))
|
|
|
|
def test_get_reverse_dependencies_none(
|
|
self, pg_engine: Engine, pg_one_to_many_tables: dict[str, Table]
|
|
) -> None:
|
|
"""Test getting reverse dependencies when there are none."""
|
|
authors = pg_one_to_many_tables["authors"]
|
|
|
|
# Create standalone view with no dependents
|
|
standalone = View(
|
|
"test_standalone_view",
|
|
select(authors.c.id),
|
|
)
|
|
|
|
with pg_engine.begin() as conn:
|
|
conn.execute(CreateView(standalone, or_replace=True))
|
|
|
|
dependents = get_reverse_dependencies(conn, "test_standalone_view")
|
|
assert len(dependents) == 0
|
|
|
|
conn.execute(DropView(standalone, if_exists=True))
|