👨‍💻

OSSを読んで学ぶ!Django ORMを徹底解剖

に公開

この記事の概要

本記事では、Django ORMの仕組みへの理解を実際にOSSの内部実装を見ながら深めていきます。

対象読者

  • Djangoを始めたてでORMにまだ慣れていないエンジニア
  • フレームワークのOSSを読んでみたい人

Django ORMとは何か

DjangoのORMは、Pythonのクラスとデータベーステーブルを1対1でマッピングする仕組みです。他のORMと同じようにSQLを直接書かずに、データベースのCRUDが記述できます。

例として以下のようなテーブルを考えます。

User

項目 カラム名 データ型 主キー(PK)
ユーザーID id UUID
名前 name VARCHAR(255)
メールアドレス email VARCHAR(255)
パスワード password VARCHAR(255)

テーブルに対応するクラスは「モデル」と呼ばれ、上記のUserテーブルに対しては以下のように記述できます。

class User(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    name = models.CharField(max_length=255)
    email = models.EmailField(unique=True)
    password = models.CharField(max_length=255)

    def __str__(self):
        return self.name

そして、Userを全件取得するクエリは以下のように書くことができます。

# "select * from users;"に対応
User.objects.all()

特定のEメールを持つユーザーのみを取り出したい時は、

User.objects.filter(email='foo@example.com')
# 単一取得の場合は, User.objects.get(email='foo@example.com')

このような表現になります。
同様に、INSERT, DELETEに対応する表現はそれぞれ

User.objects.create(...)
User.objects.filter(email='foo@example.com').update(password='newpass123')
User.objects.filter(email='foo@example.com').delete()

となります。
このような一見シンプルで抽象化された呼び出しの裏では、Manager, QuerySet, Modelといった複数のクラス・概念が密接に連携しています。

ModelとManager:ORMの中核となる2つの概念

では、ここからソースコードを元に解説をしていきます。
対象リポジトリは以下になります。必要に応じてforkして読んでみてください。
https://github.com/django/django

Django ORMの仕組みを理解する上で重要になるクラスが2つあります。
1つ目はModelBaseクラス(/django/db/models/base.py)、
2つ目はManagerクラス(/django/db/models/manager.py)です。

ModelBaseクラス:ORMの「自動生成装置」

データベーステーブルに対応するModelクラスは、ModelBaseというmetaclassによって生成されます。

class Model(AltersData, metaclass=ModelBase):
    ...

ModelBaseクラスの役割は、metaclassとして設定しているクラス(ここではModelクラス)定義時に自動的な前処理を行うこと。その中で特に重要なのが次の部分です。

# /django/db/models/base.pyより抜粋
class ModelBase:
    ...
    def _prepare(cls):
        ...
        if not opts.managers:
            if any(f.name == "objects" for f in opts.fields):
                raise ValueError(
                    "Model %s must specify a custom Manager, because it has a "
                    "field named 'objects'." % cls.__name__
                                )
            # Managerクラスを初期化
            manager = Manager()
            manager.auto_created = True
            # "cls"は"Model"クラスのこと
            cls.add_to_class("objects", manager)

ここでDjangoはすべてのModelクラスに対して、objectsという名前のManagerを自動付与します。そうです。User.objectsobjectsがまさにManagerクラスのインスタンスに相当しているのです。

Managerクラス:ORMの「フロントエンド」

では、Managerとはそもそも何でしょうか?
Djangoのドキュメントを参照すると、

Managerは、Djangoモデルに対してデータベースクエリ操作を提供するインターフェースです。>Djangoアプリケーション内の各モデルには、少なくとも1つのManagerが存在します。
デフォルトでは、Djangoはオブジェクトという名前のManagerをすべてのModelクラスに付与します。

ManagerPythonのクラスとデータベースを仲介するインターフェイスです。
User.objects.all()User.objects.create()といった操作を受け取り、内部的にはQuerySetクラスを呼び出しています。

# /django/db/models/manager.pyより抜粋
class Manager(BaseManager.from_queryset(QuerySet)):
    pass

from_querysetメソッドでは、QuerySetのメソッド群をManagerに動的にコピーします。

class BaseManager:
    ...
    @classmethod
    def from_queryset(cls, queryset_class, class_name=None):
        # type()でクラスを動的に構築して返している.
        return type(
            class_name or f"{cls.__name__}From{queryset_class.__name__}",
            (cls,),
            {
                "_queryset_class": queryset_class,
                # _get_queryset_methodsでQuerySetが持つメソッドをコピー.
                **cls._get_queryset_methods(queryset_class),
            },
        )

これによって、Manager経由でQuerySetのメソッド(filter, Create, update,...)を呼べるようになるのです。

Model.objects.all()の内部処理(SELECT句)

User.objects.all()の呼び出しから、実際にSQLが発行されるまでを見てみましょう。

  1. User.objects => Managerインスタンスが返却される。
  2. .all() => Managerがget_queryset()を呼び、QuerySetインスタンスを生成し、QuerySetインスタンスのallメソッドが実行される(この時点ではまだSQLは発行しない。遅延評価)
  3. QuerySetの評価 => Python側で値を参照(forループやlist()化)した時点でSQLが実行される。
# /django/db/models/query.pyより抜粋
class QuerySet:
    def all(self):
        """
        QuerySetインスタンスのコピーを返却.
        """
        return self._chain()

QuerySetオブジェクトなどのPythonオブジェクトを、ループやリストにする際に、通常__iter()__メソッドが呼ばれます。
実際にQuerySetクラスの__iter__メソッドをたどっていくと、最終的に_fetch_all()メソッド内でModelIterableクラスのイテレーターが発行されていることがわかります。
同様に、以下に示すModelIterable.__iter__()メソッドが実行されます。

class ModelIterable(BaseIterable):
    def __iter__(self):
        queryset = self.queryset
        db = queryset.db

        # 1. "コンパイラ" (= SQL生成器) を取得
        compiler = queryset.query.get_compiler(using=db)
        fetch_mode = queryset._fetch_mode

        # 2. sqlを実行して結果を取得
        results = compiler.execute_sql(
            chunked_fetch=self.chunked_fetch, chunk_size=self.chunk_size
        )

        ...
        # 3. 各行をモデルインスタンスに変換
        for row in compiler.results_iter(results):
            obj = model_cls.from_db(
                db,
                init_list,
                row[model_fields_start:model_fields_end],
                fetch_mode=fetch_mode,
            )
            if fetch_mode.track_peers:
                peers.append(weak_ref(obj))
                obj._state.peers = peers
            # 関連オブジェクトの処理
            for rel_populator in related_populators:
                rel_populator.populate(row, obj)
            if annotation_col_map:
                for attr_name, col_pos in annotation_col_map.items():
                    setattr(obj, attr_name, row[col_pos])

            for field, rel_objs, rel_getter in known_related_objects:
                # Avoid overwriting objects loaded by, e.g., select_related().
                if field.is_cached(obj):
                    continue
                rel_obj_id = rel_getter(obj)
                try:
                    rel_obj = rel_objs[rel_obj_id]
                except KeyError:
                    pass  # May happen in qs1 | qs2 scenarios.
                else:
                    setattr(obj, field.name, rel_obj)

            yield obj # モデルインスタンスを返却

最終的にこの部分でSQLが発行されていることがわかりました!
このように、Django ORMはforループやlistなどのイテレーターに変換するときにクエリを発行しているんですね。

Model.objects.create()の内部処理(INSERT句)

今度はINSERTに対応する場合はどうでしょうか?
QuerySetクラスのcreateメソッドを見ると以下のようなフローで処理が進むことがわかります。

# /django/db/models/query.py
Class QuerySet(AltersData):
    ...
    def create(self, **kwargs):
        obj = self.model(**kwargs)
        self._for_write = True
        obj.save(force_insert=True, using=self.db)
        return obj

objというのは対象となるModelクラスのことですね。つまり、Modelクラスに定義されているsaveメソッドが実行されることになります。
save()の内部では、save_base() => 'save_table()' => _do_insert()と呼び出していき、最終的にmanagerというオブジェクトの_insert()メソッドが実行されます。ここで、ManagerインスタンスにQuerySetクラスのメソッドをコピーしたことを思い出しましょう。そうです。_insert()メソッドはQuerySetクラスのメソッドです。そして、以下のメソッド内でレコードがDBに挿入されています。

# /django/db/models/query.pyより抜粋
class QuerySet(AltersData):
    def _insert(
            self,
           # <細かい引数>
        ):
           ...
            # 以下でDBにレコードを挿入している.
            query = sql.InsertQuery(
                self.model,
                on_conflict=on_conflict,
                update_fields=update_fields,
                unique_fields=unique_fields,
            )
            query.insert_values(fields, objs, raw=raw)
            return query.get_compiler(using=using).execute_sql(returning_fields)

まとめ

今回はDjango ORMにおいてクエリが発行されるまでの仕組みをOSSを読みながら解説しました。
Djangoは単にSQLを隠す仕組みではなく、遅延評価やクエリセットのメソッドチェーン、Fオブジェクトなど、様々な機能が提供されているORMです。
他にもupdatedelete, where句に相当する処理もあるので、気になった人はぜひ読んでみてはいかがでしょうか?

Discussion