C ++甚の新しいSQLite ORM

みなさんこんにちは。 私は初めおHabréに曞き蟌みたす。厳密に刀断しないでください。 C ++でナニバヌサルSQLite ORMラむブラリを芋぀けた経隓ず、C ++ sqlite_ormでSQLiteを操䜜するための独自のラむブラリの新しい開発を共有したいず思いたす。


ORMを怜玢したずき、いく぀かの重芁なポむントから始めたした。



hiberlite加えお、さたざたなラむブラリがhiberliteありたすが、䜕らかの理由で機胜がほずんどありたせん。 蚀い換えるず、開発者はlibsqlite3を䜿甚しおデヌタベヌスに盎接接続するためのコヌドを蚘述する必芁があるこずがlibsqlite3たすが、なぜそのようなORMが必芁なのでしょうか


私は導入郚に匕きずり蟌たれたようで、 sqlite_ormラむブラリが提䟛する可胜性に盎接行きたす 。


1CRUD


䟋


 struct User{ int id; std::string firstName; std::string lastName; int birthDate; std::shared_ptr<std::string> imageUrl; int typeId; }; 

 struct UserType { int id; std::string name; }; 

2぀のクラス、次に2぀のテヌブル。


盞互䜜甚は、デヌタベヌスぞのむンタヌフェむスを持぀サヌビスオブゞェクトであるstorageオブゞェクトを介しお行われたす。 storageはmake_storage関数によっお䜜成されたす。 䜜成時に、スキヌムが瀺されたす。


 using namespace sqlite_orm; auto storage = make_storage("db.sqlite", make_table("users", make_column("id", &User::id, autoincrement(), primary_key()), make_column("first_name", &User::firstName), make_column("last_name", &User::lastName), make_column("birth_date", &User::birthDate), make_column("image_url", &User::imageUrl), make_column("type_id", &User::typeId)), make_table("user_types", make_column("id", &UserType::id, autoincrement(), primary_key()), make_column("name", &UserType::name, default_value("name_placeholder")))); 

デヌタモデルはリポゞトリに぀いお「最新ではない」こずに泚意しおください。 たた、クラスの列名ずフィヌルド名は、互いに独立しおいたす。 これにより、たずえば、ラクダの堎合のコヌドず、アンダヌスコアを䜿甚したデヌタベヌススキヌマを蚘述できたす。


make_storage最初のパラメヌタヌはファむル名で、その埌テヌブルが移動したす。 テヌブルを䜜成するには、テヌブル名を指定したすクラスずは関係ありたせん。自動呜名を行う堎合、実装はあたり良くないためです typeid(T).name()を䜿甚する必芁がありたす。これは垞に明確な名前ではなくシステム名を返したすが、たたはマクロを䜿ったトリック通垞は承認しおいたせんを䜿甚しお、列を瀺したす。 1぀の列を䜜成するには、少なくずも2぀のパラメヌタヌが必芁です。列の名前ずクラスフィヌルドぞのリンクです。 このリンクは、列のタむプず将来の割り圓おのアドレスを決定したす。 埌でAUTOINCREMENTおよび/たたはPRIMARY KEYをDEFAULT远加するこずもできたす。


これで、 storageオブゞェクトの関数の呌び出しを介しおデヌタベヌスにク゚リを送信できたす。 たずえば、ナヌザヌを䜜成しおINSERTを実行したしょう。


 User user{-1, "Jonh", "Doe", 664416000, std::make_shared<std::string>("url_to_heaven"), 3 }; auto insertedId = storage.insert(user); cout << "insertedId = " << insertedId << endl; user.id = insertedId; 

INSERT INTO users(first_name, last_name, birth_date, image_url, type_id) VALUES('Jonh', 'Doe', 664416000, 'url_to_heaven', 3) 。


ナヌザヌオブゞェクトの䜜成時に指定した最初の匕数-1はidです。 idはPRIMARY KEY列であるため、䜜成時には無芖されたす。 sqlite_ormは、INSERT sqlite_orm PRIMARY KEY列を無芖し、新しく䜜成されたオブゞェクトのIDを返したす。 したがっお、INSERTの埌、 user.id = insertedId;を実行しuser.id = insertedId; -その埌、ナヌザヌは本栌的になり、コヌドでさらに䜿甚できたす。


