QSqlFieldを渡してテーブル作成するメソッドなどは無い。
現状のテーブル作成
QtではSQL文を発行することでテーブルを作成している。というか、Qtではそこまでしかサポートされていない。
参考
QSqlDatabase _db = QSqlDatabase::addDatabase("QSQLITE", "Memo"); QString dbPath = QDir(QApplication::applicationDirPath()).filePath("Memo.sqlite3"); _db.setDatabaseName(dbPath); QSqlDatabase db = QSqlDatabase::database("Memo"); QSqlQuery query(db); query.exec(tr("create table Memo(id INTEGER PRIMARY KEY AUTOINCREMENT, Memo TEXT, Created TEXT)"));
あとはDBの定義を変更できるらしい。既存のテーブルにのみ使える。
m_db = QSqlDatabase::addDatabase( "QSQLITE" ); m_db.setDatabaseName("testdb.sqlite"); m_db.open(); if(m_db.isOpen()) { m_images = new QSqlRelationalTableModel(this, m_db); m_images->setTable("images"); m_images->setEditStrategy(QSqlTableModel::OnFieldChange); // here is where I would like to add columns like for example name(VARCHAR) and number(INT) // then I would like to "execute" this model to the database like "CREATE TABLE" }
だが、このDB変更は使うだろうか? DBの列を追加することが考えられるが、データはどうなるのだろうか。できることが少ない上に中途半端なので使い道がない気がする。
案
Qtライブラリを使って自作のcreate tableライブラリを作れないか考えてみる。
ベースのみSQL文で作成
ベースのみSQL文でcreate table
する。つまりid
列のみ作成する。他の列はQSqlTableModel
のメソッドを介して作成する。
QSqlDatabase _db = QSqlDatabase::addDatabase("QSQLITE", "SomeDb"); QString dbPath = QDir(QApplication::applicationDirPath()).filePath("SomeDb.sqlite3"); _db.setDatabaseName(dbPath); QSqlDatabase db = QSqlDatabase::database("SomeDb"); QSqlQuery query(db); query.exec(tr("create table SomeTable (id INTEGER PRIMARY KEY AUTOINCREMENT)")); QSqlTableModel model(nullptr, db); model.setTable("SomeDb"); model.setEditStrategy(QSqlTableModel::OnFieldChange); model.select(); // 列の追加 QSqlField fMemo("Memo"); fMemo.setType(QMetaType::QString); QSqlField fCreated("Created"); fMemo.setType(QMetaType::QDateTime); model.record().append(fMemo); model.record().append(fCreated); db.close();
この案の問題は、テーブル名を変更できないこと。これもSQL文の発行をする必要がある。
query.exec(tr("alter table SomeTable rename to NewTableName"));
これをみると、alter table
文でカラムを追加するときは以下の制限があるらしい。(フィールド=カラム=列)
- PRIMARY KEY や UNIQUE 制約は設定できない
- DEFAULT 制約を設定する時は、CURRENT_TIME/CURRENT_DATE/CURRENT_TIMESTAMPは指定できない
- NOT NULL 制約を設定する時は、NULL以外のデフォルト値の設定が必要
これは厳しい。とくにUNIQUE制約が設定できないのはひどい。この案はカラムの追加によるテーブル作成なので、UNIQUE制約が設定できないことになる。実用性皆無だが、一旦良しとして考えを進める。
インタフェースはたとえば次のようなものが考えられる。
TableCreator tc(db, "テーブル名"); // 一時テーブルの作成 // 列の追加 QSqlField fMemo("Memo"); fMemo.setType(QMetaType::QString); QSqlField fCreated("Created"); fMemo.setType(QMetaType::QDateTime); tc.record().append(fMemo); tc.record().append(fCreated); tc.setTableName("変更したテーブル名"); // alter table テーブル名 rename to 変更したテーブル名 tc.create(); // QSqlTableModel::record().append(QSqlField); おそらくQtライブラリ内部で次のSQL文を発行している `alter table 一時テーブル名 add column カラム名 型 制約;` tc.toString(); // create tableのSQL文
制約についてはQSqlFieldのメソッドで設定できると思う。要調査。
- http://doc.qt.io/qt-5/qsqldatabase.html
- http://doc.qt.io/qt-5/qsqlrecord.html
- http://doc.qt.io/qt-5/qsqlfield.html
- http://doc.qt.io/qt-5/qmetatype.html
create table文作成ラッパクラス自作
これは非常に面倒だと思われる。SQLマスターでもないかぎり実装すべき構文すらすべて把握するのは不可能。さらにRDBMSによっても構文が微妙に違ったりすると思う。それらの違いを調べるのだけでも面倒。SQLite3に限定しても面倒。
QSqlFieldからcreate tableの一部を作る
SQL文の基本は以下。
create table SomeTable (列定義, ... 列定義)
例外として外部キー制約の定義がある。他にもあるかも知れない。
create table SomeTable ( 列定義, ... 列定義, FOREIGN KEY(列名) REFERENCES 外部テーブル名(列名) )
なお、外部キー定義は列定義に記述する記法もある。
create table ChildTable (ParentId REFERENCES Parent(Id));
だが、QSqlFieldには外部キー設定を保持するようなメンバが存在しない。
なので、外部キー定義のSQL文に関してはQSqlFieldを頼りにすることはできない。自作する必要がある。
なお、外部キー制約を定義したあとのライブラリならあるようだ。結合テーブルを作成するQSqlRelationalTableModelクラスというのがある。子テーブルの値を参照できるらしい。もっとも、テーブル定義の段階では関係ないが。
列の定義は以下のような構文。
列名 型 制約
スペースで区切っている。だが、そう単純でもない。たとえばPRIMARY KEY
などはそれがひとつの要素なのにスペースが間に入っている。
id INTEGER PRIMARY KEY AUTOINCREMENT
この構文を読み解くコードを書くのは面倒そう。もっとも、そんなコードを書く必要はない。ここではSQL文を書き出しさえすればいい。
諦めてSQL文で書く
自作ライブラリなど無謀。以下のような問題が起こることはやる前にわかる。
せめてC++ソースコードから分離できないか
以下のようなディレクトリを用意し、自動で読み込んでcreate table文を発行する。
- Databasies/
- SomeDatabase/
- SomeTable.sql
- SomeDatabase/
SqlTableCreator creator; creator.Create();
addDatabase()
,removeDatabase()
,database()
するときの名前:SomeDatabase
- SQLite3ファイルパス:
SomeDatabase.sqlite3
- ディレクトリパス:
SqlTableCreator.SetDirPath(QString dirPath="./");
- ファイル名、拡張子:
SqlTableCreator.SetFileExtension(QString fileExtension="sqlite3");
- ディレクトリパス:
もうスクリプトで良くない?
sqlファイルに沿ってDB作成するだけならsqlite3コマンドだけでできるはず。だったらbashなどのスクリプトで十分ではないか。それをQtアプリで呼び出せばいい。
- Qtのsqlite3とシステムコマンドのsqlite3は同じ? バージョン差異ない?: たぶんQtはシステムのsqlite3コマンドを参照していると思われる
- OS環境による差異ない?: C++をビルドする時点でOS固有アプリになってしまうので心配するだけ無駄
- テーブル定義がC++コードから消えるので列名がわからなくなってしまう:
QSqlTableModel::record().field()
で参照できるはず
必要な情報があっちこっちに散らばってしまう。
まとめ
方法 | 問題 |
---|---|
コード内にDDLを含める | create tableなど生のSQLで書かねば実装できない。C++言語と混在してコードが保守しづらい |
QSqlTableModel::record().append(QSqlField)でテーブル定義を変更する | alter table 文はUNIQUE制約が設定できない等の制限がある。QSqlFieldは外部キー制約の設定ができない |
create table文作成ラッパクラス自作 | 実装や保守が大変すぎる(RDBMSごとにおけるSQL構文の調査、RDBMSの更新反映、不正確なコードを書いてバグを作り込みそう) |
sqlファイル自動実行ライブラリ自作 | 実装は現実的な範囲と思われるが、C++で実装するメリットが微妙 |
sqlファイル自動実行bashスクリプト自作 | C++に加え、shellスクリプト言語の技能が必要 |
まとまってないな。
所感
C#のEntityFramework(コードファースト)が使えたら良かったのに。
現実的なのは、外部SQLファイルを読み込んでデータベースファイルを作成することか。これはSQLite3限定になりそう。Qtライブラリにするとさらに限定的になってしまうので、bashスクリプトがいいか?