Django のアップデート後、 oauth2_provider の マイグレーションに失敗する場合

投稿者: ytyng 1年, 7ヶ月 前

経緯

Django の django-oauth-toolkit に含まれるアプリ oauth2_provider のマイグレーションが 0004 で失敗した。

./manage.py migrate oauth2_provider すると、 Foreign Key の作成ができないとかで例外が出る。

DBのマイグレーションが途中で止まると、DDL が半端な状態になってしまうので、復旧がけっこうめんどい。

SHOW CREATE TABLE oauth2_provider_application;

を見て、

`id` bigint(20) NOT NULL AUTO_INCREMENT,

となっていれば OKで、

`id` int(11) NOT NULL AUTO_INCREMENT,

となっていると、oauth2_provider_idtoken.application_idoauth2_provider_application.id のFK制約を作る所で失敗する。型が違うため。

原因はおそらく、昔 Djangoのバージョンが古い時、 oauth2_provider でマイグレーションを0003 まで進めている場合で、
oauth2_provider_application.id が int(11) で作られているため。

最近の oauth2_provider では、id フィールドは bigint(20) で作るようになっているため、従来の int(11)とは FK が作れなくなっている。

FKを作れるようにするには、 int(11) → bigint(20) に ALTER TABLE する必要がある。
だが、既にある id 同士で 既に FK が作られているため、int → bitint の直しは非常に面倒な作業となる。

作業内容

1. マイグレーション 0004 が途中で例外で止まっていても、気にしない。0003 に戻さない。

戻そうとしても面倒になるだけなので、一旦マイグレーションのことは忘れる。

2. oauth2_provider の全テーブルの SHOW CREATE TABLE をテキストで記録しておく。

だいたいこんな感じになると思う。

