メインコンテンツにスキップ

MySQLにおけるスキーマの非互換性

概要

このページの各セクションでは、Prisma 1からPrisma ORM 2.x以降へのアップグレード時に発生する可能性のある問題について説明し、利用可能な回避策を解説します。

デフォルト値がデータベースに反映されない

問題

Prisma 1のデータモデルに@defaultディレクティブを追加すると、このフィールドのデフォルト値はPrisma 1サーバーによって実行時に生成されます。データベースのカラムにはDEFAULT制約が追加されません。この制約がデータベース自体に反映されていないため、Prisma ORM 2.x以降のイントロスペクションでは認識できません。

Prisma 1 データモデル

type Post {
id: ID! @id
published: Boolean @default(value: false)
}

Prisma 1が生成したSQLマイグレーション

CREATE TABLE "Post" (
id VARCHAR(25) PRIMARY KEY NOT NULL,
published BOOLEAN NOT NULL
);

Prisma ORM 2.x以降のバージョンでのイントロスペクションの結果

schema.prisma
model Post {
id String @id
published Boolean
}

prisma deployでPrisma 1データモデルをデータベースにマッピングした際にDEFAULT制約がデータベースに追加されていないため、Prisma ORM v2(およびそれ以降のバージョン)ではイントロスペクション中に認識されません。

回避策

データベースカラムにDEFAULT制約を手動で追加する

カラムを変更して、次のようにDEFAULT制約を追加できます

ALTER TABLE `Post`
ALTER COLUMN published SET DEFAULT false;

この調整後、データベースを再イントロスペクトすると、@default属性がpublishedフィールドに追加されます。

schema.prisma
model Post {
id String @id
published Boolean @default(false)
}

Prismaモデルに@default属性を手動で追加する

Prismaモデルに@default属性を追加できます

schema.prisma
model Post {
id String
published Boolean @default(false)
}

Prismaスキーマで@default属性が設定され、prisma generateを実行すると、生成されるPrisma Clientコードは、実行時に指定されたデフォルト値を生成します(Prisma 1サーバーがPrisma 1で行っていたことと同様)。

生成されたCUIDがID値としてデータベースに反映されない

問題

Prisma 1では、@idディレクティブでアノテーションされたIDフィールドに対して、ID値としてCUIDが自動生成されます。これらのCUIDはPrisma 1サーバーによって実行時に生成されます。この挙動がデータベース自体に反映されていないため、Prisma ORM 2.x以降のイントロスペクションでは認識できません。

Prisma 1 データモデル

type Post {
id: ID! @id
}

Prisma 1が生成したSQLマイグレーション

CREATE TABLE "Post" (
id VARCHAR(25) PRIMARY KEY NOT NULL
);

Prisma ORM 2.x以降のバージョンでのイントロスペクションの結果

schema.prisma
model Post {
id String @id
}

データベースにCUIDの挙動を示すものが何もないため、Prisma ORMのイントロスペクションではそれを認識しません。

回避策

回避策として、Prismaモデルに@default(cuid())属性を手動で追加できます

schema.prisma
model Post {
id String @id @default(cuid())
}

Prismaスキーマで@default属性が設定され、prisma generateを実行すると、生成されるPrisma Clientコードは、実行時に指定されたデフォルト値を生成します(Prisma 1サーバーがPrisma 1で行っていたことと同様)。

イントロスペクションは属性を削除するため(Prismaスキーマの以前のバージョンが上書きされるため)、各イントロスペクション後に属性を再追加する必要があることに注意してください!

@createdAtがデータベースに反映されない

問題

Prisma 1では、@createdAtディレクティブでアノテーションされたDateTimeフィールドに対して値が自動生成されます。これらの値はPrisma 1サーバーによって実行時に生成されます。この挙動がデータベース自体に反映されていないため、Prisma ORM 2.x以降のイントロスペクションでは認識できません。

Prisma 1 データモデル

type Post {
id: ID! @id
createdAt: DateTime! @createdAt
}

Prisma 1が生成したSQLマイグレーション

CREATE TABLE "Post" (
id VARCHAR(25) PRIMARY KEY NOT NULL,
"createdAt" TIMESTAMP NOT NULL
);

Prisma ORM 2.x以降のバージョンでのイントロスペクションの結果

