2012-02-15 1 views
32

우리는 SQLAlchemy 및 postgres를 사용하여 다중 점유 응용 프로그램을 호스팅합니다. 필자는 각 임차인별로 별도의 데이터베이스를 사용하지 않고 여러 스키마가있는 단일 데이터베이스로 이동하는 방법을 모색 중입니다. SQLAlchemy가이를 기본적으로 지원합니까? 나는 기본적으로 그냥 예를 들어 ... 나는 모든 테이블에 대한 스키마를 전환 할Postgres 스키마의 SQLAlchemy 지원

select * from client1.users 

대신 단지

select * from users 

참고 소정 스키마로 시작되어야 나오는 모든 쿼리를 원하는 특정 요청/요청 집합이 여기 저기에있는 단일 테이블이 아닙니다.

나는 이것이 커스텀 질의 클래스로도 달성 될 수 있다고 상상한다. 그러나 나는 이미이 정맥에서 어떤 것이 이루어지지 않았다고 상상할 수 없다. 그것은 작동하지만 당신이 시도 할 수있는 경우

답변

35

이 잘이에 갈 수있는 몇 가지 방법이있다과 방법에 따라 달라집니다 앱이 구조화되어 있습니다. 가장 기본적인 방법은 다음과 같습니다.

전체 응용 프로그램 내에서 한 번에 하나의 "클라이언트"가 실행되는 방식이라면 끝났습니다.

하지만 여기서 잘못된 점은 해당 MetaData의 모든 테이블이 해당 스키마에 있음을 의미합니다. 하나의 응용 프로그램이 여러 클라이언트를 동시에 지원하기를 원하면 (보통 "다중 점유자"가 의미하는 것), 이는 MetaData 사본을 작성하고 각 클라이언트의 모든 매핑을 제거해야하므로 다루기 힘들 수 있습니다.

client1_foo = Client1Foo() 

그 경우 당신은 "기업과 협력 할 것입니다 : 당신이 정말로 원하는 경우 이러한 접근 방식은, 할 수 있습니다, 그것은 작동하는 방법은 같은 특정 매핑 클래스와 각 클라이언트에 액세스 할 것입니다 sometable.tometadata() (http://docs.sqlalchemy.org/en/latest/core/metadata.html#sqlalchemy.schema.Table.tometadata 참조)과 함께 http://www.sqlalchemy.org/trac/wiki/UsageRecipes/EntityName에 "이름"제조법을 지정하십시오.

그래서 실제로 작동하는 방식은 응용 프로그램 내의 여러 클라이언트이지만 스레드 당 한 번에 하나씩 만한다고 가정 해 봅니다.

# start request 

# new session 
sess = Session() 

# set the search path 
sess.execute("SET search_path TO client1") 

# do stuff with session 

# close it. if you're using connection pooling, the 
# search path is still set up there, so you might want to 
# revert it first 
sess.close() 

이 마지막 방법은 스틱하는 @compiles 확장을 사용하여 컴파일러를 무시하는 것 : 음 실제로, PostgreSQL을에 그렇게 할 수있는 가장 쉬운 방법은 연결 작업을 시작할 때 검색 경로를 설정하는 것입니다 "스키마"이름을 문장 내에서. 이것은 가능하지만 모든 곳에서 일관된 훅이 없기 때문에 까다로울 것입니다. "테이블"이 생성됩니다. 최선의 방법은 아마도 각 요청마다 검색 경로를 설정하는 것입니다.

+0

감사합니다. 몇 가지 시도를하고 가장 잘 작동하는 것을보고 다시보고 하겠지만 경로는 갈 길이라고 생각합니다. – eleddy

+0

@zzzeek 나는 이것에 대한 거울 질문을 가지고있다. 그러나 Alembic을 위해, 정말로 당신의 의견을 사용할 수 있었다 : http://stackoverflow.com/questions/21109218/alembic-support-for-multiple-postgres-schemas – dtheodor

+4

덧붙여서, 나는 그럭저럭 할 수 있었다. 이것은 다음과 같은 선언적 구문을위한 것입니다 :''Base = declarative_base(); Base.metadata.schema = 'ebay'''. 하지만 아마도 더 좋은 방법 일 것입니다. –

