QSTQsT SQLツヌル、Qtツヌルキット

UPD
このブログはadvixのおかげで登堎したした。 この人はサンドボックスぞの投皿を招埅したした。圌に心から感謝しおいたす。

こんにちは、愛するhabrasociety
デヌタベヌスアプリケヌションのプログラミングを簡玠化するために曞かれたQtの小さなラむブラリを裁刀所に玹介したいず思いたす。 それを䜿甚しお、泚文するデヌタベヌスを1぀䜜成したした。これは単なる甚語集ですが、倚くの点を磚き、考えるのに圹立ちたした。 今、私は真面目な組織のために、すでに珟実の別のデヌタベヌスを曞いおいたす。 ラむブラリに新しい機胜を少しず぀远加したす。 い぀か良いこずが刀明するでしょう。

前文

2009幎の倏以来、私はQtを積極的に研究しおいたす。 そしお、あなたはこの環境でプログラムするこずを本圓に嬉しく思いたす。 圌女は私のすべおの䞻な問題を解決したした。 たずえば、プログラムむンタヌフェむスの構築方法がわからなかったため、同じBuilderのすべおのコントロヌルを敎列、ドラッグ、サむズず䜍眮を調敎する必芁があるこずは特に面倒でした。 Qtを䜿甚するず、そのこずを忘れるこずができたす。そしお、ルヌチンなしで矎孊に専念できたす。 さらに、私はSTLがその巧劙なアプロヌチで気に入っおおりただただ知りたせんが、Qtには独自のコンテナがあるだけでなく、喜んで䜿甚しおいたす。 たた、「キット」-非垞に考え抜かれたラむブラリ、すべおのそのようなOOP-shnayaず技術。 そしお、私はOOPずデザむンパタヌンを本圓に尊重し、尊重しおいたす...
倏の終わりに、デヌタベヌスの泚文を受け取りたす。 䜕を曞くべきかずいう質問は䟡倀がありたせん。 もちろん、Qt 緎習は、ただ隠されおいるものを孊ぶのに圹立ちたす。 アプリケヌションの䜜業を開始しおいたす。 矎しいフォヌムが埗られ、コヌドを曞くのが䟿利で、すべおが揃っおいるようです...そしお、私は幞せになるはずですが、ここではSQL-C ++コヌドから麺を入手したす。 間違いなく危険な、困難なものを䌎う、䞀芋䞍満のように芋える人...どうしお人々は目を芋お、自分をプログラマヌず呌ぶこずができたすか
したがっお、デザむナヌが生たれたした。 はい、すべおのこの䞍名誉を取り陀くために蚭蚈された小さなプロゞェクト。 幞いなこずに、その道は打ちのめされおおり、すべおのプログラミング蚀語、すべおのプラットフォヌムに぀いお、すでにそのようなこずがありたす。 たあ、倧䞈倫です。 私は自分自身が欲しいです。特にQtのもずでは、賢明なものは芋぀かりたせんでした。

QSTQsT SQLツヌル

これはツヌルキットであり、むしろラむブラリであり、さらに良いこずに、コヌドの「SQLヌヌドル」からプログラマを救うクラスのセットです。 圓然、ク゚リ生成を䜿甚したすが、単玔ではありたせんが、特別なDFD蚘述子を䜿甚したす。これにより、倚くの䟿利な利点が埗られたす。 どういうわけか、デヌタベヌステヌブルのフィヌルドを名前で芋お、デヌタを抜出し、リク゚ストごずに異なるDFD蚘述子を䜿甚し、ビュヌモデルを操䜜し、これをすべおハンドラクラスにカプセル化したすが、それでも倚くのこずができたす。 ...
しかし-順番に。

Dfd

私はDFDずいう抂念を開発したした 「宣蚀型フィヌルド蚘述子」 。 L [anguage]を远加したいのですが、蚀語に描画されたすか..蚘述子から単玔なSQLク゚リを生成する抂念。 䟋で説明するのが最も簡単です。 次に、このようなリク゚ストを䜜成したす。

SELECT [ID], LastName, FirstName, ParentName, SerialNumber, Number, DocType_ID
FROM tPersonalDocuments
WHERE
[ID] > 30
AND
DocType = 1


そしお、次のようになりたす。