同じナヌザヌを取埗するには、 get関数を䜿甚したす。


 try{ auto user = storage.get<User>(insertedId); cout << "user = " << user.firstName << " " << user.lastName << endl; }catch(sqlite_orm::not_found_exception) { cout << "user not found with id " << insertedId << endl; }catch(...){ cout << "unknown exeption" << endl; } 

getは、 Userクラスのオブゞェクトテンプレヌトパラメヌタヌずしお枡したものを返したす。 このIDを持぀ナヌザヌがいない堎合、䟋倖sqlite_orm::not_found_exceptionたす。 䟋倖のあるこのようなむンタヌフェむスは䞍䟿かもしれたせん。 これは、C ++では、Java、C、たたはObjective-Cで実行できるため、オブゞェクトだけを無効にできないためです。 null蚱容型ずしおstd::shared_ptr<T>を䜿甚できたす。 この堎合、 get関数の2番目のバヌゞョンget_no_throwたす。


 if(auto user = storage.get_no_throw<User>(insertedId)){ cout << "user = " << user->firstName << " " << user->lastName << endl; }else{ cout << "no user with id " << insertedId << endl; } 

ここで、ナヌザヌはstd::shared_ptr<User>あり、 nullptrず同じにするこずも、ナヌザヌ自䜓を保存するこずもできたす。


次に、ナヌザヌをUPDATEたい堎合がありたす。 これを行うには、倉曎するフィヌルドを倉曎し、 update関数を呌び出したす。


 user.firstName = "Nicholas"; user.imageUrl = "https://cdn1.iconfinder.com/data/icons/man-icon-set/100/man_icon-21-512.png" storage.update(user); 

これは次のように機胜しUPDATE users SET ... primary key... WHERE id = % , , primary key%が呌び出されUPDATE users SET ... primary key... WHERE id = % , , primary key% 。


すべおがシンプルです。 リポゞトリず察話するためのプロキシオブゞェクトはないこずに泚意しおください。リポゞトリは「クリヌンな」モデルオブゞェクトを受け入れお返したす。 これにより、ゞョブが簡玠化され、゚ントリのしきい倀が䞋がりたす。


IDによるオブゞェクトの削陀は、次のように実装されたす。


 storage.remove<User>(insertedId); 

ここでは、コンパむラで型を掚枬する堎所がないため、テンプレヌトパラメヌタずしお型を明瀺的に指定する必芁がありたす。


これがCRUDの終了点です。 ただし、これは機胜に限定されたせん。 sqlite_ormのCRUD関数は、 PRIMARY KEYを持぀1぀の列を持぀オブゞェクトでのみ機胜する関数です。 CRUD以倖の機胜もありたす。


たずえば、 SELECT * FROM users実行しおみたしょう。


 auto allUsers = storage.get_all<User>(); cout << "allUsers (" << allUsers.size() << "):" << endl; for(auto &user : allUsers) { cout << storage.dump(user) << endl; } 

倉数allUsersのタむプはstd::vector<User>です。 dump関数に泚意しおください。リポゞトリに関連付けられたクラスオブゞェクトを取埗し、 std::string圢匏でjsonスタむルで情報を返しstd::string 。 たずえば、「{id '1'、first_name 'Jonh'、last_name 'Doe'、birth_date '664416000'、image_url '0x10090c3d8'、type_id '3'}」。


しかし、これでは十分ではありたせん。 ORMラむブラリは、WHERE句がないず完党ず芋なすこずはできたせん。 したがっお、 sqlite_ormにも存圚したすが、非垞に匷力です。


䞊蚘のget_all関数は、条件付きのwhere関数の結果を匕数ずしお取るこずができたす。 たずえば、IDが10未満のナヌザヌを遞択しおみたしょう。ク゚リは次のようになりたすSELECT * FROM users WHERE id < 10 。 コヌドでは、次のようになりたす。


 auto idLesserThan10 = storage.get_all<User>(where(lesser_than(&User::id, 10))); 

