LoginSignup
0
3

More than 5 years have passed since last update.

Djangoでモデル保存時(初回)に実行されるSQLを確認する

Posted at

はじめに

自サイトでDjangoを読むというソースコード読解をやっています。
その中で、モデル保存の部分を読んでいるときに「これってなんかのメソッド呼んだら実行されるSQL表示できないのかな」と思ったのですがなさそうなので「こうやれば表示できる」という方法を投稿しておきます。

チュートリアル確認

Djangoチュートリアル その2では次の手順でモデルをDBに保存しています。

>>> from polls.models import Question, Choice
>>> from django.utils import timezone
>>> question = Question(question_text="What's new?", pub_date=timezone.now())
>>> question.save()

が、ここでsaveメソッドを呼ぶとSQLを構築してDBへの保存まで一気にされてしまいます。というわけでsaveメソッドを呼ぶのではなく、saveメソッドの中で行われていることの重要部分だけを手動で再現して実行されるSQLを出力しました。

準備その1:保存先DBの決定

まずはsaveメソッドで行われている処理の抜粋。

>>> cls = question.__class__
>>> from django.db import router
>>> using = router.db_for_write(cls, instance=question)

モデルのクラス(クラスオブジェクト)はこの後も使うので変数に入れておきます。
django.db.routerというのはDBを分散している場合にどのDBに保存するかの決定する仕組みです。分散していなければ普通は'default'になります。

準備その2:フィールド情報とかの取得

saveメソッドから処理が進んでいくと_save_tableというメソッドに着きます。この中でSQLを構築するために必要なものだけ取り出すと、

>>> from django.db.models.fields import AutoField
>>> meta = cls._meta
>>> fields = meta.local_concrete_fields
>>> fields = [f for f in fields if not isinstance(f, AutoField)]
>>> manager = cls._base_manager

metaというのはdb.models.optionsモジュールのOptionsクラスで、この中にフィールド情報とかが全部入っています。からくりについてはご興味があれば私の読解記事を参照してください。
managerは大雑把に言うとモデルに関する処理を取り仕切っているクラスです。

初回(INSERT)なのか更新(UPDATE)なのかで分岐しますが今回はINSERTなので、do_insertで行われている処理をしておきます。

>>> objs = [question]

INSERT SQLの表示

というわけで準備が整ったので、以下を実行するとSQLが表示されます。なお、managerの属性のmodelはModelインスタンスではなくModelクラスです。

>>> from django.db.models import sql
>>> query = sql.InsertQuery(manager.model)
>>> query.insert_values(fields, objs)
>>> compiler = query.get_compiler(using=using)
>>> compiler.return_id = True
>>> compiler.as_sql()
[('INSERT INTO "polls_question" ("question_text", "pub_date") VALUES (%s, %s)', ["What's new?", '2017-07-24 13:35:55.856747'])]

これらのコードはModelクラスではなく、django.db.models.managerモジュールのManagerクラスに書かれています。というのは半分嘘で、Managerクラスは継承関係がめんどくさく、下記の処理(_insertメソッドの抜粋)はdjango.db.models.queryモジュールのQuerySetクラスにあります。

おまけ

上記の処理を一度に行う関数を作りました。

sql_dumper.py
from django.db import router
from django.db.models.fields import AutoField
from django.db.models import sql

def show_insert_sql(instance):
    cls = instance.__class__
    using = router.db_for_write(cls, instance=instance)
    meta = cls._meta
    fields = meta.local_concrete_fields
    fields = [f for f in fields if not isinstance(f, AutoField)]
    manager = cls._base_manager
    objs = [question]

    query = sql.InsertQuery(manager.model)
    query.insert_values(fields, objs)
    compiler = query.get_compiler(using=using)
    compiler.return_id = True
    return compiler.as_sql()
0
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
3