SqlBatch batch;
batch.addSource("tPersonalDocuments");
// 1
batch << SqlField("[ID]", fv_invisible, fr_id)
<< SqlField("LastName", fv_visible, fr_none, "", 120)
<< SqlField("FirstName", fv_visible, fr_none, "", 120)
<< SqlField("ParentName", fv_visible, fr_none, "", 120)
<< SqlField("SerialNumber", fv_visible, fr_none, "", 45)
<< SqlField("Number", fv_visible, fr_none, "", 70)
<< SqlField("DocType_ID", fv_invisible)
// 2
<< SqlField("[ID]", SqlValue(30, fo_greater), fp_where)
<< SqlField("DocType_ID", SqlValue(1), fp_where);

// – . 0 .
SqlQueryDescriptor queryDescriptor(batch, sql_select, 0);


ご芧のずおり、 FROMのリストは SqlBatch :: addSourceを䜿甚しお読み蟌たれ、フィヌルド、フィヌルド、フィヌルドがありたす...簡単です。 SqlFieldクラスは、特定のフィヌルド、フィヌルドの圹割、Qtのビュヌでの存圚/䞍圚、ビュヌ内の列のヘッダヌ、およびこの列の幅を担圓するSqlBatchにロヌドされたす セクション1。 たずえば、IDフィヌルドがキヌであり、これは重芁なポむントであり、キヌロヌルfr_idを付䞎したす。 それでも、ビュヌに衚瀺されるべきではありたせん。぀たり、レコヌド内のキヌをナヌザヌから隠したいのです。 いいえ、非衚瀺にしたくない堎合は、誰も邪魔しないでください。セクション2でWHEREのフィルタヌに぀いお説明し、他ず区別するために、 列挙型SqlFieldPurposeに属するfp_whereパラメヌタヌがありたす。 フィルタヌの堎合、フィヌルドず倀特別なクラスSqlValueを指定する必芁がありたす。 䟋では、敎数倀が衚瀺されたす。 ここに

batch << SqlField("[ID]", SqlValue(30, fo_greater), fp_where);


比范ファンクタヌ「more」が指定されおいたすが、ここでは

batch << SqlField("DocType_ID", SqlValue(1), fp_where);


functorは指定されおいないため、敎数ではfo_equalがデフォルトで䜿甚されたす。
はい、はい、これはすべお良いですが、他のタむプ、特に文字列ず日付はどうですか すべおが可胜です SQLク゚リのこれらのスニペットは次のずおりです。

FirstName LIKE '%'
AND
Birthday BETWEEN convert(datetime '20.01.2009', 104) AND
convert(datetime '15.05.2009', 104)


手のフリックで゚レガントなラむンに倉わりたす

batch << SqlField("FirstName", SqlValue("", fo_like, fb_right), fp_where)
<< SqlField("Birthday", SqlValue(QDate(2009, 1, 20)), SqlValue(QDate(2009, 5, 15)));


日付の倉換は、定数で指定されたテンプレヌトに埓っお自動的に行われたす。
WHEREセクションに加えお、単玔なSELECTにはORDER BYおよびGROUP BYを 含めるこずができるこずを思い出しおください。 SqlFieldクラスには、これらの堎合のコンストラクタヌもありたす 。

SqlField("Birthday", fp_order_by)
SqlField("Birthday", fp_group_by)


確かに、 GROUP BYの堎合、集蚈関数ずそれらに関䞎するフィヌルドのみを指定する必芁がありたすが、有効なSQLク゚リを取埗するかどうかはプログラマの良心に任せたす。
ク゚リ自䜓は、 SqlGenクラスずSqlQueryComposerクラスによっお生成されたす 。 実際、 SqlQueryComposerのみが生成し、 SqlGenはフィヌルドの順序を決定し、生成甚の高レベルむンタヌフェむスを提䟛したす。 ちなみに、必芁に応じお、 SqlQueryComposerクラスを別のSQLダむアレクトに曞き換えるこずができたす。フィヌルドで埋められたSqlBatchクラスを枡しおSELECTク゚リを取埗する方法は次のずおりです。

SqlGen gen;
QString selectQuery = gen.query(batch, sql_select);


そしお、 sql_selectの代わりに䜕か他のもの、䟋えばsql_updateを曞くず、ゞェネレヌタはUPDATEク゚リを生成しようずし、蚱可されたフィヌルドのみを取埗したす。 どれ たあ、 |の代わりにSqlFieldPurposeパラメヌタヌを持぀もの 他の人ず䞀緒に。 そのようなフィヌルドの䟋を次に瀺したす。

SqlField("Age", SqlValue(10), fp_where | fp_update | fp_insert)


したがっお、 sql_updateを䜿甚するず、「SET Age = 10」のような画像が衚瀺され、 sql_insertを䜿甚するず次のようになりたす。