schema.prisma
model Post {
id String @id
createdAt DateTime
}

回避策

データベースカラムにDEFAULT CURRENT_TIMESTAMPを手動で追加する

カラムを変更して、次のようにDEFAULT制約を追加できます

ALTER TABLE "Post"
ALTER COLUMN "createdAt" SET DEFAULT CURRENT_TIMESTAMP;

この調整後、データベースを再イントロスペクトすると、@default属性がcreatedAtフィールドに追加されます。

schema.prisma
model Post {
id String
createdAt DateTime @default(now())
}

Prismaモデルに@default(now())属性を手動で追加する

回避策として、Prismaモデルに@default(now())属性を手動で追加できます

schema.prisma
model Post {
id String @id
createdAt DateTime @default(now())
}

Prismaスキーマで@default属性が設定され、prisma generateを実行すると、生成されるPrisma Clientコードは、実行時に指定されたデフォルト値を生成します(Prisma 1サーバーがPrisma 1で行っていたことと同様)。

イントロスペクションは属性を削除するため(Prismaスキーマの以前のバージョンが上書きされるため)、各イントロスペクション後に属性を再追加する必要があることに注意してください!

@updatedAtがデータベースに反映されない

問題

Prisma 1では、@updatedAtディレクティブでアノテーションされたDateTimeフィールドに対して値が自動生成されます。これらの値はPrisma 1サーバーによって実行時に生成されます。この挙動がデータベース自体に反映されていないため、Prisma ORM 2.x以降のイントロスペクションでは認識できません。

Prisma 1 データモデル

type Post {
id: ID! @id
updatedAt: DateTime! @updatedAt
}

Prisma 1が生成したSQLマイグレーション

CREATE TABLE "Post" (
id VARCHAR(25) PRIMARY KEY NOT NULL,
updatedAt TIMESTAMP
);

Prisma ORM 2.x以降のバージョンでのイントロスペクションの結果

schema.prisma
model Post {
id String @id
updatedAt DateTime
}

回避策

Prismaモデルに@updatedAt属性を手動で追加する

回避策として、Prismaモデルに@updatedAt属性を手動で追加できます

schema.prisma
model Post {
id String @id
updatedAt DateTime @updatedAt
}

Prismaスキーマで@updatedAt属性が設定され、prisma generateを実行すると、生成されるPrisma Clientコードは、既存のレコードが更新された際にこのカラムの値を自動的に生成します(Prisma 1サーバーがPrisma 1で行っていたことと同様)。

イントロスペクションは属性を削除するため(Prismaスキーマの以前のバージョンが上書きされるため)、各イントロスペクション後に属性を再追加する必要があることに注意してください!

インライン1対1リレーションが1対多として認識される(UNIQUE制約の欠落)

問題

Prisma ORM v1.31で導入されたデータモデル v1.1では、1対1リレーションをインラインとして宣言できます。その場合、リレーションはリレーションテーブルではなく、関係する2つのテーブルのうちの1つにある単一の外部キーを介して維持されます。

このアプローチが使用されると、Prisma ORMは外部キーカラムにUNIQUE制約を追加しません。これは、Prisma ORMバージョン2.x以降でのイントロスペクション後、この以前の1対1リレーションがPrismaスキーマに1対多リレーションとして追加されることを意味します。

Prisma ORM データモデル v1.1(Prisma ORM v1.31から利用可能)

type User {
id: ID! @id
profile: Profile @relation(link: INLINE)
}

type Profile {
id: ID! @id
user: User
}

この場合、@relationディレクティブを省略しても同じ挙動になります。なぜなら、link: INLINEが1対1リレーションのデフォルトだからです。

Prisma 1が生成したSQLマイグレーション

CREATE TABLE "User" (
id VARCHAR(25) PRIMARY KEY NOT NULL
);

CREATE TABLE "Profile" (
id VARCHAR(25) PRIMARY KEY NOT NULL,
"user" VARCHAR(25),
FOREIGN KEY ("user") REFERENCES "User"(id)
);

Prisma ORM 2.x以降のバージョンでのイントロスペクションの結果

schema.prisma
model User {
id String @id
Profile Profile[]
}

model Profile {
id String @id
user String?
User User? @relation(fields: [user], references: [id])
}