たたは、firstNameフィヌルドが「John」に等しくないナヌザヌを遞択したす。 ク゚リはSELECT * FROM users WHERE first_name != 'John'


 auto notJohn = storage.get_all<User>(where(is_not_equal(&User::firstName, "John"))); 

さらに、挔算子&& 、 || そしお! より明確にするために、これらの挔算子のリテラルバヌゞョンを䜿甚するこずをお勧めしたす。


 auto notJohn2 = storage.get_all<User>(where(not is_equal(&User::firstName, "John"))); 

notJohn2同等notJohn 。


そしお、リンクされた条件を持぀別の䟋


 auto id5and7 = storage.get_all<User>(where(lesser_or_equal(&User::id, 7) and greater_or_equal(&User::id, 5) and not is_equal(&User::id, 6))); 

このク゚リSELECT * FROM users WHERE where id >= 5 and id <= 7 and not id = 6を実装したした。


たたはSELECT * FROM users WHERE id = 10 or id = 16 


 auto id10or16 = storage.get_all<User>(where(is_equal(&User::id, 10) or is_equal(&User::id, 16))); 

したがっお、条件の任意の組み合わせを「接着」できたす。 さらに、SQLiteの「生のク゚リ」のように括匧を䜿甚しお条件の優先床を指定できたす。 たずえば、次の2぀のク゚リは、返される結果が異なりたす。


 auto cuteConditions = storage.get_all<User>(where((is_equal(&User::firstName, "John") or is_equal(&User::firstName, "Alex")) and is_equal(&User::id, 4))); cuteConditions = storage.get_all<User>(where(is_equal(&User::firstName, "John") or (is_equal(&User::firstName, "Alex") and is_equal(&User::id, 4)))); 

最初の条件ではWHERE (first_name = 'John' or first_name = 'Alex') and id = 4で、2番目の条件ではWHERE first_name = 'John' or (first_name = 'Alex' and id = 4)です。


この魔法は、C ++の括匧には操䜜の優先順䜍を明瀺的に決定するのず同じ機胜があるために機胜したす。 さらに、 sqlite_orm自䜓はC ++でSQLiteを操䜜するための䟿利なフロント゚ンドであり、それラむブラリ自䜓はク゚リを実行せず、テキストに倉換しおsqlite3゚ンゞンを送信したす。


INステヌトメントもありたす。


 auto evenLesserTen10 = storage.get_all<User>(where(in(&User::id, {2, 4, 6, 8, 10}))); 

SELECT * FROM users WHERE id IN (2, 4, 6, 8, 10)が刀明したした。 たたはここに行がありたす


 auto doesAndWhites = storage.get_all<User>(where(in(&User::lastName, {"Doe", "White"}))); 

ここでは、 SELECT * FROM users WHERE last_name IN ("Doe", "White")リク゚ストをデヌタベヌスに送信したした。


in関数は、クラスフィヌルドぞのポむンタヌずベクトル/初期化リストの2぀の匕数を取りたす。 ベクトル/初期化リストのコンテンツタむプは、最初のパラメヌタヌずしお枡したフィヌルドポむンタヌず同じです。


条件関数is_equal 、 is_not_equal 、 greater_than 、 greater_or_equal 、 lesser_than 、 lesser_or_equalはそれぞれ2぀の匕数を取りたす。 匕数は、クラスフィヌルドぞのポむンタ、たたは定数/倉数のいずれかです。 フィヌルドぞのポむンタは、列名、およびリテラルのたたク゚リに解析され、文字列のみが゚ッゞの呚りに匕甚笊が残っおいたす。


質問があるかもしれたせんどの列にも瀺されおいないクラスフィヌルドぞのポむンタヌを条件に枡すずどうなりたすか この堎合、䟋倖std::runtime_errorが説明テキストずずもにスロヌされたす。 リポゞトリにバむンドされおいないタむプを指定した堎合も同じこずが起こりたす。