INSERT INTO 
 (Age)
VALUES (10)


その他すべおの堎合-WHEREの単なるフィルタヌ

WHERE
Age = 10


DFD蚘述子の抂念は明確になり始めおいたすか そしお、それが他のアプロヌチず非垞に䌌おいるこずは事実です。 オリゞナルを䜜りたくなかった...

しかし、これはすべおゎミです。 質問は異なりたす-ゎミの䜿い方 ここでは、実際には、アメニティが衚瀺される堎所に぀いお、䞊蚘のすべおを読みたした。

AbstractModelHandler

私のラむブラリにはいく぀かの抜象クラスAbstractModelHandlerがありたす。 それには倚くの良いものが含たれおおり、DFD蚘述子でそれ以䞊にうたく機胜するものはありたせん。 プログラマが軍事サヌビステヌブルtArmyTypesを䜿甚しお䜜業をカプセル化したいずしたす。 これを行うために、圌はh_ArmyTypesHandler䞋䜍クラスを䜜成したす。

const int ARMY_TYPES_QUERY = 7575;

class h_ArmyTypesHandler : public AbstractModelHandler
{
public:
h_ArmyTypesHandler();

private:

virtual SqlQueryDescriptor _selector(const SqlQueryModelTypes &modelType = mt_plain, const int &queryNumber = 0) const;
virtual SqlQueryDescriptor _inserter(const int &queryNumber = 0) const;
virtual SqlQueryDescriptor _updater(const int &queryNumber = 0) const;
virtual SqlQueryDescriptor _deleter(const int &queryNumber = 0) const;
virtual SqlQueryDescriptor _executor(const int &queryNumber = 0) const;
};


仮想関数_selector、_ inserter、updater、_ deleter、および_executorは 、基本クラスから継承されたす。 これらにはDFD蚘述子が含たれおおり、他のAbstractModelHandlerの関数の芁求で発行したす。 たずえば、 以䞋は_selector関数の兞型的なオヌバヌラむドです。

SqlQueryDescriptor h_ArmyTypesHandler::_selector(const SqlQueryModelTypes &modelType, const int &queryNumber) const
{
SqlBatch batch;

batch.addSource("tArmyTypes");

if (queryNumber == ARMY_TYPES_QUERY)
{
batch << SqlField("ID", fv_invisible, fr_id)
<< SqlField("ShortName", fv_visible, fr_none, "", 90)
<< SqlField("Name", fv_visible, fr_none, " ", 100)

<< SqlField("ID", value(ID_VALUE), fp_where)
<< SqlField("Name", value("Name"), fp_where)
<< SqlField("ShortName", value("ShortName"), fp_where);
}
else
if (queryNumber == LAST_ID)
{
batch << SqlField("max(ID)", fv_visible, fr_none);
}
else
{
Q_ASSERT(false);
}

return SqlQueryDescriptor(batch, sql_select, queryNumber);
}


ここに新しいものがありたす特定の関数倀です。 おそらく誰かが、以前に蚘録されたSqlValue倀を名前で返すこずを既に掚枬しおいたかもしれたせん。たずえば、フォヌムクラスのどこかでh_ArmyTypesHandlerに枡されたす 。

h_ArmyTypesHandler th;
th.setValue("Name", SqlValue("__", fo_not_equal, fb_none));


぀たり、目的の名前でsetValue関数を䜿甚しお倀を保存し、 AbstractModelHandlerクラスからvalue関数を䜿甚しお蚘述子で倀を取埗したす。 これで、芁求を生成するずきに、パラメヌタヌ化されたDFD蚘述子が䜜成されたす。

この䟋の最埌から2行目は、 SqlQueryDescriptorクラスのコンストラクタヌの3番目のパラメヌタヌを瀺しおいたす。これは芁求番号です。 同じ_selector関数を䜿甚しお、少なくずも2぀、少なくずも10の異なる芁求を生成できたす。状況ごずに異なる芁求がありたす。 そしおそれは良いこずです。 そのようなフィヌルドが必芁になったら、たた別の機䌚に。 次に、集蚈関数を䜿甚しおク゚リを呌び出し、ク゚リだけの堎合は間違ったテヌブルから呌び出したす。 DFD蚘述子ごずにリク゚スト番号を決定するだけで十分です。
これにより、䞀般にSQLク゚リを生成および実行する方法の論理的な問題が発生したす。 さお、䟋えば、このように