userカラム(このリレーションの外部キーを表す)にUNIQUE制約が定義されていないため、Prisma ORMのイントロスペクションはリレーションを1対多として認識します。

回避策

外部キーカラムにUNIQUE制約を手動で追加する

外部キーカラムを変更して、次のようにUNIQUE制約を追加できます

ALTER TABLE `Profile`
ADD CONSTRAINT userId_unique UNIQUE (`user`);

この調整後、データベースを再イントロスペクトすると、1対1リレーションが適切に認識されるようになります。

schema.prisma
model User {
id String @id
Profile Profile?
}

model Profile {
id String @id
user String? @unique
User User? @relation(fields: [user], references: [id])
}

すべての非インラインリレーションが多対多として認識される

問題

Prisma 1はほとんどの場合、リレーションをリレーションテーブルとして表現します。

  • Prisma 1のデータモデル v1.0におけるすべてのリレーションは、リレーションテーブルとして表現されます。
  • データモデル v1.1では、すべての多対多リレーションと、link: TABLEとして宣言された1対1および1対多リレーションは、リレーションテーブルとして表現されます。

この表現のため、Prisma ORMバージョン2.x以降でのイントロスペクションは、これらのリレーションがPrisma 1で1対1または1対多として宣言されていたとしても、すべて多対多リレーションとして認識します。

Prisma 1 データモデル

type User {
id: ID! @id
posts: [Post!]!
}

type Post {
id: ID! @id
author: User! @relation(link: TABLE)
}

Prisma 1が生成したSQLマイグレーション

CREATE TABLE "User" (
id VARCHAR(25) PRIMARY KEY NOT NULL
);

CREATE TABLE "Post" (
id VARCHAR(25) PRIMARY KEY NOT NULL
);

CREATE TABLE "_PostToUser" (
"A" VARCHAR(25) NOT NULL REFERENCES "Post"(id) ON DELETE CASCADE,
"B" VARCHAR(25) NOT NULL REFERENCES "User"(id) ON DELETE CASCADE
);
CREATE UNIQUE INDEX "_PostToUser_AB_unique" ON "_PostToUser"("A" text_ops,"B" text_ops);
CREATE INDEX "_PostToUser_B" ON "_PostToUser"("B" text_ops);

Prisma ORM 2.x以降のバージョンでのイントロスペクションの結果

schema.prisma
model User {
id String @id
Post Post[] @relation(references: [id])
}

model Post {
id String @id
User User[] @relation(references: [id])
}

Prisma 1によって作成されたリレーションテーブルがPrisma ORMバージョン2.x以降と同じリレーションテーブルの規約を使用しているため、そのリレーションは多対多リレーションとして認識されるようになりました。

回避策

回避策として、データをPrisma ORMの1対多リレーションと互換性のある構造に移行できます

  1. Postテーブルに新しいカラムauthorIdを作成します。このカラムは、Userテーブルのidフィールドを参照する外部キーである必要があります。
    ALTER TABLE `Post` ADD COLUMN `authorId` VARCHAR(25);
    ALTER TABLE `Post` ADD FOREIGN KEY (`authorId`) REFERENCES `User` (`id`);
  2. _PostToUserリレーションテーブルからすべての行を読み取り、各行に対して以下の処理を行うSQLクエリを記述します。
    1. カラムAの値を探して、対応するPostレコードを見つける
    2. カラムBの値をauthorIdの値としてそのPostレコードに挿入する
    UPDATE Post, _PostToUser
    SET Post.authorId = _PostToUser.B
    WHERE Post.id = _PostToUser.A
  3. _PostToUserリレーションテーブルを削除する
    DROP TABLE `_PostToUser`;

その後、データベースをイントロスペクトすると、リレーションは1対多として認識されるようになります。

schema.prisma
model User {
id String @id
Post Post[]
}

model Post {
id String @id
User User @relation(fields: [authorId], references: [id])
authorId String
}

Json型がデータベースでTEXTとして表現される

問題

Prisma 1はデータモデルでJsonデータ型をサポートしています。しかし、基になるデータベースでは、Json型のフィールドは実際には基になるデータベースのTEXTデータ型を使用してプレーンな文字列として保存されます。保存されたJSONデータのパースと検証は、Prisma 1サーバーによって実行時に行われます。