ずころで、 WHERE句はDELETEク゚リで䜿甚できたす。 これにはremove_all関数がありremove_all 。 たずえば、IDが100未満のすべおのナヌザヌを削陀したしょう。


 storage.remove_all<User>(where(lesser_than(&User::id, 100))); 

䞊蚘の䟋はすべお、本栌的なオブゞェクトで動䜜したす。 しかしSELECT 1぀の列でSELECTを呌び出したい堎合はどうでしょうか これもありたす


 auto allIds = storage.select(&User::id); 

これをSELECT id FROM usersず呌びたした。 allIdsのタむプはstd::vector<decltype(User::id)>たたはstd::vector<int>です。


条件を远加できたす


 auto doeIds = storage.select(&User::id, where(is_equal(&User::lastName, "Doe"))); 

ごSELECT id FROM users WHERE last_name = 'Doe'ずおり、これはSELECT id FROM users WHERE last_name = 'Doe'です。


倚くのオプションがありたす。 たずえば、idが300未満のすべおの姓を芁求できたす。


 auto allLastNames = storage.select(&User::lastName, where(lesser_than(&User::id, 300))); 

ORDER BY


ORMもORMも泚文なし。 ORDER BY倚くのプロゞェクトで䜿甚されおおり、 sqlite_ormにはむンタヌフェヌスがありたす。


最も簡単な䟋-idで䞊べられたナヌザヌを遞択したしょう


 auto orderedUsers = storage.get_all<User>(order_by(&User::id)); 

これはSELECT * FROM users ORDER BY id倉わりSELECT * FROM users ORDER BY id 。 たたは、 whereずorder_by組み合わせおみたしょう SELECT * FROM users WHERE id < 250 ORDER BY first_name


 auto orderedUsers2 = storage.get_all<User>(where(lesser_than(&User::id, 250)), order_by(&User::firstName)); 

明瀺的なASCおよびDESC指定するこずもできたす。 䟋 SELECT * FROM users WHERE id > 100 ORDER BY first_name ASC 


 auto orderedUsers3 = storage.get_all<User>(where(greater_than(&User::id, 100)), order_by(asc(&User::firstName))); 

たたはここ


 auto orderedUsers4 = storage.get_all<User>(order_by(desc(&User::id))); 

SELECT * FROM users ORDER BY id DESCでした。


そしおもちろん、 selectはorder_byでも動䜜しselect 


 auto orderedFirstNames = storage.select(&User::firstName, order_by(desc(&User::id))); 

SELECT first_name FROM users ORDER BY ID DESCでした。


移行


ラむブラリには移行自䜓はありsync_schemaが、 sync_schema関数がありたす。 この関数の呌び出しは、デヌタベヌスに珟圚のスキヌムを芁求し、ストレヌゞの䜜成時に指定されたスキヌムず比范し、䞀臎しないものがあればそれを修正したす。 同時に、既存のデヌタの安党性はこの呌び出しを保蚌したせん。 スキヌムが同䞀になるこずのみを保蚌したすたたはstd::runtime_errorがスロヌされたす。スキヌムを同期するルヌルの詳现に぀いおは、 githubのリポゞトリペヌゞを参照しおください。


取匕


ラむブラリには、トランザクションを実装するための2぀のオプション、明瀺的および暗黙的がありたす。 明瀺的ずは、 begin_transactionおよびcommitたたはrollback関数の盎接呌び出しを意味したす。 䟋


 auto secondUser = storage.get<User>(2); storage.begin_transaction(); secondUser.typeId = 3; storage.update(secondUser); storage.rollback(); //  storage.commit(); secondUser = storage.get<decltype(secondUser)>(secondUser.id); assert(secondUser.typeId != 3); 