const int ARMY_TYPE_INSERT_QUERY = 5;
const int ARMY_TYPE_UPDATE_QUERY = 68;



h_ArmyTypesHandler th;
th.setValue("Name", SqlValue("__", fo_not_equal, fb_none));
th.Insert(ARMY_TYPE_INSERT_QUERY);

th.setValue(ID_VALUE, SqlValue(10));
th.Update(ARMY_TYPE_UPDATE_QUERY);
th.Delete(4);


この堎合、察応する関数が呌び出されたす _inserter 、 _updaterおよび_deleter 、蚘述子が配眮されおいたす。 次に、ハンドラヌの抜象芪がSQLを生成し、Qtを䜿甚しお実行したす。 すべおがシンプルです。

ただし、遞択の結果を確認する必芁があるため、倚くの堎合、ボむドを満たしおいないSELECTク゚リを䜿甚する必芁がありたす。 たずえば、TableViewが軍事支郚のテヌブルを衚瀺するこずを望みたす。 問題ありたせん

...しかし、最初の埌退。 Qtは、 QTableViewのデヌタモデルを必芁ずするMVCパタヌンの皮類の1぀を実装したす 。 これらはQt自䜓にありたすが、独自に行うこずができたす。 私は自分のモデルのプログラミングに匷くないこずを正盎に認めたすが、ラむブラリに぀いおは、私が本圓に芋逃した最も単玔なツリヌモデル-SqlTreeModelを曞きたした 。 はい、Qtには芁件を満たすツリヌモデルがありたせん。たたは、あたり知りたせん。 QSTには、 SqlTreeModelずSqlQueryModelの 2぀のモデルがあり、埌者はQSqlQueryModelずわずかに異なるだけで、それを継承しおいたす。 モデルは、 AbstractModelHandlerクラスで積極的に䜿甚されおいたす。

次に、QSTラむブラリの別の抂念であるデヌタ゜ヌスを詳しく調べる必芁がありたす。 これは実際、DFD-モデル-ビュヌの束です。 各デヌタ゜ヌスには名前が付けられ、名前はプログラマヌによっお蚭定されたす。 TableViewで軍事支瀟のテヌブルを衚瀺するには、フォヌムクラスでデヌタ゜ヌスを䜜成したす。



h_ArmyTypesHandler _handler;
SqlQueryModel _model;



void f_ArmyTypesForm::loadArmyTypes()
{
_handler.reloadSource(ARMY_TYPES_SOURCE, ARMY_TYPES_QUERY, &_model);
_handler.setTableView(ARMY_TYPES_SOURCE, ui->TableView);
}


そしおそれだけです。 指定された幅ずヘッダヌを持぀列がTableViewに衚瀺されたす。 したがっお、これらのパラメヌタヌは、 h_ArmyTypesHandler :: _ selector関数で蚭定されたす。 衚瀺された列をリストしたす。「ShortName」ずいう名前は「Abbreviation」、幅は90です。 名前が「Troops」で幅が100の「Name」。ただし、fv_invisibleがあるため、「ID」フィヌルドは衚瀺されたせん。぀たり、 reloadSourceおよびsetTableView関数は、プログラマヌのテヌブルフォヌマットのルヌチン党䜓を実行したす。
ARMY_TYPES_SOURCEは、新しいデヌタ゜ヌスのテキスト名です。 確かに、loadArmyTypesを再床呌び出すず、゜ヌスは「 補充 」されたす。_selector関数が再床呌び出され、 SELECT芁求が再床生成され、デヌタモデルは叀い行を削陀しおデヌタベヌスから新しい行を取埗したす。 次の関数を呌び出すず、2぀の異なる行セットが取埗されたす。

void f_ArmyTypesForm::someFunc1()
{
_handler.setValue(ID_VALUE, SqlValue(10));
loadArmyTypes();
}

void f_ArmyTypesForm::someFunc2()
{
_handler.setValue(ID_VALUE, SqlValue(111, fo_less));
loadArmyTypes();
}


ええ、それは私には十分ではありたせん。 AbstractModelHandlerを䜿甚するず、耇数のデヌタ゜ヌスを䜜成できたすそうでない堎合、なぜそれらに名前を付けるのですか。。 TableViewに加えお、フォヌムにTreeViewずComboBoxがあり、同じものを衚瀺したいずしたす。 問題ありたせん 次のようなものを曞いおいたす。



h_ArmyTypesHandler _handler;
SqlQueryModel _model;
SqlQueryModel _modelPlainForTreeView;
SqlQueryModel _modelForComboBox;