Prisma 1 データモデル

type User {
id: ID! @id
jsonData: Json
}

Prisma 1が生成したSQLマイグレーション

CREATE TABLE "User" (
id VARCHAR(25) PRIMARY KEY NOT NULL,
jsonData TEXT
);

Prisma ORM 2.x以降のバージョンでのイントロスペクションの結果

schema.prisma
model User {
id String @id
jsonData String?
}

回避策

カラムの型をJSONに手動で変更できます

ALTER TABLE User MODIFY COLUMN jsonData JSON;

この調整後、データベースを再イントロスペクトすると、フィールドはJsonとして認識されるようになります。

schema.prisma
model User {
id String @id
jsonData Json?
}

EnumがデータベースでTEXTとして表現される

問題

Prisma 1はデータモデルでenumデータ型をサポートしています。しかし、基になるデータベースでは、enumとして宣言された型は実際には基になるデータベースのTEXTデータ型を使用してプレーンな文字列として保存されます。保存されたenumデータの検証は、Prisma 1サーバーによって実行時に行われます。

Prisma 1 データモデル

type User {
id: ID! @id
role: Role
}

enum Role {
ADMIN
CUSTOMER
}

Prisma 1が生成したSQLマイグレーション

CREATE TABLE "User" (
id VARCHAR(25) PRIMARY KEY NOT NULL,
role TEXT
);

Prisma ORM 2.x以降のバージョンでのイントロスペクションの結果

schema.prisma
model User {
id String @id
role String?
}

回避策

roleカラムを希望する値を持つenumに手動で変更できます

  1. Prisma 1データモデルで定義したenumを反映するenumをデータベースに作成します
    CREATE TYPE "Role" AS ENUM ('CUSTOMER', 'ADMIN');
  2. 型をTEXTから新しいenumに変更します
    ALTER TABLE "User" ALTER COLUMN "role" TYPE "Role"
    USING "role"::text::"Role";

イントロスペクション後、型はenumとして適切に認識されるようになります。

schema.prisma
model User {
id String @id
role Role?
}

enum Role {
ADMIN
CUSTOMER
}

CUIDの長さの不一致

問題

Prisma 1は、すべてのデータベースレコードのID値としてCUIDを使用します。基になるデータベースでは、これらのIDは最大25文字(VARCHAR(25)として)の文字列として表現されます。しかし、Prisma ORM 2.x(またはそれ以降のバージョン)のスキーマで@default(cuid())を使用してデフォルトのCUIDを設定すると、生成されるID値が25文字の制限を超える可能性があります(最大長は30文字になる可能性があります)。そのため、Prisma ORM 2.x(またはそれ以降のバージョン)でIDを確実に機能させるには、カラムの型をVARCHAR(30)に調整する必要があります。

Prisma 1 データモデル

type User {
id: ID! @id
}

Prisma 1が生成したSQLマイグレーション

CREATE TABLE "User" (
id VARCHAR(25) PRIMARY KEY NOT NULL
);

Prisma ORM 2.x以降のバージョンでのイントロスペクションの結果

schema.prisma
model User {
id String @id
}

回避策

VARCHAR(25)カラムをVARCHAR(30)に手動で変更できます

SET FOREIGN_KEY_CHECKS=0;
ALTER TABLE `User` CHANGE `id` `id` char(30) CHARACTER SET utf8 NOT NULL;
SET FOREIGN_KEY_CHECKS=1;

: Upgrade CLIでこの問題を修正する場合、基になるデータベースでカラムの型を変更した後でも、生成されたSQLステートメントがUpgrade CLIに表示され続けます。これは現在のところUpgrade CLIの制限です。

スカラーリスト(配列)が追加テーブルで維持される

問題

Prisma 1では、モデルにスカラー型のリストを定義できます。内部的には、これはリストの値を追跡する追加のテーブルで実装されています。

隠れたパフォーマンスコストを伴う追加テーブルのアプローチを排除するため、Prisma ORM 2.x以降のバージョンでは、使用するデータベースでスカラーリストがネイティブにサポートされている場合にのみ、スカラーリストをサポートします。現在、PostgreSQLのみがスカラーリスト(配列)をネイティブにサポートしています