2

는 잘 모르겠어요 Table definitions

의 스키마 속성이있다 :

Table(CP.get('users', metadata, schema='client1',....) 
+0

나는 하나의 요청에 대해 모든 테이블의 모든 쿼리를 전환 할 수 있도록 좀 더 글로벌하게 무언가를 찾고 있습니다. 이를 반영하기 위해 질문을 업데이트 할 것입니다. – eleddy

0

search_path 만 변경할 수 있습니다. 문제

set search_path=client9; 

세션을 시작한 다음 테이블을 무조건 유지하십시오.

또한 데이터베이스 별 또는 사용자 별 기본 search_path를 설정할 수도 있습니다. 기본적으로 빈 스키마로 설정하여 쉽게 설정할 수있는 오류를 쉽게 잡을 수 있습니다.

+0

그건 ... 좋은 생각입니다. 하이 파이브! – eleddy

+0

그리고 session.commit() 후에 새로운 트랜잭션이 시작되어 search_path가 재설정된다는 것을 기억하십시오. SA 세션 이벤트는 새로운 트랜잭션마다 search_path를 설정하는 데 효과적입니다. – Brett

5

당신은 SQLAlchemy의 이벤트 인터페이스를 사용하여이 문제를 관리 할 수 ​​있습니다. 첫 번째 연결을 만들기 전에 그래서, 분명히 이것은 첫 번째 연결이 생성되기 전에 (예 : 응용 프로그램 initiallization에서)

내가 볼 문제 실행해야

from sqlalchemy import event 
from sqlalchemy.pool import Pool 

def set_search_path(db_conn, conn_proxy): 
    print "Setting search path..." 
    db_conn.cursor().execute('set search_path=client9, public') 

event.listen(Pool,'connect', set_search_path) 

의 라인을 따라 리스너를 설정 session.execute (...) 솔루션을 사용하면 세션에서 사용하는 특정 연결에서이 작업이 실행됩니다. 그러나 sqlalchemy에서 세션이 동일한 연결을 무기한으로 계속 사용한다는 것을 보장하는 어떤 것도 볼 수 없습니다. 연결 풀에서 새 연결을 선택하면 검색 경로 설정이 손실됩니다.

데이터베이스 또는 사용자 검색 경로와 다른 search_path 응용 프로그램을 설정하려면 이와 같은 접근 방식이 필요합니다. 엔진 구성에서이 설정을 할 수는 있지만이를 수행 할 수있는 방법은 없습니다. connect 이벤트를 사용하면 작동합니다. 누구나 가지고 있다면 더 간단한 해결책에 관심이 있습니다.

반면에 응용 프로그램 내에서 여러 클라이언트를 처리하려는 경우이 방법은 작동하지 않으며 session.execute (...) 접근 방식이 최선의 방법 일 수 있습니다.

+1

'client9'를 하드 코딩하는 대신 인수로 전달하는 우아한 방법이 있습니까? 내 현재 (해키) 해결 방법은'application_name' 쿼리 arg를 db-url ('? application_name = bla')에 전달한 다음'set_search_path'에서'db_conn.dsn.split ('application_name =') [ 1])'. – rkrzr

0

위의 답변 중 SqlAlchmeny 1.2.4에서 해결 된 답변이 없습니다. 이것이 나를 위해 일한 해결책입니다.

from sqlalchemy import MetaData, Table 
from sqlalchemy import create_engine  

def table_schemato_psql(schema_name, table_name): 

     conn_str = 'postgresql://{username}:{password}@localhost:5432/{database}'.format(
      username='<username>', 
      password='<password>', 
      database='<database name>' 
     ) 

     engine = create_engine(conn_str) 

     with engine.connect() as conn: 
      conn.execute('SET search_path TO {schema}'.format(schema=schema_name)) 

      meta = MetaData() 

      table_data = Table(table_name, meta, 
           autoload=True, 
           autoload_with=conn, 
           postgresql_ignore_search_path=True) 

      for column in table_data.columns: 
       print column.name