SQLAlchemy를 쓰면서 가장 혼란스러운 것 중 하나가 'Query 객체는 도대체 언제 SELECT 쿼리를 수행하는가?'였다. 마주치게 되는 상황은 이렇다.

  • query.all()로 SELECT의 결과를 모두 가져올 수 있다고 한다. 이는 전형적인 반복자(iterator)로, for row in query.all()처럼 for문에서 사용할 수 있다.
  • 그런데 그냥 query 자체를 for문에 넣는 예제도 보인다. for row in query를 해도 문제가 없다.
  • LIMIT 쿼리를 어떻게 표현하는지 검색했더니, 그냥 슬라이싱하라고 한다. 그래서 for row in query[:10].all()로 했더니 에러가 발생하고, for row in query[:10]처럼 표현하니 제대로 동작한다.

그냥 SQLAlchemy의 소스 코드를 GitHub에서 살펴보면, 그 비밀을 알 수 있다. sqlalchemy.orm.query.py 모듈을 보면 되고, 아래는 그들 중 일부를 가져온 snippet이다.

__iter__

Query 객체는 __iter__ 메소드에서 실제로 SELECT를 수행한다고 요약할 수 있다. __iter__ 메소드가 호출되는 타이밍 중 몇개를 예로 들면,

  • iter 함수에 전달되었을 때
  • for문에 사용되었을 때(for문은 in 우측에 전달된 객체를 iter 함수에 전달하므로)
  • list 함수에 전달되었을 때

그럼 의문이 조금 풀린다.

  • for row in query : for문에 의해 간접적으로 __iter__가 호출되므로 잘 동작한다.
  • for row in query.all() : 위 snippet을 보면 알 수 있듯, list(self)의 반환을 iteration하므로 잘 동작한다.

__getitem__

__getitem__ 메소드는 인덱싱과 슬라이싱 연산을 오버라이딩한다. Query.__getitem__ 메소드는

  • 슬라이스를 받았다면 현재 Query 객체에 LIMIT 쿼리를 추가하는 self.slice 메소드를 호출한 후, 이를 통해 LIMIT이 반영된 Query 객체의 SELECT 결과를 반환하거나
  • 인덱스를 받았고 그 인덱스가 -1이라면 list(self)[-1]을 반환하고, 아니라면 list(self[item:item + 1])[0]으로 __getitem__을 재귀호출하여 해당 인덱스에 대한 row 하나만을 가져오도록 LIMIT 쿼리를 수행해서 경제적으로 row를 반환한다. 예를 들어 query[15]에 대해서는, LIMIT 15, 1 쿼리가 추가된다.

따라서,

  • for row in query[5:8] : __getitem__에 슬라이스가 전달되고, LIMIT 5, 3 쿼리가 추가된 Query 객체의 iterator가 반환된다.
  • row = query[10] : __getitem__에 인덱스가 전달되고, LIMIT 10, 1 쿼리가 추가된 Query 객체의 결과 중 0번째 인덱스가 반환된다.

'Python 계열 > SQLAlchemy' 카테고리의 다른 글

Column.like, Column.ilike, not_, ~expr  (0) 2019.02.12
특정 컬럼만 SELECT  (0) 2019.02.12
aliasing과 함수  (0) 2019.02.12
limit  (0) 2019.02.12
Multiple Primary Key  (0) 2018.10.18

+ Recent posts