2番目の方法は少し耇雑です。 最初のコヌド


 storage.transaction([&] () mutable { auto secondUser = storage.get<User>(2); secondUser.typeId = 1; storage.update(secondUser); auto gottaRollback = bool(rand() % 2); if(gottaRollback){ //     return false; //      ROLLBACK } return true; //      COMMIT }); 

transaction関数はすぐにBEGIN TRANSACTION呌び出し、ラムダ匏を匕数ずしお受け取り、 boolを返したす。 true返された堎合、 COMMITが実行されROLLBACK false堎合 ROLLBACK このメ゜ッドは、トランザクション終了関数暙準ラむブラリのmutexのstd::lock_guardなどを呌び出すこずを忘れないようにしたす。


集蚈関数AVG 、 MAX 、 MIN 、 COUNT 、 GROUP_CONCATたす。


 auto averageId = storage.avg(&User::id); // 'SELECT AVG(id) FROM users' auto averageBirthDate = storage.avg(&User::birthDate); // 'SELECT AVG(birth_date) FROM users' auto usersCount = storage.count<User>(); // 'SELECT COUNT(*) FROM users' auto countId = storage.count(&User::id); // 'SELECT COUNT(id) FROM users' auto countImageUrl = storage.count(&User::imageUrl); // 'SELECT COUNT(image_url) FROM users' auto concatedUserId = storage.group_concat(&User::id); // 'SELECT GROUP_CONCAT(id) FROM users' auto concatedUserIdWithDashes = storage.group_concat(&User::id, "---"); // 'SELECT GROUP_CONCAT(id, "---") FROM users' auto maxId = storage.max(&User::id); // 'SELECT MAX(id) FROM users' auto maxFirstName = storage.max(&User::firstName); // 'SELECT MAX(first_name) FROM users' auto minId = storage.min(&User::id); // 'SELECT MIN(id) FROM users' auto minLastName = storage.min(&User::lastName); // 'SELECT MIN(last_name) FROM users' 

詳现はこちらをご芧ください 。 批刀ず同様、貢献も歓迎したす。


線集1


最埌のコミットでは、タプルタプルから耇数の列を遞択しおベクタヌに「生」する機胜が远加されおいたす。 䟋


 // `SELECT first_name, last_name FROM users WHERE id > 250 ORDER BY id` auto partialSelect = storage.select(columns(&User::firstName, &User::lastName), where(greater_than(&User::id, 250)), order_by(&User::id)); cout << "partialSelect count = " << partialSelect.size() << endl; for(auto &t : partialSelect) { auto &firstName = std::get<0>(t); auto &lastName = std::get<1>(t); cout << firstName << " " << lastName << endl; } 

線集2


最埌のコミットにより、 LIMITおよびOFFSETサポヌトが远加されたす。 LIMITおよびOFFSETを䜿甚するための3぀のオプションがありたす。


  1. LIMITlimit
  2. LIMITlimitOFFSEToffset
  3. LIMIToffset、limit

䟋


 // `SELECT first_name, last_name FROM users WHERE id > 250 ORDER BY id LIMIT 5` auto limited5 = storage.get_all<User>(where(greater_than(&User::id, 250)), order_by(&User::id), limit(5)); cout << "limited5 count = " << limited5.size() << endl; for(auto &user : limited5) { cout << storage.dump(user) << endl; } // `SELECT first_name, last_name FROM users WHERE id > 250 ORDER BY id LIMIT 5, 10` auto limited5comma10 = storage.get_all<User>(where(greater_than(&User::id, 250)), order_by(&User::id), limit(5, 10)); cout << "limited5comma10 count = " << limited5comma10.size() << endl; for(auto &user : limited5comma10) { cout << storage.dump(user) << endl; } // `SELECT first_name, last_name FROM users WHERE id > 250 ORDER BY id LIMIT 5 OFFSET 10` auto limit5offset10 = storage.get_all<User>(where(greater_than(&User::id, 250)), order_by(&User::id), limit(5, offset(10))); cout << "limit5offset10 count = " << limit5offset10.size() << endl; for(auto &user : limit5offset10) { cout << storage.dump(user) << endl; } 

LIMIT 5, 10およびLIMIT 5 OFFSET 10意味が異なるこずを忘れないでください。 正確には、 LIMIT 5, 10 LIMIT 10 OFFSET 5はLIMIT 10 OFFSET 5です。



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


All Articles