Skip to content

Add support to schema editor for cloning table into schema #207

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Apr 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ executors:
type: string
docker:
- image: python:<< parameters.version >>-buster
- image: postgres:12.0
- image: postgres:13.0
environment:
POSTGRES_DB: 'psqlextra'
POSTGRES_USER: 'psqlextra'
Expand Down
92 changes: 82 additions & 10 deletions psqlextra/backend/introspection.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from dataclasses import dataclass
from typing import List, Optional, Tuple
from typing import Dict, List, Optional, Tuple

from psqlextra.types import PostgresPartitioningMethod

Expand Down Expand Up @@ -48,6 +48,22 @@ def partition_by_name(
class PostgresIntrospection(base_impl.introspection()):
"""Adds introspection features specific to PostgreSQL."""

# TODO: This class is a mess, both here and in the
# the base.
#
# Some methods return untyped dicts, some named tuples,
# some flat lists of strings. It's horribly inconsistent.
#
# Most methods are poorly named. For example; `get_table_description`
# does not return a complete table description. It merely returns
# the columns.
#
# We do our best in this class to stay consistent with
# the base in Django by respecting its naming scheme
# and commonly used return types. Creating an API that
# matches the look&feel from the Django base class
# is more important than fixing those issues.

def get_partitioned_tables(
self, cursor
) -> PostgresIntrospectedPartitonedTable:
Expand Down Expand Up @@ -172,6 +188,9 @@ def get_partition_key(self, cursor, table_name: str) -> List[str]:
cursor.execute(sql, (table_name,))
return [row[0] for row in cursor.fetchall()]

def get_columns(self, cursor, table_name: str):
return self.get_table_description(cursor, table_name)

def get_constraints(self, cursor, table_name: str):
"""Retrieve any constraints or keys (unique, pk, fk, check, index)
across one or more columns.
Expand Down Expand Up @@ -202,15 +221,68 @@ def get_constraints(self, cursor, table_name: str):
def get_table_locks(self, cursor) -> List[Tuple[str, str, str]]:
cursor.execute(
"""
SELECT
n.nspname,
t.relname,
l.mode
FROM pg_locks l
INNER JOIN pg_class t ON t.oid = l.relation
INNER JOIN pg_namespace n ON n.oid = t.relnamespace
WHERE t.relnamespace >= 2200
ORDER BY n.nspname, t.relname, l.mode"""
SELECT
n.nspname,
t.relname,
l.mode
FROM pg_locks l
INNER JOIN pg_class t ON t.oid = l.relation
INNER JOIN pg_namespace n ON n.oid = t.relnamespace
WHERE t.relnamespace >= 2200
ORDER BY n.nspname, t.relname, l.mode
"""
)

return cursor.fetchall()

def get_storage_settings(self, cursor, table_name: str) -> Dict[str, str]:
sql = """
SELECT
unnest(c.reloptions || array(select 'toast.' || x from pg_catalog.unnest(tc.reloptions) x))
FROM
pg_catalog.pg_class c
LEFT JOIN
pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid)
LEFT JOIN
pg_catalog.pg_am am ON (c.relam = am.oid)
WHERE
c.relname::text = %s
AND pg_catalog.pg_table_is_visible(c.oid)
"""

cursor.execute(sql, (table_name,))

storage_settings = {}
for row in cursor.fetchall():
# It's hard to believe, but storage settings are really
# represented as `key=value` strings in Postgres.
# See: https://www.postgresql.org/docs/current/catalog-pg-class.html
name, value = row[0].split("=")
storage_settings[name] = value

return storage_settings

def get_relations(self, cursor, table_name: str):
"""Gets a dictionary {field_name: (field_name_other_table,
other_table)} representing all relations in the specified table.

This is overriden because the query in Django does not handle
relations between tables in different schemas properly.
"""

cursor.execute(
"""
SELECT a1.attname, c2.relname, a2.attname
FROM pg_constraint con
LEFT JOIN pg_class c1 ON con.conrelid = c1.oid
LEFT JOIN pg_class c2 ON con.confrelid = c2.oid
LEFT JOIN pg_attribute a1 ON c1.oid = a1.attrelid AND a1.attnum = con.conkey[1]
LEFT JOIN pg_attribute a2 ON c2.oid = a2.attrelid AND a2.attnum = con.confkey[1]
WHERE
con.conrelid = %s::regclass AND
con.contype = 'f' AND
pg_catalog.pg_table_is_visible(c1.oid)
""",
[table_name],
)
return {row[0]: (row[2], row[1]) for row in cursor.fetchall()}
Loading