Migrasi database dengan mssql-django

Artikel ini menjelaskan cara kerja sistem migrasi Django dengan SQL Server melalui mssql-django backend dan dokumen kasus tepi yang diketahui.

Membuat dan menerapkan migrasi

Alur kerja migrasi Django bekerja dengan cara yang sama dengan SQL Server seperti database lain:

  1. Hasilkan migrasi dari perubahan model:

    python manage.py makemigrations myapp
    
  2. Tinjau file migrasi yang dihasilkan di <app>/migrations/.

  3. Terapkan migrasi ke database:

    python manage.py migrate myapp
    
  4. Periksa status migrasi:

    python manage.py showmigrations myapp
    

Penyiapan proyek awal

Saat Anda menyiapkan proyek Django baru dengan SQL Server, jalankan migrasi untuk membuat tabel bawaan Django (autentikasi, sesi, admin):

python manage.py migrate

Perintah ini membuat semua tabel yang diperlukan oleh aplikasi yang tercantum di INSTALLED_APPS.

SQL kustom dalam migrasi

Gunakan migrations.RunSQL untuk menjalankan pernyataan SQL mentah selama migrasi. Pendekatan ini berguna untuk membuat prosedur tersimpan, pemicu, atau objek khusus SQL Server lainnya:

from django.db import migrations

class Migration(migrations.Migration):

    dependencies = [
        ("myapp", "0001_initial"),
    ]

    operations = [
        migrations.RunSQL(
            sql="CREATE INDEX IX_myapp_product_name ON myapp_product (name);",
            reverse_sql="DROP INDEX IX_myapp_product_name ON myapp_product;",
        ),
    ]

Kasus tepi migrasi yang diketahui

Operasi migrasi berikut memerlukan solusi saat menargetkan SQL Server.

Perubahan AutoField

Mengubah bidang model dari atau ke AutoField pada waktu migrasi tidak didukung. SQL Server tidak mengizinkan penambahan atau penghapusan IDENTITY properti dari kolom yang sudah ada.

Solusi sementara: Buat model baru dengan jenis bidang yang diinginkan. Migrasikan data dari tabel lama ke tabel baru, lalu hapus tabel lama.

Mengganti nama bidang atau model dengan batasan kunci asing

Mengganti nama bidang atau model yang memiliki batasan kunci asing dapat gagal. SQL Server memerlukan penurunan dan pembuatan ulang batasan FK selama operasi penggantian nama.

Solusi sementara: Gunakan migrations.SeparateDatabaseAndState untuk menghilangkan batasan FK, mengganti nama kolom, dan membuat ulang batasan, sambil memberi tahu Django untuk memperbarui status modelnya. Contoh berikut mengubah nama kunci asing product pada model Order menjadi item:

from django.db import migrations

class Migration(migrations.Migration):

    dependencies = [
        ("myapp", "0002_previous"),
    ]

    operations = [
        migrations.SeparateDatabaseAndState(
            database_operations=[
                migrations.RunSQL(
                    sql="ALTER TABLE myapp_order DROP CONSTRAINT FK_order_product;",
                    reverse_sql="ALTER TABLE myapp_order ADD CONSTRAINT FK_order_product FOREIGN KEY (product_id) REFERENCES myapp_product(id);",
                ),
                migrations.RunSQL(
                    sql="EXECUTE sp_rename 'myapp_order.product_id', 'item_id', 'COLUMN';",
                    reverse_sql="EXECUTE sp_rename 'myapp_order.item_id', 'product_id', 'COLUMN';",
                ),
                migrations.RunSQL(
                    sql="ALTER TABLE myapp_order ADD CONSTRAINT FK_order_item FOREIGN KEY (item_id) REFERENCES myapp_product(id);",
                    reverse_sql="ALTER TABLE myapp_order DROP CONSTRAINT FK_order_item;",
                ),
            ],
            state_operations=[
                migrations.RenameField(
                    model_name="order",
                    old_name="product",
                    new_name="item",
                ),
            ],
        ),
    ]

Cari nama batasan aktual dalam database Anda sebelum menjalankan kode T-SQL ini. Django menghasilkan nama constraint yang menyertakan hash singkat, sehingga nama pada skema Anda tidak cocok dengan placeholder yang ditampilkan di sini.

Migrasi squash

Setelah banyak migrasi menumpuk, Anda dapat menggabungkannya menjadi lebih sedikit berkas:

python manage.py squashmigrations myapp 0001 0010

Tip