show create table oauth2_provider_accesstoken;
CREATE TABLE `oauth2_provider_accesstoken` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`token` varchar(255) NOT NULL,
`expires` datetime(6) NOT NULL,
`scope` longtext NOT NULL,
`application_id` bigint(20) DEFAULT NULL,
`user_id` int(11) DEFAULT NULL,
`created` datetime(6) NOT NULL,
`updated` datetime(6) NOT NULL,
`source_refresh_token_id` bigint(20) DEFAULT NULL,
`id_token_id` bigint(20) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `token` (`token`),
UNIQUE KEY `source_refresh_token_id` (`source_refresh_token_id`),
UNIQUE KEY `id_token_id` (`id_token_id`),
KEY `oauth2_provider_accesstoken_user_id_6e4c9a65_fk_user_user_id` (`user_id`),
CONSTRAINT `oauth2_provider_acce_id_token_id_85db651b_fk_oauth2_pr` FOREIGN KEY (`id_token_id`) REFERENCES `oauth2_provider_idtoken` (`id`),
CONSTRAINT `oauth2_provider_acce_source_refresh_token_e66fbc72_fk_oauth2_pr` FOREIGN KEY (`source_refresh_token_id`) REFERENCES `oauth2_provider_refreshtoken` (`id`),
CONSTRAINT `oauth2_provider_accesstoken_user_id_6e4c9a65_fk_user_user_id` FOREIGN KEY (`user_id`) REFERENCES `user_user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
show create table oauth2_provider_application;
CREATE TABLE `oauth2_provider_application` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`client_id` varchar(100) NOT NULL,
`redirect_uris` longtext NOT NULL,
`client_type` varchar(32) NOT NULL,
`authorization_grant_type` varchar(32) NOT NULL,
`client_secret` varchar(255) NOT NULL,
`name` varchar(255) NOT NULL,
`user_id` int(11) NOT NULL,
`skip_authorization` tinyint(1) NOT NULL,
`algorithm` varchar(5) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `client_id` (`client_id`),
KEY `oauth2_provider_application_9d667c2b` (`client_secret`),
KEY `oauth2_provider_application_user_id_79829054_fk_user_user_id` (`user_id`),
CONSTRAINT `oauth2_provider_application_user_id_79829054_fk_user_user_id` FOREIGN KEY (`user_id`) REFERENCES `user_user` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8
show create table oauth2_provider_grant;
CREATE TABLE `oauth2_provider_grant` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`code` varchar(255) NOT NULL,
`expires` datetime(6) NOT NULL,
`redirect_uri` longtext NOT NULL,
`scope` longtext NOT NULL,
`application_id` int(11) NOT NULL,
`user_id` int(11) NOT NULL,
`code_challenge` varchar(128) NOT NULL,
`code_challenge_method` varchar(10) NOT NULL,
`nonce` varchar(255) NOT NULL,
`claims` longtext NOT NULL,
PRIMARY KEY (`id`),
KEY `oauth2_application_id_81923564_fk_oauth2_provider_application_id` (`application_id`),
KEY `oauth2_provider_grant_user_id_e8f62af8_fk_user_user_id` (`user_id`),
KEY `oauth2_provider_grant_c1336794` (`code`),
CONSTRAINT `oauth2_application_id_81923564_fk_oauth2_provider_application_id` FOREIGN KEY (`application_id`) REFERENCES `oauth2_provider_application` (`id`),
CONSTRAINT `oauth2_provider_grant_user_id_e8f62af8_fk_user_user_id` FOREIGN KEY (`user_id`) REFERENCES `user_user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
show create table oauth2_provider_idtoken;
CREATE TABLE `oauth2_provider_idtoken` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`jti` char(32) NOT NULL,
`expires` datetime(6) NOT NULL,
`scope` longtext NOT NULL,
`created` datetime(6) NOT NULL,
`updated` datetime(6) NOT NULL,
`application_id` bigint(20) DEFAULT NULL,
`user_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `jti` (`jti`),
KEY `oauth2_provider_idtoken_user_id_dd512b59_fk_user_user_id` (`user_id`),
CONSTRAINT `oauth2_provider_idtoken_user_id_dd512b59_fk_user_user_id` FOREIGN KEY (`user_id`) REFERENCES `user_user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
show create table oauth2_provider_refreshtoken;
CREATE TABLE `oauth2_provider_refreshtoken` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`token` varchar(255) NOT NULL,
`access_token_id` bigint(20) DEFAULT NULL,
`application_id` bigint(20) NOT NULL,
`user_id` int(11) NOT NULL,
`created` datetime(6) NOT NULL,
`updated` datetime(6) NOT NULL,
`revoked` datetime(6) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `access_token_id` (`access_token_id`),
UNIQUE KEY `oauth2_provider_refreshtoken_token_revoked_af8a5134_uniq` (`token`,`revoked`),
KEY `oauth2_provider_refreshtoken_user_id_da837fce_fk_user_user_id` (`user_id`),
CONSTRAINT `oauth2_provider_refr_access_token_id_775e84e8_fk_oauth2_pr` FOREIGN KEY (`access_token_id`) REFERENCES `oauth2_provider_accesstoken` (`id`),
CONSTRAINT `oauth2_provider_refreshtoken_user_id_da837fce_fk_user_user_id` FOREIGN KEY (`user_id`) REFERENCES `user_user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

3. oauth2_provider のテーブル同士の Foreign Key Constraint をすべて消す

alter table oauth2_provider_accesstoken
drop foreign key oauth2_provider_acce_id_token_id_85db651b_fk_oauth2_pr,
drop foreign key oauth2_provider_acce_source_refresh_token_e66fbc72_fk_oauth2_pr;
alter table oauth2_provider_grant
drop foreign key oauth2_application_id_81923564_fk_oauth2_provider_application_id;
alter table oauth2_provider_refreshtoken
drop foreign key oauth2_provider_refr_access_token_id_775e84e8_fk_oauth2_pr,
drop foreign key oauth2_provider_refreshtoken_user_id_da837fce_fk_user_user_id;

こんな感じで消していく。
実際のテーブルの状態と見比べながらやってください。

4. int -> bigint へ変更する

alter table oauth2_provider_accesstoken
modify id bigint auto_increment;
alter table oauth2_provider_application
modify id bigint auto_increment;
alter table oauth2_provider_grant
modify id bigint auto_increment,
modify application_id bigint NOT NULL;

こんな感じで。これがすべてではないかもしれない。

5. FK を作り直す

2 で作ったテキストのコピーを使って alter table 文を組み立てる。

alter table oauth2_provider_accesstoken
add CONSTRAINT `oauth2_provider_acce_id_token_id_85db651b_fk_oauth2_pr` FOREIGN KEY (`id_token_id`) REFERENCES `oauth2_provider_idtoken` (`id`),
add CONSTRAINT `oauth2_provider_acce_source_refresh_token_e66fbc72_fk_oauth2_pr` FOREIGN KEY (`source_refresh_token_id`) REFERENCES `oauth2_provider_refreshtoken` (`id`);
alter table oauth2_provider_grant
add CONSTRAINT `oauth2_application_id_81923564_fk_oauth2_provider_application_id` FOREIGN KEY (`application_id`) REFERENCES `oauth2_provider_application` (`id`);
alter table oauth2_provider_refreshtoken
add CONSTRAINT `oauth2_provider_refr_access_token_id_775e84e8_fk_oauth2_pr` FOREIGN KEY (`access_token_id`) REFERENCES `oauth2_provider_accesstoken` (`id`),
add CONSTRAINT `oauth2_provider_refreshtoken_user_id_da837fce_fk_user_user_id` FOREIGN KEY (`user_id`) REFERENCES `user_user` (`id`);

6. 手動でマイグレーション 0004 を実行する

これが 0004 の内容。エラーが出て失敗してるなら途中までは成功している。
全部冪等なので、もう一度SQLを発行しても問題無い。

ALTER TABLE `oauth2_provider_application` ALTER COLUMN `algorithm` DROP DEFAULT;
CREATE TABLE `oauth2_provider_idtoken` (`id` bigint AUTO_INCREMENT NOT NULL PRIMARY KEY, `jti` char(32) NOT NULL UNIQUE, `expires` datetime(6) NOT NULL, `scope` longtext NOT NULL, `created` datetime(6) NOT NULL, `updated` datetime(6) NOT NULL, `application_id` bigint NULL, `user_id` integer NULL);
ALTER TABLE `oauth2_provider_accesstoken` ADD COLUMN `id_token_id` bigint NULL UNIQUE , ADD CONSTRAINT `oauth2_provider_acce_id_token_id_85db651b_fk_oauth2_pr` FOREIGN KEY (`id_token_id`) REFERENCES `oauth2_provider_idtoken`(`id`);
ALTER TABLE `oauth2_provider_grant` ADD COLUMN `nonce` varchar(255) DEFAULT '' NOT NULL;
ALTER TABLE `oauth2_provider_grant` ALTER COLUMN `nonce` DROP DEFAULT;
ALTER TABLE `oauth2_provider_grant` ADD COLUMN `claims` longtext NOT NULL;
ALTER TABLE `oauth2_provider_idtoken` ADD CONSTRAINT `oauth2_provider_idto_application_id_08c5ff4f_fk_oauth2_pr` FOREIGN KEY (`application_id`) REFERENCES `oauth2_provider_application` (`id`);
ALTER TABLE `oauth2_provider_idtoken` ADD CONSTRAINT `oauth2_provider_idtoken_user_id_dd512b59_fk_user_user_id` FOREIGN KEY (`user_id`) REFERENCES `user_user` (`id`);

7. フェイクマイグレーションで 0004 を終わったことにする

./manage.py showmigrations oauth2_provider
./manage.py migrate oauth2_provider 0004 --fake
./manage.py showmigrations oauth2_provider

8. 最後までマイグレーションする

./manage.py migrate oauth2_provider

私の場合、

django.db.utils.OperationalError: (1054, "Unknown column 'oauth2_provider_application.created' in 'field list'")

が出たので、

ALTER TABLE oauth2_provider_application
ADD COLUMN `created` datetime(6),
ADD COLUMN `updated` datetime(6);

これを実行した。created, updated は oauth2_provider の抽象クラスのフィールドなので、 oauth2_provider のすべてのテーブルに同様のカラムを追加する必要がある。

現在未評価

コメント

アーカイブ

2024
2023
2022
2021
2020
2019
2018
2017
2016
2015
2014
2013
2012
2011