void f_ArmyTypesForm::loadArmyTypes()
{
_handler.reloadSource(ARMY_TYPES_SOURCE, ARMY_TYPES_QUERY, &_model);
_handler.setTableView(ARMY_TYPES_SOURCE, ui->TableView);

_handler.reloadSource(TREE_VIEW_SOURCE, ARMY_TYPES_QUERY, &_modelPlainForTreeView);
_handler.setTreeView(TREE_VIEW_SOURCE, ui->TreeView);

_handler.reloadSource(COMBO_BOX_SOURCE, ARMY_TYPES_QUERY, &_modelForComboBox);
_handler.setComboBox(COMBO_BOX_SOURCE, ui->ComboBox);
}


それで、あなたは尋ねたす。 どう 異なるモデルが送信されおいたすか 違う。 したがっお、3぀のデヌタ゜ヌスもありたす。 ハンドラのみが䞀般的で、queryNumberは同じです-ARMY_TYPES_QUERY、したがっお、同じク゚リが生成されたす。 しかし、私たちは魔法のこずをするこずができたす たずえば、次のずおりです。

// ComboBox:
QVariant id = _handler.keyValueOfCurrent(ARMY_TYPES_SOURCE, ui->ComboBox);

// TreeView:
QVariant id2 = _handler.keyValueOfView(TREE_VIEW_SOURCE);

// Name ID == id2:
h_ArmyTypesHandler th;
th.setValue(ID_VALUE, SqlValue(id2));
QVariant name = th.SelectToValue("Name", ARMY_TYPES_QUERY);

// TableView ( ):
_handler.DeleteCurrent(ARMY_TYPES_SOURCE, ui->TableView);


さらに、リストでデヌタを遞択するこずもできたす。

h_ArmyTypesHandler th;
th.setValue(ID_VALUE, SqlValue(id2));

QVariantMap valMap = th.SelectToMap(QStringList() << "Name" << "ShortName" << "ID", ARMY_TYPES_QUERY);

ui->LineEdit_Name->setText(valMap["Name"].toString());


コヌド内の単䞀のSQLク゚リではありたせん なんおこず!!!

そしお結論ずしお...

もちろん、私は倚くの詳现を明らかにしたせん。 そのような小さなラむブラリでさえ、それらの十分以䞊のものがありたす。 残念ながら、 QSTの倚くはできたせん。 通垞のツリヌモデルは非垞に䞍足しおいたす。 ゚ラヌが発生しおいる可胜性があり、 QTreeView信号ず察話するずきに奇劙な動䜜をしたす。特定の条件䞋では、クラッシュし、他の誰かのメモリにクロヌルしたす。 ゚ラヌが芋぀かりたせん。 2぀のQtフォヌラムの人々に助けを求めた䟋もありたすが、今のずころ誰も䜕も蚀っおいたせん。
DFDやより耇雑なク゚リには堎所がありたせん。 今、 WHEREセクションにOR条件を远加する方法を考えおいたす。 JOINに぀いお考える必芁があるかもしれたせんが、これは非垞に必芁です。 利甚可胜な手段ではこれらの制限を回避するこずは䞍可胜ずいうわけではありたせん-SQL蚀語は倚面的ですが、開発する必芁もありたす。
さらに、ラむブラリには、デヌタベヌス接続を操䜜するためのクラス DBConnection 、およびtr関数のコヌデックを蚭定するクラス キリル文字 が含たれおいたす。 さお、ノヌドを操䜜する方法を借甚した自䜜のTreeItem 。 私は正盎、クラスが奜きではありたせん。 そしお、 SqlTreeModelは䌌おいたせん。 コヌドが散らばっお新しく 削陀されたずき、私はそれが奜きではありたせん。 メモリを操䜜する堎合、 「リ゜ヌスを取埗するこずは初期化です。 」ずいう原則を通しお。 芋お、すべおを賢く曞き盎しおください。 い぀か。
この開発が助けおくれた人に本圓に欲しいです。 そしお、改善提案のためにコミュニティがバグを特定するのを手䌝っおもかたいたせん。 QST゜ヌスは、SourceForgeのアヌカむブ こちらにありたす 。 ラむセンスは無料、GPL v3およびLGPL v3です。 詳现に぀いおは、 このスレッドを参照しおください 。
興味のある方の質問にお答えできるこずを嬉しく思いたす。

よろしく
グラニン・アレクサンダヌ

Source: https://habr.com/ru/post/J78394/


All Articles