したがって、PostgreSQLを使用している場合、Prisma ORM 2.x以降のバージョンでもスカラーリストを使用し続けることができますが、Prisma 1の追加テーブルから実際のPostgreSQL配列にデータを転送するためのデータ移行を実行する必要があります。

Prisma 1 データモデル

type User {
id: ID! @id
coinflips: [Boolean!]! @scalarList(strategy: RELATION)
}

Prisma 1が生成したSQLマイグレーション

CREATE TABLE "User" (
id VARCHAR(25) PRIMARY KEY NOT NULL
);

CREATE TABLE "User_coinflips" (
"nodeId" VARCHAR(25) REFERENCES "User"(id),
position INTEGER,
value BOOLEAN NOT NULL,
CONSTRAINT "User_coinflips_pkey" PRIMARY KEY ("nodeId", position)
);
CREATE UNIQUE INDEX "User_coinflips_pkey" ON "User_coinflips"("nodeId" text_ops,position int4_ops);

Prisma ORM 2のイントロスペクション結果

schema.prisma
model User {
id String @id
User_coinflips User_coinflips[]
}

model User_coinflips {
nodeId String
position Int
value Boolean
User User @relation(fields: [nodeId], references: [id])

@@id([nodeId, position])
}

これでPrisma Clientを生成でき、追加テーブルを介してスカラーリストからデータにアクセスできるようになることに注意してください。PostgreSQLユーザーは、代わりにデータをネイティブのPostgreSQL配列に移行し、スカラーリスト用のより洗練されたPrisma Client APIから引き続き恩恵を受けることができます(詳細については以下のセクションを参照してください)。

サンプルのPrisma Client API呼び出しを展開

coinflipsデータにアクセスするには、常にクエリにincludeする必要があります

const user = await prisma.user.findUnique({
where: { id: 1 },
include: {
coinflips: {
orderBy: { position: 'asc' },
},
},
})

: リストの順序を維持するためにorderByが重要です。

これがクエリの結果です

{
id: 1,
name: 'Alice',
coinflips: [
{ id: 1, position: 1000, value: false },
{ id: 2, position: 2000, value: true },
{ id: 3, position: 3000, value: false },
{ id: 4, position: 4000, value: true },
{ id: 5, position: 5000, value: true },
{ id: 6, position: 6000, value: false }
]
}

リストからブール値のみにアクセスするには、user上のcoinflipsを次のようにmapできます

const currentCoinflips = user!.coinflips.map((cf) => cf.value)

: 上記の感嘆符は、user値を強制アンラップしていることを意味します。これは、前のクエリから返されるusernullである可能性があるため必要です。

map呼び出し後のcurrentCoinflipsの値です

[false, true, false, true, true, false]

回避策

以下の回避策はPostgreSQLユーザーのみが利用できます!

スカラーリスト(つまり、配列)がネイティブのPostgreSQL機能として利用可能であるため、Prismaスキーマでcoinflips: Boolean[]と同じ表記を使い続けることができます。

ただし、そのためには、基になるデータをUser_coinflipsテーブルからPostgreSQL配列に手動で移行する必要があります。その方法は次のとおりです。

  1. 新しいcoinflipsカラムをUserテーブルに追加します
    ALTER TABLE "User" ADD COLUMN coinflips BOOLEAN[];
  2. データを"User_coinflips".valueから"User.coinflips"に移行します
    UPDATE "User"
    SET coinflips = t.flips
    FROM (
    SELECT "nodeId", array_agg(VALUE ORDER BY position) AS flips
    FROM "User_coinflips"
    GROUP BY "nodeId"
    ) t
    where t."nodeId" = "User"."id";
  3. クリーンアップのため、User_coinflipsテーブルを削除できます
    DROP TABLE "User_coinflips";

これでデータベースをイントロスペクトでき、coinflipsフィールドは新しいPrismaスキーマで配列として表現されます。

schema.prisma
model User {
id String @id
coinflips Boolean[]
}

これまでどおりPrisma Clientを使い続けることができます

const user = await prisma.user.findUnique({
where: { id: 1 },
})

これがAPI呼び出しの結果です

{
id: 1,
name: 'Alice',
coinflips: [ false, true, false, true, true, false ]
}
© 2025 prisma.dokyumento.jp. All rights reserved.