Alembic detects FK changes on PostgreSQL when not using public schema #1729
-
Describe the bug Expected behavior To Reproduce from sqlalchemy import create_engine, text as sqlatext, Column, Integer, String, DateTime, DECIMAL, Table, ForeignKey, Boolean
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, relationship
dbschema = 'iris'
engine = create_engine(DATABASE_URL, connect_args={})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
Base.metadata.schema = dbschema
user_group_association = Table(
'user_group', Base.metadata,
Column('user_id', Integer, ForeignKey('iris.users.id', name="fk_user_group_user_id"),primary_key=True),
Column('group_id', Integer, ForeignKey('iris.groups.id', name="fk_user_group_group_id"),primary_key=True),
schema=dbschema,
)
service_group_association = Table(
'service_group',
Base.metadata,
Column('service_id', Integer, ForeignKey('iris.services.id', name="fk_service_group_service_id"), primary_key=True),
Column('group_id', Integer, ForeignKey('iris.groups.id', name="fk_service_group_group_id"), primary_key=True),
schema=dbschema,
)
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
username = Column(String, unique=True, index=True)
email = Column(String, unique=True, index=True)
hashed_password = Column(String)
created_at = Column(DateTime, default=datetime.utcnow)
first_name = Column(String, nullable=True)
last_name = Column(String, nullable=True)
groups = relationship('Group', secondary=user_group_association, back_populates='users')
class Group(Base):
__tablename__ = 'groups'
id = Column(Integer, primary_key=True, index=True)
name = Column(String, unique=True, index=True)
mapped_to = Column(String, nullable=True)
users = relationship('User', secondary=user_group_association, back_populates='groups')
services = relationship('Service', secondary=service_group_association, back_populates='groups') Error def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint(op.f('fk_service_group_service_id'), 'service_group', type_='foreignkey')
op.drop_constraint(op.f('fk_service_group_group_id'), 'service_group', type_='foreignkey')
op.create_foreign_key('fk_service_group_group_id', 'service_group', 'groups', ['group_id'], ['id'], source_schema='iris', referent_schema='iris')
op.create_foreign_key('fk_service_group_service_id', 'service_group', 'services', ['service_id'], ['id'], source_schema='iris', referent_schema='iris')
op.drop_constraint(op.f('fk_user_group_user_id'), 'user_group', type_='foreignkey')
op.drop_constraint(op.f('fk_user_group_group_id'), 'user_group', type_='foreignkey')
op.create_foreign_key('fk_user_group_user_id', 'user_group', 'users', ['user_id'], ['id'], source_schema='iris', referent_schema='iris')
op.create_foreign_key('fk_user_group_group_id', 'user_group', 'groups', ['group_id'], ['id'], source_schema='iris', referent_schema='iris')
# ### end Alembic commands ### Versions.
Additional context def run_migrations_online() -> None:
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
connectable = engine_from_config(
config.get_section(config.config_ini_section, {}),
prefix="sqlalchemy.",
poolclass=pool.NullPool,
)
with connectable.connect() as connection:
# make use of non-supported SQLAlchemy attribute to ensure
# the dialect reflects tables in terms of the current tenant name
connection.dialect.default_schema_name = 'iris'
context.configure(
connection=connection,
target_metadata=target_metadata,
#include_schemas=True,
#version_table_schema='iris'
)
connection.execute(text("CREATE SCHEMA IF NOT EXISTS iris"))
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online() Have a nice day! |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 24 replies
-
hi - the source of the issue is in your env.py:
doing multi-tenancy on PostgreSQL schemas is difficult. For this reason we have a complete recipe for how to do this here: https://alembic.sqlalchemy.org/en/latest/cookbook.html#rudimental-schema-level-multi-tenancy-for-postgresql-mysql-other-databases the main thing is that this all goes off of the PG schema search path. See https://docs.sqlalchemy.org/en/20/dialects/postgresql.html#remote-schema-table-introspection-and-postgresql-search-path for background |
Beta Was this translation helpful? Give feedback.
Thanks Federico, it's not a driver or settings modification. More like a bad coincidence.
Or maybe a coincidence with user name, schema name and the aproach with redirecting the schema.
root cause of the problem:
Using the postgresql.conf default search path like :
`
search_path
"$user", public`
My user is named
iris
too.So, the side effect of this is, that we have suddenly a search_path pointing to the iris application schema.
I've confirmed this with another database and another user.
The fault happens, as soon as user name == schema name.
Is there a possible alembic side fix?
Of course i could:
a) rename the user
b) change default search_path to not include $user
c) change the schema …