SQLite3で型に厳格なSTRICTテーブルを作る

STRICTテーブルは3.37(2021-11-27リリース)から利用可能
2023.03.03

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

SQLiteの際立った特徴の一つは動的な型システムを採用していることです。

一般的なリレーショナルデータベースであれば、カラムの型がレコードによって異なるなんてことはありませんが、SQLiteはその限りではありません。

整数型と宣言したカラムにテキスト型のレコードを格納できます。

これはバグではなくて仕様のため、SQLiteのデータ型のドキュメント冒頭で丁寧に解説されていますし("Flexible typing is a feature of SQLite, not a bug.")、"The Advantages Of Flexible Typing"というそのものズバリのドキュメントも存在します。

とはいえ、他のRDBMSに慣れ親しんだ人にも驚きが少ない仕様を提供するためか、型に厳格な STRICT テーブルがバージョン3.37(2021-11-27リリース)から提供されました。

STRICT tables provide a prescriptive style of data type management, for developers who prefer that kind of thing.

https://www.sqlite.org/releaselog/3_37_0.html

本ブログでは、同機能について簡単に紹介します。

SQLiteの動的な型

SQLiteのスキーマは参考情報でしかなく、同じカラムであってもレコードごとに異なる型を格納可能です。

試しに、整数型で宣言したカラムにテキスト型の値を格納してみましょう。

sqlite> CREATE TABLE t1(a INT);
sqlite> INSERT INTO t1 VALUES(1), ('a');
sqlite> SELECT typeof(a), a FROM t1;
integer|1
text|a

このユルイ型システムをよしとせず、CHECK 制約で型を縛っていた人も多いのではないかと思います。

sqlite> CREATE TABLE t2(a INT CHECK (typeof(a) == 'integer'));
sqlite> INSERT INTO t2 VALUES('a');
Error: CHECK constraint failed: typeof(a) == 'integer'

STRICTテーブルで静的型付け

厳格な型が欲しいユーザーのために、SQLite 3.37(2021-11-27リリース)からは、テーブル定義の最後に STRICT オプションを追加すると、型に厳格なテーブルを作成できるようになりました。

sqlite> CREATE TABLE t3(a INT) STRICT;
sqlite> INSERT INTO t3 VALUES('a');
Runtime error: cannot store TEXT value in INT column t3.a (19)

ユルイ型を使い続ける

STRICT テーブルに対応した SQLite 3.37 以降も動的な型システムを使い続ける方法は2通りあります。

1つ目は、 STRICT 宣言せずにテーブルを作成することです。 デフォルトの挙動に変更はありません。

2つ目は、STRICT テーブルに対して任意の型を受け取る ANY 型でカラム定義することです。

sqlite> CREATE TABLE t1(a ANY) STRICT;
sqlite> INSERT INTO t1 VALUES(1), ('a');
sqlite> SELECT typeof(a), a FROM t1;
integer|1
text|a

ただしANY 型は非STRICT テーブルと互換なわけではありません。

例えば、このANY 型は型強制が行われません。 一方で、非 STRICT テーブルは、型強制されます。

sqlite> CREATE TABLE t2(a INT);
sqlite> INSERT INTO t2 VALUES('01');
sqlite> SELECT typeof(a), a FROM t3;
integer|1

ANY 型の詳細は次のドキュメントをご確認ください。

https://www.sqlite.org/stricttables.html

古いバージョンでSTRICTテーブルを扱う

STRICT テーブルを含むデータベースを STRICT 未対応の古いバージョンで動かすことは考えないほうがいいでしょう。

テーブル定義の末尾のキーワードに対応していないため、諸々の操作が失敗します。

sqlite> .version
SQLite 3.36.0 2021-06-18 18:58:49 d24547a13b6b119c43ca2ede05fecaa707068f18c7430d47fc95fb5a2232aapl
clang-13.0.0

sqlite> .schema
Error: malformed database schema (t1) - near "STRICT": syntax error

sqlite> INSERT INTO t1 VALUES(1);
Error: malformed database schema (t1) - near "STRICT": syntax error

ただし、データベースファイルをオープンしたあと PRAGMA writable_schema=ON; と宣言すると、動作はします。

このプラグマは、壊れたデータファイルのリカバリなどに使われるものであり、エラーが起きるまで(今回だとSTRICT キーワード直前まで)を正として処理します。

sqlite> .version
SQLite 3.36.0 2021-06-18 18:58:49 d24547a13b6b119c43ca2ede05fecaa707068f18c7430d47fc95fb5a2232aapl
clang-13.0.0

sqlite> PRAGMA writable_schema=ON;

sqlite> .schema
CREATE TABLE t1(a INT) STRICT;

sqlite> INSERT INTO t1(a) VALUES(1), ('a');
sqlite> SELECT typeof(a), a FROM t1;
integer|1
text|a

STRICT で定義したはずのテーブルが STRICT に動作していません。

特殊な事情がない限り、このような使い方はやめたほうがいいでしょう。

SQLite4ではどうなる?

SQLite3 の次世代バージョンであるSQLite4の設計方針は公開されています。

SQLite4: The Design Of SQLite4

SQLite4でも互換なSQLite3の主要機能の3つ目として、動的な型が挙げられています。

Among the features that will remain in common between SQLite3 that SQLite4 are:

...

3 . Dynamic typing is used, rather than the rigid static typing of most other SQL database engines.

STRICT モードはあくまでも要望が多いからオプションとして提供しているものであり、SQLiteの目指す世界観とはずれるようです。

ALTER TABLE ドキュメントの"8. Why ALTER TABLE is such a problem for SQLite"はそのヒントの一つかもしれません。

知らんけど。

最後に

SQLite 3.37(2021-11-27)以降では、テーブル定義を STRICT 宣言するだけで、型に厳格なデータ管理をできます。

一般的なRDMBSのように SQLiteでも型チェックするテーブルを作成したい場合は、STRICT テーブルも検討しましょう。

それでは。

参考