mirror of
https://github.com/d3vyce/sqlalchemy-pgview.git
synced 2026-03-02 02:00:47 +01:00
Initial commit
This commit is contained in:
207
docs/guide/dependencies.md
Normal file
207
docs/guide/dependencies.md
Normal file
@@ -0,0 +1,207 @@
|
||||
# Dependency Tracking
|
||||
|
||||
SQLAlchemy-PGView can query PostgreSQL system catalogs to discover view dependencies, helping you manage complex view hierarchies.
|
||||
|
||||
## Why Track Dependencies?
|
||||
|
||||
- **Safe migrations**: Know which views to drop/recreate when modifying a base view
|
||||
- **Impact analysis**: Understand what breaks if you change a table or view
|
||||
- **Correct ordering**: Create views in the right order during deployment
|
||||
|
||||
## Getting All Views
|
||||
|
||||
```python
|
||||
from sqlalchemy_pgview import get_all_views
|
||||
|
||||
with engine.connect() as conn:
|
||||
views = get_all_views(conn)
|
||||
|
||||
for view in views:
|
||||
print(f"{view.schema}.{view.name}")
|
||||
print(f" Materialized: {view.is_materialized}")
|
||||
print(f" Definition: {view.definition[:50]}...")
|
||||
|
||||
# Only views in 'analytics' schema
|
||||
views = get_all_views(conn, schema="analytics")
|
||||
|
||||
# Exclude materialized views
|
||||
views = get_all_views(conn, include_materialized=False)
|
||||
```
|
||||
|
||||
## Getting View Definition
|
||||
|
||||
```python
|
||||
from sqlalchemy_pgview import get_view_definition
|
||||
|
||||
with engine.connect() as conn:
|
||||
definition = get_view_definition(conn, "user_stats")
|
||||
print(definition)
|
||||
# SELECT u.id, u.name, count(o.id) AS order_count
|
||||
# FROM users u LEFT JOIN orders o ON u.id = o.user_id
|
||||
# GROUP BY u.id, u.name
|
||||
```
|
||||
|
||||
## Direct Dependencies
|
||||
|
||||
Find what a view depends on:
|
||||
|
||||
```python
|
||||
from sqlalchemy_pgview import get_view_dependencies
|
||||
|
||||
with engine.connect() as conn:
|
||||
deps = get_view_dependencies(conn, "user_order_summary")
|
||||
|
||||
for dep in deps:
|
||||
print(f"{dep.dependent_fullname} depends on {dep.referenced_fullname}")
|
||||
```
|
||||
|
||||
## Reverse Dependencies
|
||||
|
||||
Find what depends on a view (impact analysis):
|
||||
|
||||
```python
|
||||
from sqlalchemy_pgview import get_reverse_dependencies
|
||||
|
||||
with engine.connect() as conn:
|
||||
# What views depend on 'users' table/view?
|
||||
dependents = get_reverse_dependencies(conn, "users")
|
||||
|
||||
print("Views that depend on 'users':")
|
||||
for view in dependents:
|
||||
print(f" - {view.fullname}")
|
||||
```
|
||||
|
||||
## Dependency Order
|
||||
|
||||
Get views sorted by dependencies (dependencies first):
|
||||
|
||||
```python
|
||||
from sqlalchemy_pgview import get_dependency_order
|
||||
|
||||
with engine.connect() as conn:
|
||||
ordered_views = get_dependency_order(conn)
|
||||
|
||||
print("Views in creation order:")
|
||||
for i, view in enumerate(ordered_views, 1):
|
||||
print(f"{i}. {view.fullname}")
|
||||
```
|
||||
|
||||
This is useful for:
|
||||
|
||||
- **Creating views**: Process in order to avoid "relation does not exist" errors
|
||||
- **Dropping views**: Process in reverse order to avoid dependency errors
|
||||
|
||||
## Safe View Modification
|
||||
|
||||
```python
|
||||
from sqlalchemy_pgview import (
|
||||
get_reverse_dependencies,
|
||||
get_view_definition,
|
||||
DropView,
|
||||
CreateView,
|
||||
)
|
||||
|
||||
def modify_view_safely(conn, view_name, new_definition):
|
||||
"""Modify a view by dropping/recreating it and all dependents."""
|
||||
|
||||
# Get all views that depend on this one
|
||||
dependents = get_reverse_dependencies(conn, view_name)
|
||||
|
||||
# Save their definitions
|
||||
saved_definitions = {}
|
||||
for dep in dependents:
|
||||
saved_definitions[dep.fullname] = get_view_definition(
|
||||
conn, dep.name, dep.schema
|
||||
)
|
||||
|
||||
# Drop dependents in reverse order
|
||||
for dep in reversed(dependents):
|
||||
conn.execute(DropView(dep.name, schema=dep.schema, if_exists=True))
|
||||
|
||||
# Drop and recreate the target view
|
||||
conn.execute(DropView(view_name, if_exists=True))
|
||||
conn.execute(text(f"CREATE VIEW {view_name} AS {new_definition}"))
|
||||
|
||||
# Recreate dependents in order
|
||||
for dep in dependents:
|
||||
definition = saved_definitions[dep.fullname]
|
||||
conn.execute(text(
|
||||
f"CREATE VIEW {dep.fullname} AS {definition}"
|
||||
))
|
||||
```
|
||||
|
||||
## Migration Helper
|
||||
|
||||
```python
|
||||
def generate_view_migration(conn, schema=None):
|
||||
"""Generate migration code for all views in dependency order."""
|
||||
|
||||
views = get_dependency_order(conn, schema=schema)
|
||||
|
||||
print("def upgrade():")
|
||||
for view in views:
|
||||
view_type = "materialized_view" if view.is_materialized else "view"
|
||||
definition = view.definition.replace("'", "\\'")
|
||||
print(f" op.create_{view_type}(")
|
||||
print(f" '{view.name}',")
|
||||
print(f" '''{definition}''',")
|
||||
if view.schema != "public":
|
||||
print(f" schema='{view.schema}',")
|
||||
print(f" )")
|
||||
print()
|
||||
|
||||
print("def downgrade():")
|
||||
for view in reversed(views):
|
||||
view_type = "materialized_view" if view.is_materialized else "view"
|
||||
print(f" op.drop_{view_type}('{view.name}'", end="")
|
||||
if view.schema != "public":
|
||||
print(f", schema='{view.schema}'", end="")
|
||||
print(")")
|
||||
```
|
||||
|
||||
## Dependency Visualization
|
||||
|
||||
```python
|
||||
def print_dependency_tree(conn, view_name, indent=0):
|
||||
"""Print a tree of view dependencies."""
|
||||
prefix = " " * indent
|
||||
print(f"{prefix}- {view_name}")
|
||||
|
||||
deps = get_view_dependencies(conn, view_name)
|
||||
for dep in deps:
|
||||
print_dependency_tree(conn, dep.referenced_fullname, indent + 1)
|
||||
```
|
||||
|
||||
Output:
|
||||
```
|
||||
- sales_summary
|
||||
- monthly_sales
|
||||
- orders
|
||||
- products
|
||||
- customer_stats
|
||||
- customers
|
||||
- orders
|
||||
```
|
||||
|
||||
## Data Classes
|
||||
|
||||
### ViewInfo
|
||||
|
||||
| Attribute | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `name` | `str` | View name |
|
||||
| `schema` | `str` | Schema name |
|
||||
| `definition` | `str` | SQL definition |
|
||||
| `is_materialized` | `bool` | Whether the view is materialized |
|
||||
| `fullname` | `str` | `schema.name` (property) |
|
||||
|
||||
### ViewDependency
|
||||
|
||||
| Attribute | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `dependent_view` | `str` | Name of the dependent view |
|
||||
| `dependent_schema` | `str` | Schema of the dependent view |
|
||||
| `referenced_view` | `str` | Name of the referenced object |
|
||||
| `referenced_schema` | `str` | Schema of the referenced object |
|
||||
| `dependent_fullname` | `str` | `schema.view` (property) |
|
||||
| `referenced_fullname` | `str` | `schema.view` (property) |
|
||||
Reference in New Issue
Block a user