mirror of
https://github.com/d3vyce/sqlalchemy-pgview.git
synced 2026-03-01 18:00:47 +01:00
291 lines
8.9 KiB
Python
291 lines
8.9 KiB
Python
"""Tests for metadata integration and auto-registration."""
|
|
|
|
from decimal import Decimal
|
|
|
|
from sqlalchemy import (
|
|
Column,
|
|
ForeignKey,
|
|
Integer,
|
|
MetaData,
|
|
Numeric,
|
|
String,
|
|
Table,
|
|
func,
|
|
insert,
|
|
select,
|
|
)
|
|
from sqlalchemy.engine import Engine
|
|
|
|
from sqlalchemy_pgview import (
|
|
MaterializedView,
|
|
View,
|
|
get_materialized_views,
|
|
get_views,
|
|
)
|
|
|
|
|
|
class TestAutoRegistration:
|
|
"""Tests for auto-registration of views with metadata."""
|
|
|
|
def test_view_auto_registers_with_metadata(self) -> None:
|
|
"""Test that View is auto-registered when metadata is provided."""
|
|
metadata = MetaData()
|
|
|
|
# Dummy table for selectable
|
|
users = Table("users", metadata, Column("id", Integer, primary_key=True))
|
|
|
|
# View should be auto-registered
|
|
user_view = View(
|
|
"user_view",
|
|
select(users.c.id),
|
|
metadata=metadata,
|
|
)
|
|
|
|
registered = get_views(metadata)
|
|
assert "user_view" in registered
|
|
assert registered["user_view"] is user_view
|
|
|
|
def test_materialized_view_auto_registers_with_metadata(self) -> None:
|
|
"""Test that MaterializedView is auto-registered when metadata is provided."""
|
|
metadata = MetaData()
|
|
|
|
users = Table("users", metadata, Column("id", Integer, primary_key=True))
|
|
|
|
mv = MaterializedView(
|
|
"user_mv",
|
|
select(users.c.id),
|
|
metadata=metadata,
|
|
with_data=True,
|
|
)
|
|
|
|
registered = get_materialized_views(metadata)
|
|
assert "user_mv" in registered
|
|
assert registered["user_mv"] is mv
|
|
|
|
def test_view_with_schema_registers_correctly(self) -> None:
|
|
"""Test that views with schema are registered with correct key."""
|
|
metadata = MetaData()
|
|
|
|
users = Table("users", metadata, Column("id", Integer, primary_key=True))
|
|
|
|
view = View(
|
|
"stats_view",
|
|
select(users.c.id),
|
|
schema="analytics",
|
|
metadata=metadata,
|
|
)
|
|
|
|
registered = get_views(metadata)
|
|
assert "analytics.stats_view" in registered
|
|
assert registered["analytics.stats_view"] is view
|
|
|
|
def test_view_without_metadata_not_registered(self) -> None:
|
|
"""Test that views without metadata are not auto-registered."""
|
|
metadata = MetaData()
|
|
|
|
users = Table("users", metadata, Column("id", Integer, primary_key=True))
|
|
|
|
# No metadata provided - should not be registered
|
|
View("orphan_view", select(users.c.id))
|
|
|
|
registered = get_views(metadata)
|
|
assert "orphan_view" not in registered
|
|
|
|
def test_multiple_views_registered(self) -> None:
|
|
"""Test that multiple views can be registered."""
|
|
metadata = MetaData()
|
|
|
|
users = Table("users", metadata, Column("id", Integer, primary_key=True))
|
|
|
|
View("view1", select(users.c.id), metadata=metadata)
|
|
View("view2", select(users.c.id), metadata=metadata)
|
|
MaterializedView("mv1", select(users.c.id), metadata=metadata)
|
|
|
|
views = get_views(metadata)
|
|
mviews = get_materialized_views(metadata)
|
|
|
|
assert len(views) == 2
|
|
assert "view1" in views
|
|
assert "view2" in views
|
|
assert len(mviews) == 1
|
|
assert "mv1" in mviews
|
|
|
|
|
|
class TestMetadataCreateAll:
|
|
"""Tests for metadata.create_all() with views."""
|
|
|
|
def test_create_all_creates_views(self, pg_engine: Engine) -> None:
|
|
"""Test that metadata.create_all() creates registered views."""
|
|
metadata = MetaData()
|
|
|
|
users = Table(
|
|
"test_users_ca",
|
|
metadata,
|
|
Column("id", Integer, primary_key=True),
|
|
Column("name", String(100)),
|
|
)
|
|
|
|
orders = Table(
|
|
"test_orders_ca",
|
|
metadata,
|
|
Column("id", Integer, primary_key=True),
|
|
Column("user_id", Integer, ForeignKey("test_users_ca.id")),
|
|
Column("total", Numeric(10, 2)),
|
|
)
|
|
|
|
# Define view - auto-registered
|
|
user_stats = View(
|
|
"test_user_stats_ca",
|
|
select(
|
|
users.c.id,
|
|
users.c.name,
|
|
func.count(orders.c.id).label("order_count"),
|
|
)
|
|
.select_from(users.outerjoin(orders, users.c.id == orders.c.user_id))
|
|
.group_by(users.c.id, users.c.name),
|
|
metadata=metadata,
|
|
)
|
|
|
|
try:
|
|
# Create all - should create tables AND views
|
|
metadata.create_all(pg_engine)
|
|
|
|
# Insert test data
|
|
with pg_engine.begin() as conn:
|
|
conn.execute(insert(users).values(id=1, name="Alice"))
|
|
conn.execute(insert(users).values(id=2, name="Bob"))
|
|
conn.execute(insert(orders).values(id=1, user_id=1, total=100))
|
|
conn.execute(insert(orders).values(id=2, user_id=1, total=200))
|
|
|
|
# Query the view
|
|
with pg_engine.connect() as conn:
|
|
result = conn.execute(
|
|
select(user_stats.as_table()).order_by(user_stats.as_table().c.name)
|
|
).fetchall()
|
|
|
|
assert len(result) == 2
|
|
assert result[0].name == "Alice"
|
|
assert result[0].order_count == 2
|
|
assert result[1].name == "Bob"
|
|
assert result[1].order_count == 0
|
|
|
|
finally:
|
|
metadata.drop_all(pg_engine)
|
|
|
|
def test_create_all_creates_materialized_views(self, pg_engine: Engine) -> None:
|
|
"""Test that metadata.create_all() creates materialized views."""
|
|
metadata = MetaData()
|
|
|
|
orders = Table(
|
|
"test_orders_mv",
|
|
metadata,
|
|
Column("id", Integer, primary_key=True),
|
|
Column("total", Numeric(10, 2)),
|
|
)
|
|
|
|
# Define materialized view - auto-registered
|
|
order_summary = MaterializedView(
|
|
"test_order_summary_mv",
|
|
select(
|
|
func.count(orders.c.id).label("order_count"),
|
|
func.sum(orders.c.total).label("total_revenue"),
|
|
),
|
|
metadata=metadata,
|
|
with_data=True,
|
|
)
|
|
|
|
try:
|
|
metadata.create_all(pg_engine)
|
|
|
|
# Insert test data
|
|
with pg_engine.begin() as conn:
|
|
conn.execute(insert(orders).values(id=1, total=100))
|
|
conn.execute(insert(orders).values(id=2, total=200))
|
|
|
|
# Query the materialized view (shows data at creation time = 0)
|
|
with pg_engine.connect() as conn:
|
|
result = conn.execute(select(order_summary.as_table())).fetchone()
|
|
# MV was created before data, so shows 0
|
|
assert result.order_count == 0
|
|
|
|
# Refresh to see actual data
|
|
order_summary.refresh(conn)
|
|
|
|
result = conn.execute(select(order_summary.as_table())).fetchone()
|
|
assert result.order_count == 2
|
|
assert result.total_revenue == Decimal("300")
|
|
|
|
finally:
|
|
metadata.drop_all(pg_engine)
|
|
|
|
def test_drop_all_drops_views(self, pg_engine: Engine) -> None:
|
|
"""Test that metadata.drop_all() drops registered views."""
|
|
from sqlalchemy import text
|
|
|
|
metadata = MetaData()
|
|
|
|
users = Table(
|
|
"test_users_drop",
|
|
metadata,
|
|
Column("id", Integer, primary_key=True),
|
|
)
|
|
|
|
View(
|
|
"test_view_drop",
|
|
select(users.c.id),
|
|
metadata=metadata,
|
|
)
|
|
|
|
MaterializedView(
|
|
"test_mv_drop",
|
|
select(users.c.id),
|
|
metadata=metadata,
|
|
)
|
|
|
|
# Create all
|
|
metadata.create_all(pg_engine)
|
|
|
|
# Verify views exist
|
|
with pg_engine.connect() as conn:
|
|
result = conn.execute(
|
|
text(
|
|
"SELECT COUNT(*) FROM pg_class WHERE relname IN ('test_view_drop', 'test_mv_drop')"
|
|
)
|
|
).scalar()
|
|
assert result == 2
|
|
|
|
# Drop all
|
|
metadata.drop_all(pg_engine)
|
|
|
|
# Verify views are gone
|
|
with pg_engine.connect() as conn:
|
|
result = conn.execute(
|
|
text(
|
|
"SELECT COUNT(*) FROM pg_class WHERE relname IN ('test_view_drop', 'test_mv_drop')"
|
|
)
|
|
).scalar()
|
|
assert result == 0
|
|
|
|
def test_views_created_after_tables(self, pg_engine: Engine) -> None:
|
|
"""Test that views are created after tables (dependencies work)."""
|
|
metadata = MetaData()
|
|
|
|
# Table
|
|
users = Table(
|
|
"test_users_order",
|
|
metadata,
|
|
Column("id", Integer, primary_key=True),
|
|
Column("name", String(100)),
|
|
)
|
|
|
|
# View that depends on table
|
|
View(
|
|
"test_users_view_order",
|
|
select(users.c.id, users.c.name),
|
|
metadata=metadata,
|
|
)
|
|
|
|
# This should not raise - tables created before views
|
|
metadata.create_all(pg_engine)
|
|
metadata.drop_all(pg_engine)
|