Selalu uji migrasi yang digabungkan pada basis data baru untuk memastikan migrasi tersebut menghasilkan skema yang benar.

Kolom yang dihasilkan (kolom komputasi)

Backend mssql-django mendukung Django GeneratedField (Django 5.0 dan yang lebih baru), yang memetakan ke SQL Server kolom komputasi.

Kolom yang dihasilkan tersimpan (PERSISTED)

Kolom yang dihasilkan yang disimpan ditulis secara fisik ke disk dan diperbarui saat kolom sumber berubah:

from django.db import models
from django.db.models import F

class Product(models.Model):
    price = models.DecimalField(max_digits=10, decimal_places=2)
    tax_rate = models.DecimalField(max_digits=5, decimal_places=4)
    total_price = models.GeneratedField(
        expression=F("price") * (1 + F("tax_rate")),
        output_field=models.DecimalField(max_digits=10, decimal_places=2),
        db_persist=True,
    )

Ini menghasilkan: total_price AS ([price] * (1 + [tax_rate])) PERSISTED.

Kolom yang dihasilkan virtual

Kolom yang dihasilkan virtual dihitung pada waktu kueri dan tidak menggunakan penyimpanan:

from django.db import models
from django.db.models import F, Value
from django.db.models.functions import Concat

class Employee(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    full_name = models.GeneratedField(
        expression=Concat(F("first_name"), Value(" "), F("last_name")),
        output_field=models.CharField(max_length=101),
        db_persist=False,
    )

Note

SQL Server membatasi indeks pada kolom terhitung yang tidak dipersistenkan. Gunakan db_persist=True jika Anda perlu mengindeks kolom yang dihasilkan.

Komentar tabel dan kolom

Backend mssql-django mendukung fitur Django db_comment (Django 4.2 dan yang lebih baru). Komentar disimpan sebagai MS_Description properti yang diperluas pada objek SQL Server.

Komentar tabel

class AuditLog(models.Model):
    action = models.CharField(max_length=50)
    timestamp = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table_comment = "Tracks user actions for compliance auditing."

Komentar kolom

class Measurement(models.Model):
    value = models.FloatField(db_comment="Sensor reading in Celsius")
    recorded_at = models.DateTimeField(db_comment="UTC timestamp from the data logger")

Komentar terlihat di SQL Server Management Studio di bawah properti kolom/tabel dan melalui sys.extended_properties.

Kunci primer komposit

Django 5.2 memperkenalkan CompositePrimaryKey. Backend mssql-django memiliki dukungan parsial untuk kunci primer komposit, tetapi beberapa kasus pengujian Django masih dikecualikan. Validasikan migrasi dan kueri yang menggunakan kunci komposit pada aplikasi Anda sebelum menerapkannya di lingkungan produksi.

  • inspectdb tidak menghasilkan kunci primer komposit dengan benar. Tentukan secara manual setelah inspeksi.
  • Pencarian tuple tidak didukung. Backend memecah perbandingan kunci komposit menjadi kondisi pada setiap kolom.
  • Perbandingan tuple dengan subkueri memerlukan Django 5.2.4 dan versi lebih baru.
  • Beberapa operasi migrasi masih memiliki pengecualian yang diketahui. Lihat Batasan dan fitur yang tidak didukung di mssql-django untuk status saat ini.
from django.db import models
from django.db.models import CompositePrimaryKey

class OrderItem(models.Model):
    pk = CompositePrimaryKey("order_id", "product_id")
    order = models.ForeignKey("Order", on_delete=models.CASCADE)
    product = models.ForeignKey("Product", on_delete=models.CASCADE)
    quantity = models.IntegerField()

IDENTITY_INSERT penanganan

Saat Anda menyisipkan nilai eksplisit ke AutoField (misalnya, memulihkan data dari cadangan dengan ID tertentu), backend secara otomatis membungkus pernyataan insert dalam SET IDENTITY_INSERT ON / SET IDENTITY_INSERT OFF. Tidak diperlukan SQL manual.

# The backend handles IDENTITY_INSERT automatically
Product.objects.create(id=42, name="Restored Widget", price=9.99)

Note

SQL Server hanya mengizinkan satu tabel per sesi untuk memiliki IDENTITY_INSERT ON pada satu waktu. Jika Anda menyisipkan ID eksplisit ke dalam beberapa tabel dalam satu blok atomic(), sistem back-end menangani pengalihan tersebut untuk setiap pernyataan. Namun, sesi simultan yang juga menggunakan IDENTITY_INSERT pada tabel yang sama dapat menimbulkan konflik.