Repository: BOT-Man-JL/ORM-Lite Branch: master Commit: dd8bd2371f81 Files: 17 Total size: 149.4 KB Directory structure: gitextract_a6u7d0as/ ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── appveyor.yml ├── docs/ │ ├── get-started.md │ └── orm-lite.md ├── sample/ │ ├── makefile │ ├── sample.cpp │ ├── sample.pbxproj │ └── sample.vcxproj ├── src/ │ ├── nullable.h │ └── ormlite.h └── test/ ├── makefile ├── test.cpp ├── test.pbxproj └── test.vcxproj ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ [Xx]64/ [Xx]86/ [Bb]uild/ bld/ [Bb]in/ [Oo]bj/ # Visual Studio 2015 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUNIT *.VisualState.xml TestResult.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # DNX project.lock.json artifacts/ *_i.c *_p.c *_i.h *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile *.VC.db # Visual Studio profiler *.psess *.vsp *.vspx *.sap # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # JustCode is a .NET coding add-in .JustCode # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # TODO: Un-comment the next line if you do not want to checkin # your web deploy settings because they may include unencrypted # passwords #*.pubxml *.publishproj # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore **/packages/* # except build/, which is used as an MSBuild target. !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config # NuGet v3's project.json files produces more ignoreable files *.nuget.props *.nuget.targets # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Windows Store app package directory AppPackages/ BundleArtifacts/ # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ # Others ClientBin/ [Ss]tyle[Cc]op.* ~$* *~ *.dbmdl *.dbproj.schemaview *.pfx *.publishsettings node_modules/ orleans.codegen.cs # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files *.mdf *.ldf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # LightSwitch generated files GeneratedArtifacts/ ModelManifest.xml # Paket dependency manager .paket/paket.exe # FAKE - F# Make .fake/ # Xcode # # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore ## Build generated build/ DerivedData/ ## Various settings *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 xcuserdata/ ## Other *.moved-aside *.xccheckout *.xcscmblueprint # SQLite src/sqlite3.c src/sqlite3.h # Catch catch.hpp # Make Output *.db *.o sample/sample test/test ================================================ FILE: .travis.yml ================================================ language: cpp sudo: required compiler: - gcc - clang script: - unzip -qu src/sqlite3.h.zip -d src - unzip -qu src/sqlite3.c.zip -d src - unzip -qu test/catch.hpp.zip -d test - make -C sample && ./sample/sample - make -C test && ./test/test install: - if [ "$CC" = "gcc" ]; then export CXX="g++-5" CC="gcc-5"; fi - if [ "$CC" = "clang" ]; then export CXX="clang++-3.8" CC="clang-3.8"; fi addons: apt: sources: - ubuntu-toolchain-r-test - llvm-toolchain-precise-3.8 packages: - gcc-5 - g++-5 - clang-3.8 - unzip ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2016 BOT Man JL Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # ORM Lite **ORM Lite** is a C++ [_**Object Relation Mapping** (ORM)_](https://en.wikipedia.org/wiki/Object-relational_mapping) for **SQLite3** (currently 😂), written in Modern C++ style. [![Build status - MSVC](https://ci.appveyor.com/api/projects/status/github/BOT-Man-JL/ORM-Lite?svg=true&branch=master)](https://ci.appveyor.com/project/BOT-Man-JL/ORM-Lite) [![Build status - gcc/clang](https://travis-ci.org/BOT-Man-JL/ORM-Lite.svg?branch=master)](https://travis-ci.org/BOT-Man-JL/ORM-Lite) ## Features - **Easy** to Use - **Header Only** ([src/ormlite.h](src/ormlite.h), [src/nullable.h](src/nullable.h)) - **Powerful** Compile-time **Type/DSL Deduction** ## Documentation #### [Get Started Here](docs/get-started.md) 😉 #### [Full Document](docs/orm-lite.md) 😊 ## Planned Features - Support More Databases (Looking for a Better Driver recently...) - Customized Primary Key (Hard to Design an Elegant Interface for it...) - Blob (Hard to be Serialized to Script...) - Date/Time Types (Weak Typed in SQL...) - Subquery (Too Complicated... the Interface would be) Feel free to [Issue](https://github.com/BOT-Man-JL/ORM-Lite/issues/new), if you have any idea. 😎 ## Implementation Details (实现细节) Posts in **Chinese** only: - [How to Design a Naive C++ ORM](https://BOT-Man-JL.github.io/articles/?post=2016/How-to-Design-a-Naive-Cpp-ORM) - [How to Design a Better C++ ORM](https://bot-man-jl.github.io/articles/?post=2016/How-to-Design-a-Better-Cpp-ORM) ================================================ FILE: appveyor.yml ================================================ version: 1.0.{build} image: Visual Studio 2017 build_script: - ps: Expand-Archive src/sqlite3.h.zip -DestinationPath src - ps: Expand-Archive src/sqlite3.c.zip -DestinationPath src - ps: Expand-Archive test/catch.hpp.zip -DestinationPath test - ps: msbuild /verbosity:minimal sample/sample.vcxproj "/p:Configuration=Debug;Platform=Win32" - ps: msbuild /verbosity:minimal sample/sample.vcxproj "/p:Configuration=Debug;Platform=x64" - ps: msbuild /verbosity:minimal sample/sample.vcxproj "/p:Configuration=Release;Platform=Win32" - ps: msbuild /verbosity:minimal sample/sample.vcxproj "/p:Configuration=Release;Platform=x64" - ps: msbuild /verbosity:minimal test/test.vcxproj "/p:Configuration=Debug;Platform=Win32" - ps: msbuild /verbosity:minimal test/test.vcxproj "/p:Configuration=Debug;Platform=x64" - ps: msbuild /verbosity:minimal test/test.vcxproj "/p:Configuration=Release;Platform=Win32" - ps: msbuild /verbosity:minimal test/test.vcxproj "/p:Configuration=Release;Platform=x64" test_script: - ps: sample\Debug\sample\sample.exe - ps: sample\Release\sample.exe - ps: sample\x64\Debug\sample\sample.exe - ps: sample\x64\Release\sample.exe - ps: test\Debug\test\test.exe - ps: test\Release\test.exe - ps: test\x64\Debug\test\test.exe - ps: test\x64\Release\test.exe ================================================ FILE: docs/get-started.md ================================================ # Get Started Here is a **short tour** for this **Amazing** ORM 😉 The full code is in [sample.cpp](../sample/sample.cpp). ## Preparation Before we start, Include **src** into your Project: - `ORMLite.h` - **SQLite3** Dependency > If you haven't installed **SQLite3**, > you can find the zip file of `sqlite3.h` and `sqlite3.c` in `/src` ## Including *ORM Lite* ``` cpp #include "ORMLite.h" using namespace BOT_ORM; using namespace BOT_ORM::Expression; struct UserModel { int user_id; std::string user_name; double credit_count; Nullable age; Nullable salary; Nullable title; // Inject ORM-Lite into this Class :-) ORMAP ("UserModel", user_id, user_name, credit_count, age, salary, title); }; ``` `Nullable` helps us construct `Nullable` Value in C++, which is described in the [Document](orm-lite.md) 😁 In this sample, `ORMAP ("UserModel", ...)` do that: - `Class UserModel` will be mapped into `TABLE UserModel`; - NOT `Nullable` members will be mapped as `NOT NULL`; - `int, double, std::string` will be mapped into `INT, REAL, TEXT` respectively; - The first entry `id` will be set as the **Primary Key** of the Table; And then we define and inject other 2 Classes in this sample: ``` cpp struct SellerModel { int seller_id; std::string seller_name; double credit_count; // Inject ORM-Lite into this Class :-) ORMAP ("SellerModel", seller_id, seller_name, credit_count); }; struct OrderModel { int order_id; int user_id; int seller_id; std::string product_name; Nullable fee; // Inject ORM-Lite into this Class :-) ORMAP ("OrderModel", order_id, user_id, seller_id, product_name, fee); }; ``` ## Field Extracting - `FieldExtractor` ``` cpp // Define more Query Helper Objects and their Field Extractor UserModel user; SellerModel seller; OrderModel order; auto field = FieldExtractor { user, seller, order }; // Extract Field from 'field' // For example: field (user.user_name) // => Retrieve the field of user_name in UserModel table ``` ## Working on *Database* with *ORMapper* - `ORMapper` ``` cpp // Open a Connection with 'sample.db' ORMapper mapper ("sample.db"); ``` ## Create or Drop Tables - `ORMapper.CreateTbl` - `ORMapper.DropTbl` - `Expression` ``` cpp // Create Table with Constraints mapper.CreateTbl ( UserModel {}, Constraint::Default (field (user.salary), 1000.0)); // Remarks: // CREATE TABLE UserModel( // user_id INTEGER NOT NULL PRIMARY KEY, // user_name TEXT NOT NULL, // credit_count REAL NOT NULL, // age INTEGER, // salary REAL DEFAULT 1000, // title TEXT); mapper.CreateTbl ( SellerModel {}, Constraint::Check (field (seller.credit_count) > 0.0)); // Remarks: // CREATE TABLE SellerModel( // seller_id INTEGER NOT NULL PRIMARY KEY, // seller_name TEXT NOT NULL, // credit_count REAL NOT NULL, // CHECK (credit_count > 0)); mapper.CreateTbl ( OrderModel {}, Constraint::Reference ( field (order.user_id), field (user.user_id)), Constraint::Reference ( field (order.seller_id), field (seller.seller_id)), Constraint::Unique (Constraint::CompositeField { field (order.product_name), field (order.fee) })); // Remarks: // CREATE TABLE OrderModel( // order_id INTEGER NOT NULL PRIMARY KEY, // user_id INTEGER NOT NULL, // seller_id INTEGER NOT NULL, // product_name TEXT NOT NULL, // fee REAL, // FOREIGN KEY (user_id) REFERENCES UserModel(user_id), // FOREIGN KEY (seller_id) REFERENCES SellerModel(seller_id), // UNIQUE (product_name, fee)); ... // Drop Tables mapper.DropTbl (UserModel {}); mapper.DropTbl (SellerModel {}); mapper.DropTbl (OrderModel {}); ``` #### User Model | user_id| user_name| credit_count| age| salary| title| |--------|----------|-------------|-------|--------|-------| | 0| John| 0.2| 21| 1000.0| `null`| | 1| Jack| 0.4| `null`| 3.14| `null`| | 2| Jess| 0.6| `null`| 1000.0| Dr.| | ...| ...| ...| ...| ...| ...| #### Seller Model | seller_id| seller_name| credit_count| |----------|------------|-------------| | ...| ...| ...| #### Order Model | order_id| user_id| seller_id| product_name| fee| |---------|--------|----------|-------------|----| | ...| ...| ...| ...| ...| ## Basic CRUD - `ORMapper.Insert` (Create) - `ORMapper.Query` (Read) - `ORMapper.Update` (Update) - `ORMapper.Delete` (Delete) - `ORMapper.Transaction` - `Table Constraints` ``` cpp std::vector initObjs = { { 0, "John", 0.2, 21, nullptr, nullptr }, { 1, "Jack", 0.4, nullptr, 3.14, nullptr }, { 2, "Jess", 0.6, nullptr, nullptr, std::string ("Dr.") } }; // Insert Values with Primary Key for (const auto &obj : initObjs) mapper.Insert (obj); initObjs[1].salary = nullptr; initObjs[1].title = "St."; // Update Entity by Primary Key (WHERE UserModel.id = 1) mapper.Update (initObjs[1]); // Delete Entity by Primary Key (WHERE UserModel.id = 2) mapper.Delete (initObjs[2]); // Transactional Statements try { mapper.Transaction ([&] () { mapper.Delete (initObjs[0]); // OK mapper.Insert (UserModel { 1, "Joke", 0, nullptr, nullptr, nullptr }); // Failed }); } catch (const std::exception &ex) { // If any statement Failed, throw an exception std::cout << ex.what () << std::endl; // SQL error: 'UNIQUE constraint failed: UserModel.id' // Remarks: // mapper.Delete (initObjs[0]); will not applied :-) } // Select All to List auto result1 = mapper.Query (UserModel {}).ToList (); // decltype (result1) == std::list // result1 = [{ 0, 0.2, "John", 21, 1000, null }, // { 1, 0.4, "Jack", null, null, "St." }] // Table Constraints try { // Insert Values without Primary Key mapper.Insert (SellerModel { 0, "John Inc.", 0.0 }, false); } catch (const std::exception &ex) { std::cout << ex.what () << std::endl; // SQL error: 'CHECK constraint failed: SellerModel' } ``` ## Batch Operations - `ORMapper.InsertRange` - `ORMapper.UpdateRange` ``` cpp std::vector dataToSeed; for (int i = 50; i < 100; i++) dataToSeed.emplace_back (UserModel { i, "July_" + std::to_string (i), i * 0.2, nullptr, nullptr, nullptr }); // Insert by Batch Insert mapper.Transaction ([&] () { mapper.InsertRange (dataToSeed); }); for (size_t i = 0; i < 20; i++) { dataToSeed[i + 30].age = 30 + (int) i / 2; dataToSeed[i + 20].title = "Mr. " + std::to_string (i); } // Update by Batch Update mapper.Transaction ([&] () { // Note that: it will erase the default value of 'salary' mapper.UpdateRange (dataToSeed); }); ``` ## Single-Table Query - `Expression` - `Aggregate` - `Queryable.Where` - `Queryable.OrderBy` - `Queryable.Take` - `Queryable.Skip` - `Queryable.Select` ``` cpp // Select by Condition auto result2 = mapper.Query (UserModel {}) .Where ( field (user.user_name) & std::string ("July%") && (field (user.age) >= 32 && field (user.title) != nullptr) ) .OrderByDescending (field (user.age)) .OrderBy (field (user.user_id)) .Take (3) .Skip (1) .ToVector (); // Remarks: // SELECT * FROM UserModel // WHERE (user_name LIKE 'July%' AND // (age >= 32 AND title IS NOT NULL)) // ORDER BY age DESC, user_id // LIMIT 3 OFFSET 1 // decltype (result2) == std::vector // result2 = [{ 89, 17.8, "July_89", 34, null, "Mr. 19" }, // { 86, 17.2, "July_86", 33, null, "Mr. 16" }, // { 87, 17.4, "July_87", 33, null, "Mr. 17" }] // Calculate Aggregate Function auto avg = mapper.Query (UserModel {}) .Where (field (user.user_name) & std::string ("July%")) .Aggregate (Avg (field (user.credit_count))); // Remarks: // SELECT AVG (credit_count) FROM UserModel // WHERE (user_name LIKE 'July%') // avg = 14.9 auto count = mapper.Query (UserModel {}) .Where (field (user.user_name) | std::string ("July%")) .Aggregate (Count ()); // Remarks: // SELECT COUNT (*) FROM UserModel // WHERE (user_name NOT LIKE 'July%') // count = 2 ``` ## Update / Delete by Statement - `Expression` - `ORMapper.Update` - `ORMapper.Delete` ``` cpp // Update by Condition mapper.Update ( UserModel {}, (field (user.age) = 10) && (field (user.credit_count) = 1.0), field (user.user_name) == std::string ("July")); // Remarks: // UPDATE UserModel SET age = 10,credit_count = 1.0 // WHERE (user_name = 'July') // Delete by Condition mapper.Delete (UserModel {}, field (user.user_id) >= 90); // Remarks: // DELETE FROM UserModel WHERE (id >= 90) ``` ## Multi-Table Query - `Expression` - `Aggregate` - `Queryable.Join` - `Queryable.LeftJoin` - `Queryable.Select` - `Queryable.GroupBy` - `Queryable.Having` - `Queryable.Union` ``` cpp mapper.Transaction ([&] () { for (size_t i = 0; i < 50; i++) { mapper.Insert ( SellerModel { (int) i + 50, "Seller " + std::to_string (i), 3.14 }); mapper.Insert ( OrderModel { 0, (int) i / 2 + 50, (int) i / 4 + 50, "Item " + std::to_string (i), i * 0.5 }, false); } }); // Join Tables for Query auto joinedQuery = mapper.Query (UserModel {}) .Join (OrderModel {}, field (user.user_id) == field (order.user_id)) .LeftJoin (SellerModel {}, field (seller.seller_id) == field (order.seller_id)) .Where (field (user.user_id) >= 65); // Get Result to List auto result3 = joinedQuery.ToList (); // Remarks: // SELECT * FROM UserModel // JOIN OrderModel // ON UserModel.user_id=OrderModel.user_id // LEFT JOIN SellerModel // ON SellerModel.seller_id=OrderModel.seller_id // WHERE (UserModel.user_id>=65) // decltype (result3) == std::list, ..>> // result3 = [(65, "July_65", 13, null, null, null, // 31, 65, 57, "Item 30", 15, // 57, "Seller 7", 3.14), // (65, "July_65", 13, null, null, null, // 32, 65, 57, "Item 31", 15.5, // 57, "Seller 7", 3.14), // ... ] // Group & Having ~ auto result4 = joinedQuery .Select (field (order.user_id), field (user.user_name), Avg (field (order.fee))) .GroupBy (field (user.user_name)) .Having (Sum (field (order.fee)) >= 40.5) .Skip (3) .ToList (); // Remarks: // SELECT OrderModel.user_id, // UserModel.user_name, // AVG (OrderModel.fee) // FROM UserModel // JOIN OrderModel // ON UserModel.user_id=OrderModel.user_id // LEFT JOIN SellerModel // ON SellerModel.seller_id=OrderModel.seller_id // WHERE (UserModel.user_id>=65) // GROUP BY UserModel.user_name // HAVING SUM (OrderModel.fee)>=40.5 // LIMIT ~0 OFFSET 3 // decltype (result4) == std::list, ..>> // result4 = [(73, "July_73", 23.25), // (74, "July_74", 24.25)] // Compound Select // Results are Nullable-Tuples auto result5 = mapper.Query (OrderModel {}) .Select (field (order.product_name), field (order.user_id)) .Where (field (order.user_id) == 50) .Union ( joinedQuery .Select (field (user.user_name), field (order.order_id)) ) .Take (4) .ToList (); // Remarks: // SELECT OrderModel.product_name, // OrderModel.user_id // FROM OrderModel // WHERE (OrderModel.user_id==50) // UNION // SELECT UserModel.user_name, // OrderModel.order_id // FROM UserModel // JOIN OrderModel // ON UserModel.user_id=OrderModel.user_id // LEFT JOIN SellerModel // ON SellerModel.seller_id=OrderModel.seller_id // WHERE (UserModel.user_id>=65) // LIMIT 4; // decltype (result5) == std::list, ..>> // result5 = [("Item 0", 50), // ("Item 1", 50), // ("July_65", 31), // ("July_65", 32)] ``` ## Wrapping Up This little ORM is very cool, enjoy it. 😇 See the **[Full Document Here](orm-lite.md)**. ================================================ FILE: docs/orm-lite.md ================================================ # ORM Lite ## Requirements - **C++ 14** Support - MSVC >= 14 (VS 2015 Update 3) - gcc >= 5.4 - Clang >= 3.8 - **SQLite 3** Dependency ## Mapping | SQL Concepts | C++ Concepts | Notes | |---------------------|--------------|-------| | Table / Relation | Object | Deduced by `ORMAP` | | Tuple (in `SELECT`) | `std::tuple` | Deduced by `Join` / `Select` | | Column | Field | Decuced by `Field` | ## `BOT_ORM` ``` cpp #include "ORMLite.h" using namespace BOT_ORM; using namespace BOT_ORM::Expression; ``` Macro `ORMAP` in `ORMLite.h` - `ORMAP (TableName, PrimaryKey, ...);` Modules under `namespace BOT_ORM` - `BOT_ORM::Nullable` - `BOT_ORM::ORMapper` - `BOT_ORM::Queryable` - `BOT_ORM::FieldExtractor` - `BOT_ORM::Constraint` Modules under `namespace BOT_ORM::Expression` - `BOT_ORM::Expression::Selectable` - `BOT_ORM::Expression::Field` - `BOT_ORM::Expression::NullableField` - `BOT_ORM::Expression::Aggregate` - `BOT_ORM::Expression::Expr` - `BOT_ORM::Expression::SetExpr` - `BOT_ORM::Expression::Count ()` - `BOT_ORM::Expression::Sum ()` - `BOT_ORM::Expression::Avg ()` - `BOT_ORM::Expression::Max ()` - `BOT_ORM::Expression::Min ()` Static Modules under `class BOT_ORM::Constraint` - `BOT_ORM::Constraint::CompositeField` - `BOT_ORM::Constraint::Default` - `BOT_ORM::Constraint::Check` - `BOT_ORM::Constraint::Unique` - `BOT_ORM::Constraint::Reference` ## `ORMAP (TableName, PrimaryKey, ...)` Before we use ORM Lite, we should **Inject** some code into the Class; ``` cpp struct MyClass { int field1; double field2; std::string field3; Nullable field4; Nullable field5; Nullable field6; // Inject ORM-Lite into this Class :-) ORMAP ("TableName", field1, field2, field3, field4, field5, field6); }; ``` In this sample, `ORMAP ("TableName", field1, ...)` specifies that: - Class `MyClass` will be mapped into Table `TableName`; - `field1, field2, field3, field4, field5, field6` will be mapped into `INTEGER field1 NOT NULL`, `REAL field2 NOT NULL`, `TEXT field3 NOT NULL`, `INTEGER field4`, `REAL field5` and `TEXT field6` respectively; - The first entry `field1` will be set as the **Primary Key** of the Table; Note that: - You should Pass **at least 2** Params into this Macro (TableName and PrimaryKey), otherwise it will Not Compile...; - Currently Only Support - T such that `std::is_integral::value == true` and **NOT** `char` or `*char_t` - T such that `std::is_floating_point::value == true` - T such that `std::is_same::value == true` - which are mapped as `INTEGER`, `REAL` and `TEXT` (SQLite3); - Not `Nullable` members will be mapped as `NOT NULL`; - The **Primary Key** (first entry) is **Recommended** to be `Integral`, and it could be regarded as the `ROWID` with a Better Query **Performance** :-) (otherwise SQLite 3 will Generate a Column for `ROWID` Implicitly) - Field Names MUST **NOT** be SQL Keywords (SQL Constraint); - `std::string` Value MUST **NOT** contain `\0` (SQL Constraint); - `std::string` Value can be **utf-8** to support **Locale**; - `ORMAP (...)` will **auto** Inject some **private members**; - `__Accept ()` to Implement **Visitor Pattern**; - `__Tuple ()` to **Flatten** data to tuple; - `__FieldNames ()` and `__TableName` to store strings; - and the Access by Implementation; ## `BOT_ORM::Nullable` It keeps the *Similar Semantic* of `Nullable` as `C#`; and [Reference Here](https://stackoverflow.com/questions/2537942/nullable-values-in-c/28811646#28811646) ### Construction & Assignment - Default Constructed / `nullptr` Constructed / `nullptr` Assigned object is `NULL` Valued; - Value Constructed / Value Assigned object is `NOT NULL` Valued - `Nullable` Objects are **Copyable** / **Movable**, and the *Destination* Value has the *Same Value* as the *Source* ``` cpp // Default or Null Construction Nullable (); Nullable (nullptr_t); // Null Assignment const Nullable & operator= (nullptr_t); // Value Construction Nullable (const T &value); // Value Assignment const Nullable & operator= (const T &value); ``` ### Get Value Return the `Underlying Value` of the object or `Default Not-null Value` of `T`; (similar to `GetValueOrDefault` in C#) ``` cpp const T &Value (); const ``` ### Comparison Two Objects have the Same value only if their `Nullable` Construction: - Both are `NULL`; - Both are `NOT NULL` and have the Same `Underlying Value`; ``` cpp bool operator==(const Nullable &op1, const Nullable &op2); bool operator==(const Nullable &op1, T &op2); bool operator==(T &op1, const Nullable &op2); bool operator==(const Nullable &op1, nullptr_t); bool operator==(nullptr_t, const Nullable &op2); ``` ## `BOT_ORM::ORMapper` ### Connection ``` cpp ORMapper (const string &connectionString); ``` Remarks: - Construct a **O/R Mapper** to connect to `connectionString`; - For SQLite, the `connectionString` is the **database name**; - The `ORMapper` **Keeps** the **Connection** and **Shares** the **Connection** with `Queryable`; - **Disconnecting** at all related `ORMapper`/`Queryable` destructied; ### Transaction ``` cpp void Transaction (Fn fn); ``` Remarks: - Invoke `fn` **Transactionally**, as following: ``` cpp try { _connector.Execute ("begin transaction;"); fn (); _connector.Execute ("commit transaction;"); } catch (...) { _connector.Execute ("rollback transaction;"); throw; } ``` ### Create and Drop Table ``` cpp // Create Table void CreateTbl (const MyClass &); void CreateTbl (const MyClass &, const Constraint &constraint1, const Constraint &constraint2, ...); // Drop Table void DropTbl (const MyClass &); ``` Remarks: - Create/Drop Table for class `MyClass`; - `void CreateTbl (const MyClass &, ...);` will Create a Table with **Constraints**; - `Constraint` will be described later; SQL: ``` sql CREATE TABLE MyClass (...); DROP TABLE MyClass; ``` ### Insert ``` cpp // Insert a single value void Insert (const MyClass &entity, bool withId = true); // Insert values void InsertRange (const Container &entities, bool withId = true); ``` Remarks: - Insert `entity` / `entities` into Table for `MyClass`; - If `withId` is `false`, it will insert the `entity` without **Primary Key**; - Note that: **Primary Key** is recommended to be **Integral** in this case (**INT PK** would be `AUTOINCREMENT`, Floating Point / String may **Failed**); - **NULL** Fields will **NOT** be Set; - `entities` must **SUPPORT** `forward_iterator`; SQL: ``` sql INSERT INTO MyClass (...) VALUES (...); INSERT INTO MyClass (...) VALUES (...); INSERT INTO MyClass (...) VALUES (...); ... ``` ### Update ``` cpp // Update value by Primary Key void Update (const MyClass &entity); // Update values by Primary Key void UpdateRange (const Container &entities); // Update by Expressions void Update (const MyClass &, const Expression::SetExpr &setExpr, const Expression::Expr &whereExpr); ``` Remarks: - Update `entity` / `entities` in Table `MyClass` with the Same **Primary Key**; - Update Set `setExpr` Where `whereExpr` for Table `MyClass` (`Expressions` will be described later); - **NULL** Fields will also be Set; - `entities` must **SUPPORT** `forward_iterator`; SQL: ``` sql UPDATE MyClass SET (...) WHERE KEY = ; UPDATE MyClass SET (...) WHERE KEY = ; UPDATE MyClass SET (...) WHERE KEY = ; ... UPDATE MyClass SET (...) WHERE ...; ``` ### Delete ``` cpp // Delete value by Primary Key void Delete (const MyClass &entity); // Delete by Expressions void Delete (const MyClass &, const Expression::Expr &whereExpr); ``` Remarks: - Delete Entry in Table `MyClass` with the Same **Primary Key**; - Delete Where `whereExpr` for Table `MyClass` (`Expression` will be described later); - This function will **NOT** throw a `std::runtime_error` even if there is nothing to delete; SQL: ``` sql DELETE FROM MyClass WHERE KEY = ; DELETE FROM MyClass WHERE ...; ``` ### Query ``` cpp // Retrieve a Queryable Object Queryable Query (MyClass queryHelper); ``` Remarks: - Return new `Queryable` object with `QueryResult` is `MyClass`; - `MyClass` **MUST** be **Copy Constructible** to Construct a `queryHelper`; - The `ORMapper` **Shares** the **Connection** with `Queryable`; ## `BOT_ORM::Queryable` ### Retrieve Results ``` cpp Nullable Aggregate (const Expression::Aggregate &agg) const; std::vector ToVector () const; std::list ToList () const; ``` Remarks: - `QueryResult` specifies the **Row Type** of Query Result; - `Aggregate` will Get the one-or-zero-row Result for `agg`; - `ToVector` / `ToList` returns the Collection of `QueryResult`; - The results are from the **Connection** of `Queryable`; - If the Result is `null` for `NOT Nullable` Field, it will throw `std::runtime_error`; - `Expression` will be described later; ### Set Conditions ``` cpp Queryable Distinct (bool isDistinct = true) const; Queryable Where (const Expression::Expr &expr) const; Queryable GroupBy (const Expression::Field &field) const; Queryable Having (const Expression::Expr &expr) const; Queryable OrderBy (const Expression::Field &field) const; Queryable OrderByDescending (const Expression::Field &field) const; Queryable Take (size_t count) const; Queryable Skip (size_t count) const; ``` Remarks: - Default Selection is `ALL`; - These functions will Set/Append Conditions to a copy of `this`; - `OrderBy` will **Append** `field` to Condition, while Other functions will **Set** `DISTINCT`, `expr`, `field` or `count` to Condition; - New `Queryable` **Shares** the **Connection** of `this`; - `Expression` will be described later; ### Construct New `QueryResult` ``` cpp auto Select (const Expression::Selectable &target1, const Expression::Selectable &target2, ...) const; auto Join (const MyClass2 &queryHelper2, const Expression::Expr &onExpr) const; auto LeftJoin (const MyClass2 &queryHelper2, const Expression::Expr &onExpr) const; ``` Remarks: - Default Selection is **All Columns** (`SELECT *`); - `Select` will Set `QueryResult` to `std::tuple`, which can be retrieved by `SELECT target1, target2, ...` (`target` can be **Field** or **Aggregate Functions**); - `Join` / `LeftJoin` will Set `QueryResult` to `std::tuple<...>` - `...` is the **flattened nullable concatenation** of all entries of **Previous** `QueryResult` and `queryHelper2`; - **Flatten** and **Nullable** means all fields of `...` are all `Nullable`, where `T` is the Supported Types of `ORMAP` (NOT `std::tuple` or `MyClass`); - `onExpr` specifies the `ON` Expression for `JOIN`; - All Functions will copy the **Conditions** of `this` to the new one; - New `Queryable` **Shares** the **Connection** of `this`; - `Expression` will be described later; ### Compound Select ``` cpp Queryable Union (const Queryable &queryable) const; Queryable UnionAll (const Queryable &queryable) const; Queryable Intersect (const Queryable &queryable) const; Queryable Expect (const Queryable &queryable) const; ``` Remarks: - All Functions will return a Compound Select for `this` and `queryable`; - All Functions will only **Inherit Conditions** `OrderBy` and `Limit` from `this`; - New `Queryable` **Shares** the **Connection** of `this`; ### Query SQL We will use the following SQL to Query: ``` sql SELECT [DISTINCT] ... FROM TABLE [LEFT] JOIN TABLE ON ... ... WHERE ... GROUP BY HAVING ... [ SELECT ... FROM ...] ORDER BY [DESC], ... LIMIT OFFSET ; ``` Remarks: - If the Corresponding Condition is NOT Set, it will be omitted; ## `BOT_ORM::FieldExtractor` ``` cpp // Construction FieldExtractor (const MyClass1 &queryHelper1, const MyClass2 &queryHelper2, ...); // Get Field<> by operator () Field operator () (const T &field) const; NullableField operator () (const Nullable &field) const; ``` Remarks: - Construction of `FieldExtractor` will take all fields' pointers of `queryHelper` into a **Hash Table**; - `operator () (field)` will find the position of `field` in the **Hash Table** from `queryHelper` and Construct the corresponding `Field`; - If the `field` is `Nullable` it will Construct a `NullableField`; and it will Construct a `Field` otherwise; - If `field` is not a member of `queryHelper`, it will throw `std::runtime_error`; - `Expression` will be described later; ## `namespace BOT_ORM::Expression` ### Fields and Aggregate Functions #### Definitions ``` cpp BOT_ORM::Expression::Selectable BOT_ORM::Expression::Field : public Selectable BOT_ORM::Expression::NullableField : public Field BOT_ORM::Expression::Aggregate : public Selectable ``` #### Operations ``` cpp // Field / Aggregate ? Value Expr operator == (const Selectable &op, T value); Expr operator != (const Selectable &op, T value); Expr operator > (const Selectable &op, T value); Expr operator >= (const Selectable &op, T value); Expr operator < (const Selectable &op, T value); Expr operator <= (const Selectable &op, T value); // Field ? Field Expr operator == (const Field &op1, const Field &op2); Expr operator != (const Field &op1, const Field &op2); Expr operator > (const Field &op1, const Field &op2); Expr operator >= (const Field &op1, const Field &op2); Expr operator < (const Field &op1, const Field &op2); Expr operator <= (const Field &op1, const Field &op2); // Nullable Field ? nullptr Expr operator == (const NullableField &op, nullptr_t); Expr operator !== (const NullableField &op, nullptr_t); // String Field ? std::string Expr operator& (const Field &op, std::string val); Expr operator| (const Field &op, std::string val); // Get SetExpr SetExpr operator = (const Field &op, T value); SetExpr operator = (const NullableField &op, nullptr_t); ``` Remarks: - `Selectable ? T` returns `Expr`; - `Field ? Field` returns `Expr`; - `NullableField == / != nullptr` returns `Expr IS NULL / IS NOT NULL`; - `Field & / | T` returns `Expr LIKE / NOT LIKE `; - `Field = T` returns `SetExpr = `; - `NullableField = nullptr` returns `SetExpr = null`; ### Expressions #### Definitions ``` cpp BOT_ORM::Expression::Expr BOT_ORM::Expression::SetExpr ``` #### Operations ``` cpp // Get Composite Expr Expr operator && (const Expr &op1, const Expr &op2); Expr operator || (const Expr &op1, const Expr &op2); // Concatenate 2 SetExpr SetExpr operator && (const SetExpr &op1, const SetExpr &op2); ``` Remarks: - `Expr && / || Expr` returns `( and / or )`; - `SetExpr && SetExpr` returns `, `; ### Aggregate Function Helpers ``` cpp BOT_ORM::Expression::Count (); BOT_ORM::Expression::Count (const Field &field); BOT_ORM::Expression::Sum (const Field &field); BOT_ORM::Expression::Avg (const Field &field); BOT_ORM::Expression::Max (const Field &field); BOT_ORM::Expression::Min (const Field &field); ``` Remarks: They will Generate Aggregate Functions as: - `size_t COUNT (*)` - `size_t COUNT (field)` - `T SUM (field)` - `T AVG (field)` - `T MAX (field)` - `T MIN (field)` ## `class BOT_ORM::Constraint` ### Composite Field `CompositeField` could be constructed from normal `Field` and is used by **Constraint Functions**; ``` cpp CompositeField (const Expression::Field &field1, const Expression::Field &field2, ...); ``` Remarks: - Composite Field will Contain fields in the given **Order**; - If the `field`s are not from the Same Table, it will throw `std::runtime_error`; ### Generate Constraints ``` cpp Constraint Default (const Expression::Field &field, const T &value); Constraint Check (const Expression::Expr &expr); Constraint Unique (const Expression::Field &field); Constraint Unique (const CompositeField &fields); Constraint Reference(const Expression::Field &field, const Expression::Field &refered); Constraint Reference (const CompositeField &field, const CompositeField &refered); ``` Remarks: - They will Generate Constraints as: `DEFAULT`, `CHECK`, `UNIQUE` and `FOREIGN KEY`; - `FOREIGN KEY` is Enabled by Default (during the Construction of `ORMapper`); - Why there is NO **Composite Primary Key** Constaints: - It's recommended to use a **Integral** Field as the Primary Key, described in Section ## Macro `ORMAP`; - We can use `Unique` to implement a **Composite Unique** Constraint; - And use `Reference` to define a **Composite Foreign Key**; ## Error Handling ### Compile-time Error ORM Lite uses `static_assert` to Check if the Code is valid: - **Forget** to **Place** `ORMAP` into the Class > Please Inject the Class with 'ORMAP' first - Place **Unsupported Types** into `ORMAP` > Only Support Integral, Floating Point and std::string Note that: Error Messages will often appear at the **TOP**; ### Runtime Error All Functions will throw `std::runtime_error` with the **Error Message** if Failed: - Failed to **Connect** to **Database** > SQL error: Can't open database `` - Failed at Executing **Query** Script > SQL error: `` at `` - Query Result's **Column Count** does **NOT Match** the Expected Count (happening in **NOT** *Code First* Cases...) > SQL error: Bad Column Count at `` - Get `NULL` from Query while the Expected **Field** is **NOT NULL** (happening in **NOT** *Code First* Cases...) > SQL error: Get Null Value at `` - Pass a **Non-Member** Var of Registered Object to Field **Extractor** > No Such Field for current Extractor - **Composite** Fields from **NOT** the Same Tables > Fields are NOT from the Same Table ================================================ FILE: sample/makefile ================================================ TARGET = sample SOURCES = sample.cpp ../src/sqlite3.c WARNINGFLAGS = -Wall -W CPPFLAGS = -std=c++14 LINKS = -lstdc++ -lpthread -ldl OBJS = $(patsubst %.c,%.o,$(patsubst %.cpp,%.o,$(SOURCES))) %.o: %.c $(CC) -c $< -o $@ $(WARNINGFLAGS) $(LINKS) %.o: %.cpp $(CC) -c $< -o $@ $(WARNINGFLAGS) $(LINKS) $(CPPFLAGS) $(TARGET): $(OBJS) $(CC) $(OBJS) -o $(TARGET) $(LINKS) clean: rm -rf $(OBJS) $(TARGET) *.db ================================================ FILE: sample/sample.cpp ================================================ // Sample of ORM Lite // https://github.com/BOT-Man-JL/ORM-Lite // BOT Man, 2016 #include #include #include #include "../src/ormlite.h" using namespace BOT_ORM; using namespace BOT_ORM::Expression; namespace PrintHelper { template void PrintNullable (const BOT_ORM::Nullable &val) { if (val == nullptr) std::cout << "null"; else std::cout << val.Value (); } template struct TuplePrinter { static void print (const Tuple& t) { TuplePrinter::print (t); std::cout << ", "; PrintNullable (std::get (t)); } }; template struct TuplePrinter { static void print (const Tuple& t) { PrintNullable (std::get<0> (t)); } }; template void PrintTuple (const std::tuple& t) { std::cout << "("; TuplePrinter::print (t); std::cout << ")\n"; } template void PrintTuples (const Container &vals) { for (const auto &val : vals) PrintTuple (val); std::cout << std::endl; } } struct UserModel { int user_id; std::string user_name; double credit_count; Nullable age; Nullable salary; Nullable title; // Inject ORM-Lite into this Class :-) ORMAP ("UserModel", user_id, user_name, credit_count, age, salary, title); }; struct SellerModel { int seller_id; std::string seller_name; double credit_count; // Inject ORM-Lite into this Class :-) ORMAP ("SellerModel", seller_id, seller_name, credit_count); }; struct OrderModel { int order_id; int user_id; int seller_id; std::string product_name; Nullable fee; // Inject ORM-Lite into this Class :-) ORMAP ("OrderModel", order_id, user_id, seller_id, product_name, fee); }; int main () { /* ## Field Extracting */ // Define more Query Helper Objects and their Field Extractor UserModel user; SellerModel seller; OrderModel order; auto field = FieldExtractor { user, seller, order }; // Extract Field from 'field' // For example: field (user.user_name) // => Retrieve the field of user_name in UserModel table /* ## Working on *Database* with *ORMapper* */ // Open a Connection with 'sample.db' ORMapper mapper ("sample.db"); /* ## Create or Drop Tables */ try { // Drop Tables mapper.DropTbl (OrderModel {}); mapper.DropTbl (UserModel {}); mapper.DropTbl (SellerModel {}); } catch (...) {} // Create Table with Constraints mapper.CreateTbl ( UserModel {}, Constraint::Default (field (user.salary), 1000.0)); // Remarks: // CREATE TABLE UserModel( // user_id INTEGER NOT NULL PRIMARY KEY, // user_name TEXT NOT NULL, // credit_count REAL NOT NULL, // age INTEGER, // salary REAL DEFAULT 1000, // title TEXT); mapper.CreateTbl ( SellerModel {}, Constraint::Check (field (seller.credit_count) > 0.0)); // Remarks: // CREATE TABLE SellerModel( // seller_id INTEGER NOT NULL PRIMARY KEY, // seller_name TEXT NOT NULL, // credit_count REAL NOT NULL, // CHECK (credit_count > 0)); mapper.CreateTbl ( OrderModel {}, Constraint::Reference ( field (order.user_id), field (user.user_id)), Constraint::Reference ( field (order.seller_id), field (seller.seller_id)), Constraint::Unique (Constraint::CompositeField { field (order.product_name), field (order.fee) })); // Remarks: // CREATE TABLE OrderModel( // order_id INTEGER NOT NULL PRIMARY KEY, // user_id INTEGER NOT NULL, // seller_id INTEGER NOT NULL, // product_name TEXT NOT NULL, // fee REAL, // FOREIGN KEY (user_id) REFERENCES UserModel(user_id), // FOREIGN KEY (seller_id) REFERENCES SellerModel(seller_id), // UNIQUE (product_name, fee)); /* ## Basic CRUD */ std::vector initObjs = { { 0, "John", 0.2, 21, nullptr, nullptr }, { 1, "Jack", 0.4, nullptr, 3.14, nullptr }, { 2, "Jess", 0.6, nullptr, nullptr, std::string ("Dr.") } }; // Insert Values with Primary Key for (const auto &obj : initObjs) mapper.Insert (obj); initObjs[1].salary = nullptr; initObjs[1].title = "St."; // Update Entity by Primary Key (WHERE UserModel.id = 1) mapper.Update (initObjs[1]); // Delete Entity by Primary Key (WHERE UserModel.id = 2) mapper.Delete (initObjs[2]); // Transactional Statements try { mapper.Transaction ([&] () { mapper.Delete (initObjs[0]); // OK mapper.Insert (UserModel { 1, "Joke", 0, nullptr, nullptr, nullptr }); // Failed }); } catch (const std::exception &ex) { // If any statement Failed, throw an exception std::cout << ex.what () << std::endl; // SQL error: 'UNIQUE constraint failed: UserModel.id' // Remarks: // mapper.Delete (initObjs[0]); will not applied :-) } // Select All to List auto result1 = mapper.Query (UserModel {}).ToList (); // decltype (result1) == std::list // result1 = [{ 0, 0.2, "John", 21, 1000, null }, // { 1, 0.4, "Jack", null, null, "St." }] // Table Constraints try { // Insert Values without Primary Key mapper.Insert (SellerModel { 0, "John Inc.", 0.0 }, false); } catch (const std::exception &ex) { std::cout << ex.what () << std::endl; // SQL error: 'CHECK constraint failed: SellerModel' } /* ## Batch Operations */ std::vector dataToSeed; for (int i = 50; i < 100; i++) dataToSeed.emplace_back (UserModel { i, "July_" + std::to_string (i), i * 0.2, nullptr, nullptr, nullptr }); // Insert by Batch Insert mapper.Transaction ([&] () { mapper.InsertRange (dataToSeed); }); for (size_t i = 0; i < 20; i++) { dataToSeed[i + 30].age = 30 + (int) i / 2; dataToSeed[i + 20].title = "Mr. " + std::to_string (i); } // Update by Batch Update mapper.Transaction ([&] () { // Note that: it will erase the default value of 'salary' mapper.UpdateRange (dataToSeed); }); /* ## Single-Table Query */ // Select by Condition auto result2 = mapper.Query (UserModel {}) .Where ( field (user.user_name) & std::string ("July%") && (field (user.age) >= 32 && field (user.title) != nullptr) ) .OrderByDescending (field (user.age)) .OrderBy (field (user.user_id)) .Take (3) .Skip (1) .ToVector (); // Remarks: // SELECT * FROM UserModel // WHERE (user_name LIKE 'July%' AND // (age >= 32 AND title IS NOT NULL)) // ORDER BY age DESC, user_id // LIMIT 3 OFFSET 1 // decltype (result2) == std::vector // result2 = [{ 89, 17.8, "July_89", 34, null, "Mr. 19" }, // { 86, 17.2, "July_86", 33, null, "Mr. 16" }, // { 87, 17.4, "July_87", 33, null, "Mr. 17" }] // Calculate Aggregate Function auto avg = mapper.Query (UserModel {}) .Where (field (user.user_name) & std::string ("July%")) .Aggregate (Avg (field (user.credit_count))); // Remarks: // SELECT AVG (credit_count) FROM UserModel // WHERE (user_name LIKE 'July%') // avg = 14.9 auto count = mapper.Query (UserModel {}) .Where (field (user.user_name) | std::string ("July%")) .Aggregate (Count ()); // Remarks: // SELECT COUNT (*) FROM UserModel // WHERE (user_name NOT LIKE 'July%') // count = 2 /* ## Update / Delete by Statement */ // Update by Condition mapper.Update ( UserModel {}, (field (user.age) = 10) && (field (user.credit_count) = 1.0), field (user.user_name) == std::string ("July")); // Remarks: // UPDATE UserModel SET age = 10,credit_count = 1.0 // WHERE (user_name = 'July') // Delete by Condition mapper.Delete (UserModel {}, field (user.user_id) >= 90); // Remarks: // DELETE FROM UserModel WHERE (id >= 90) /* ## Multi-Table Query */ mapper.Transaction ([&] () { for (size_t i = 0; i < 50; i++) { mapper.Insert ( SellerModel { (int) i + 50, "Seller " + std::to_string (i), 3.14 }); mapper.Insert ( OrderModel { 0, (int) i / 2 + 50, (int) i / 4 + 50, "Item " + std::to_string (i), i * 0.5 }, false); } }); // Join Tables for Query auto joinedQuery = mapper.Query (UserModel {}) .Join (OrderModel {}, field (user.user_id) == field (order.user_id)) .LeftJoin (SellerModel {}, field (seller.seller_id) == field (order.seller_id)) .Where (field (user.user_id) >= 65); // Get Result to List auto result3 = joinedQuery.ToList (); // Remarks: // SELECT * FROM UserModel // JOIN OrderModel // ON UserModel.user_id=OrderModel.user_id // LEFT JOIN SellerModel // ON SellerModel.seller_id=OrderModel.seller_id // WHERE (UserModel.user_id>=65) // decltype (result3) == std::list, ..>> // result3 = [(65, "July_65", 13, null, null, null, // 31, 65, 57, "Item 30", 15, // 57, "Seller 7", 3.14), // (65, "July_65", 13, null, null, null, // 32, 65, 57, "Item 31", 15.5, // 57, "Seller 7", 3.14), // ... ] // Group & Having ~ auto result4 = joinedQuery .Select (field (order.user_id), field (user.user_name), Avg (field (order.fee))) .GroupBy (field (user.user_name)) .Having (Sum (field (order.fee)) >= 40.5) .Skip (3) .ToList (); // Remarks: // SELECT OrderModel.user_id, // UserModel.user_name, // AVG (OrderModel.fee) // FROM UserModel // JOIN OrderModel // ON UserModel.user_id=OrderModel.user_id // LEFT JOIN SellerModel // ON SellerModel.seller_id=OrderModel.seller_id // WHERE (UserModel.user_id>=65) // GROUP BY UserModel.user_name // HAVING SUM (OrderModel.fee)>=40.5 // LIMIT ~0 OFFSET 3 // decltype (result4) == std::list, ..>> // result4 = [(73, "July_73", 23.25), // (74, "July_74", 24.25)] // Compound Select // Results are Nullable-Tuples auto result5 = mapper.Query (OrderModel {}) .Select (field (order.product_name), field (order.user_id)) .Where (field (order.user_id) == 50) .Union ( joinedQuery .Select (field (user.user_name), field (order.order_id)) ) .Take (4) .ToList (); // Remarks: // SELECT OrderModel.product_name, // OrderModel.user_id // FROM OrderModel // WHERE (OrderModel.user_id==50) // UNION // SELECT UserModel.user_name, // OrderModel.order_id // FROM UserModel // JOIN OrderModel // ON UserModel.user_id=OrderModel.user_id // LEFT JOIN SellerModel // ON SellerModel.seller_id=OrderModel.seller_id // WHERE (UserModel.user_id>=65) // LIMIT 4; // decltype (result5) == std::list, ..>> // result5 = [("Item 0", 50), // ("Item 1", 50), // ("July_65", 31), // ("July_65", 32)] // ==================================================== // Output UserModel Objects auto printUserModeles = [] (const auto &objs) { for (const auto& item : objs) { std::cout << item.user_id << "\t" << item.credit_count << "\t" << item.user_name << "\t"; PrintHelper::PrintNullable (item.age); std::cout << "\t"; PrintHelper::PrintNullable (item.salary); std::cout << "\t"; PrintHelper::PrintNullable (item.title); std::cout << "\n"; } std::cout << std::endl; }; // Sec 1 std::cout << "\n"; printUserModeles (result1); // Sec 2 printUserModeles (result2); PrintHelper::PrintNullable (count); std::cout << "\n"; PrintHelper::PrintNullable (avg); std::cout << "\n" << std::endl; // Sec 3 PrintHelper::PrintTuples (result3); PrintHelper::PrintTuples (result4); PrintHelper::PrintTuples (result5); //std::cin.get (); return 0; } ================================================ FILE: sample/sample.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 1781A76A1F5FF9EE008E6067 /* sample.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1781A7691F5FF9EE008E6067 /* sample.cpp */; }; 1781A76F1F5FF9FC008E6067 /* sqlite3.c in Sources */ = {isa = PBXBuildFile; fileRef = 1781A76D1F5FF9FC008E6067 /* sqlite3.c */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ 1781A75D1F5FF992008E6067 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = /usr/share/man/man1/; dstSubfolderSpec = 0; files = ( ); runOnlyForDeploymentPostprocessing = 1; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 1781A75F1F5FF992008E6067 /* sample */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = sample; sourceTree = BUILT_PRODUCTS_DIR; }; 1781A7691F5FF9EE008E6067 /* sample.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = sample.cpp; path = sample.cpp; sourceTree = ""; }; 1781A76B1F5FF9FC008E6067 /* nullable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = nullable.h; path = ../src/nullable.h; sourceTree = ""; }; 1781A76C1F5FF9FC008E6067 /* ormlite.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ormlite.h; path = ../src/ormlite.h; sourceTree = ""; }; 1781A76D1F5FF9FC008E6067 /* sqlite3.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = sqlite3.c; path = ../src/sqlite3.c; sourceTree = ""; }; 1781A76E1F5FF9FC008E6067 /* sqlite3.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = sqlite3.h; path = ../src/sqlite3.h; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 1781A75C1F5FF992008E6067 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 1781A7561F5FF992008E6067 = { isa = PBXGroup; children = ( 1781A76B1F5FF9FC008E6067 /* nullable.h */, 1781A76C1F5FF9FC008E6067 /* ormlite.h */, 1781A76D1F5FF9FC008E6067 /* sqlite3.c */, 1781A76E1F5FF9FC008E6067 /* sqlite3.h */, 1781A7691F5FF9EE008E6067 /* sample.cpp */, 1781A7601F5FF992008E6067 /* Products */, ); sourceTree = ""; }; 1781A7601F5FF992008E6067 /* Products */ = { isa = PBXGroup; children = ( 1781A75F1F5FF992008E6067 /* sample */, ); name = Products; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 1781A75E1F5FF992008E6067 /* sample */ = { isa = PBXNativeTarget; buildConfigurationList = 1781A7661F5FF992008E6067 /* Build configuration list for PBXNativeTarget "sample" */; buildPhases = ( 1781A75B1F5FF992008E6067 /* Sources */, 1781A75C1F5FF992008E6067 /* Frameworks */, 1781A75D1F5FF992008E6067 /* CopyFiles */, ); buildRules = ( ); dependencies = ( ); name = sample; productName = sample; productReference = 1781A75F1F5FF992008E6067 /* sample */; productType = "com.apple.product-type.tool"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 1781A7571F5FF992008E6067 /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 0830; TargetAttributes = { 1781A75E1F5FF992008E6067 = { CreatedOnToolsVersion = 8.3.3; ProvisioningStyle = Automatic; }; }; }; buildConfigurationList = 1781A75A1F5FF992008E6067 /* Build configuration list for PBXProject "sample" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( en, ); mainGroup = 1781A7561F5FF992008E6067; productRefGroup = 1781A7601F5FF992008E6067 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 1781A75E1F5FF992008E6067 /* sample */, ); }; /* End PBXProject section */ /* Begin PBXSourcesBuildPhase section */ 1781A75B1F5FF992008E6067 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 1781A76A1F5FF9EE008E6067 /* sample.cpp in Sources */, 1781A76F1F5FF9FC008E6067 /* sqlite3.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 1781A7641F5FF992008E6067 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.12; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; }; name = Debug; }; 1781A7651F5FF992008E6067 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.12; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; }; name = Release; }; 1781A7671F5FF992008E6067 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "c++14"; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 1781A7681F5FF992008E6067 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "c++14"; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 1781A75A1F5FF992008E6067 /* Build configuration list for PBXProject "sample" */ = { isa = XCConfigurationList; buildConfigurations = ( 1781A7641F5FF992008E6067 /* Debug */, 1781A7651F5FF992008E6067 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 1781A7661F5FF992008E6067 /* Build configuration list for PBXNativeTarget "sample" */ = { isa = XCConfigurationList; buildConfigurations = ( 1781A7671F5FF992008E6067 /* Debug */, 1781A7681F5FF992008E6067 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 1781A7571F5FF992008E6067 /* Project object */; } ================================================ FILE: sample/sample.vcxproj ================================================  Debug Win32 Release Win32 Debug x64 Release x64 {AC415D81-EA1D-43F3-A741-60ED85136D48} Win32Proj sample Application true v141 Unicode Application false v141 true Unicode Application true v141 Unicode Application false v141 true Unicode true $(SolutionDir)$(Configuration)\sample\ $(Configuration)\sample\ true $(Platform)\$(Configuration)\sample\ $(SolutionDir)$(Platform)\$(Configuration)\sample\ false false Level3 Disabled WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) /std:c++14 %(AdditionalOptions) Console true Level3 Disabled _DEBUG;_CONSOLE;%(PreprocessorDefinitions) Console true Level3 MaxSpeed true true WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) Console true true true Level3 MaxSpeed true true NDEBUG;_CONSOLE;%(PreprocessorDefinitions) Console true true true ================================================ FILE: src/nullable.h ================================================ // Nullable module for ORM Lite // ORM Lite - An ORM for SQLite in C++ 17 // https://github.com/BOT-Man-JL/ORM-Lite // BOT Man, 2017 #ifndef BOT_ORM_NULLABLE_H #define BOT_ORM_NULLABLE_H // std::nullptr_t #include // Nullable Template // https://stackoverflow.com/questions/2537942/nullable-values-in-c/28811646#28811646 namespace BOT_ORM { template class Nullable { template friend bool operator== (const Nullable &op1, const Nullable &op2); template friend bool operator== (const Nullable &op, const T2 &value); template friend bool operator== (const T2 &value, const Nullable &op); template friend bool operator== (const Nullable &op, std::nullptr_t); template friend bool operator== (std::nullptr_t, const Nullable &op); public: // Default or Null Construction Nullable () : m_hasValue (false), m_value (T ()) {} Nullable (std::nullptr_t) : Nullable () {} // Null Assignment const Nullable & operator= (std::nullptr_t) { m_hasValue = false; m_value = T (); return *this; } // Value Construction template Nullable (const T2 &value) : m_hasValue (true), m_value (value) {} // Value Assignment template const Nullable & operator= (const T2 &value) { m_hasValue = true; m_value = value; return *this; } private: bool m_hasValue; T m_value; public: const T &Value () const { return m_value; } }; // == varialbe template inline bool operator== (const Nullable &op1, const Nullable &op2) { return op1.m_hasValue == op2.m_hasValue && (!op1.m_hasValue || op1.m_value == op2.m_value); } // == value template inline bool operator== (const Nullable &op, const T2 &value) { return op.m_hasValue && op.m_value == value; } template inline bool operator== (const T2 &value, const Nullable &op) { return op.m_hasValue && op.m_value == value; } // == nullptr template inline bool operator== (const Nullable &op, std::nullptr_t) { return !op.m_hasValue; } template inline bool operator== (std::nullptr_t, const Nullable &op) { return !op.m_hasValue; } } #endif // !BOT_ORM_NULLABLE_H ================================================ FILE: src/ormlite.h ================================================ // ORM Lite // An ORM for SQLite in C++ 14 // https://github.com/BOT-Man-JL/ORM-Lite // BOT Man, 2017 #ifndef BOT_ORM_H #define BOT_ORM_H // Container #include #include #include #include #include // Serialization #include // Type Traits #include // std::shared_ptr #include // for Field Name Extractor #include // for SQL Connector #include #include // SQLite 3 Dependency #include "sqlite3.h" // Nullable Module #include "nullable.h" // Public Macro #define ORMAP(_TABLE_NAME_, ...) \ private: \ friend class BOT_ORM_Impl::InjectionHelper; \ template \ inline decltype (auto) __Accept (FN fn) \ { return fn (__VA_ARGS__); } \ template \ inline decltype (auto) __Accept (FN fn) const \ { return fn (__VA_ARGS__); } \ constexpr static const char *__FieldNames = #__VA_ARGS__; \ constexpr static const char *__TableName = _TABLE_NAME_; \ // Private Macros #define NO_ORMAP "Please Inject the Class with 'ORMAP' first" #define BAD_TYPE "Only Support Integral, Floating Point and std::string" #define BAD_COLUMN_COUNT "Bad Column Count" #define NULL_DESERIALIZE "Get Null Value" #define NO_FIELD "No Such Field for current Extractor" #define NOT_SAME_TABLE "Fields are NOT from the Same Table" // Helpers namespace BOT_ORM_Impl { // Naive SQL Driver (Todo: Improved Later) class SQLConnector { public: SQLConnector (const std::string &fileName) { if (sqlite3_open (fileName.c_str (), &db) != SQLITE_OK) throw std::runtime_error ( std::string ("SQL error: Can't open database '") + sqlite3_errmsg (db) + "'"); } ~SQLConnector () { sqlite3_close (db); } void Execute (const std::string &cmd) { char *zErrMsg = 0; int rc = SQLITE_OK; for (size_t iTry = 0; iTry < MAX_TRIAL; iTry++) { rc = sqlite3_exec (db, cmd.c_str (), 0, 0, &zErrMsg); if (rc != SQLITE_BUSY) break; std::this_thread::sleep_for ( std::chrono::microseconds (20)); } if (rc != SQLITE_OK) { auto errStr = std::string ("SQL error: '") + zErrMsg + "' at '" + cmd + "'"; sqlite3_free (zErrMsg); throw std::runtime_error (errStr); } } void ExecuteCallback (const std::string &cmd, std::function callback) { char *zErrMsg = 0; int rc = SQLITE_OK; auto callbackParam = std::make_pair (&callback, std::string {}); for (size_t iTry = 0; iTry < MAX_TRIAL; iTry++) { rc = sqlite3_exec (db, cmd.c_str (), CallbackWrapper, &callbackParam, &zErrMsg); if (rc != SQLITE_BUSY) break; std::this_thread::sleep_for ( std::chrono::microseconds (20)); } if (rc == SQLITE_ABORT) { auto errStr = "SQL error: '" + callbackParam.second + "' at '" + cmd + "'"; sqlite3_free (zErrMsg); throw std::runtime_error (errStr); } else if (rc != SQLITE_OK) { auto errStr = std::string ("SQL error: '") + zErrMsg + "' at '" + cmd + "'"; sqlite3_free (zErrMsg); throw std::runtime_error (errStr); } } private: sqlite3 *db; constexpr static size_t MAX_TRIAL = 16; static int CallbackWrapper ( void *callbackParam, int argc, char **argv, char **) { auto pParam = static_cast *, std::string >*>(callbackParam); try { pParam->first->operator()(argc, argv); return 0; } catch (const std::exception &ex) { pParam->second = ex.what (); return 1; } } }; // Helper - Field Type Checker template struct TypeString { constexpr static const char *typeStr = (std::is_integral::value && !std::is_same::value && !std::is_same::value && !std::is_same::value && !std::is_same::value && !std::is_same::value) ? " integer not null" : (std::is_floating_point::value) ? " real not null" : (std::is_same::value) ? " text not null" : nullptr; static_assert (typeStr != nullptr, BAD_TYPE); }; template struct TypeString > { constexpr static const char *typeStr = (std::is_integral::value && !std::is_same::value && !std::is_same::value && !std::is_same::value && !std::is_same::value && !std::is_same::value) ? " integer" : (std::is_floating_point::value) ? " real" : (std::is_same::value) ? " text" : nullptr; static_assert (typeStr != nullptr, BAD_TYPE); }; // Serialization Helper struct SerializationHelper { template static inline std::enable_if_t::typeStr == nullptr, bool> Serialize (std::ostream &, const T &) {} template static inline std::enable_if_t::typeStr != nullptr, bool> Serialize (std::ostream &os, const T &value) { os << value; return true; } static inline bool Serialize (std::ostream &os, const std::string &value) { os << "'" << value << "'"; return true; } template static inline bool Serialize ( std::ostream &os, const BOT_ORM::Nullable &value) { if (value == nullptr) return false; return Serialize (os, value.Value ()); } }; // Deserialization Helper struct DeserializationHelper { template static inline std::enable_if_t::typeStr == nullptr> Deserialize (T &, const char *) {} template static inline std::enable_if_t::typeStr != nullptr> Deserialize (T &property, const char *value) { if (value) std::istringstream { value } >> property; else throw std::runtime_error (NULL_DESERIALIZE); } static inline void Deserialize (std::string &property, const char *value) { if (value) property = value; else throw std::runtime_error (NULL_DESERIALIZE); } template static inline void Deserialize ( BOT_ORM::Nullable &property, const char *value) { if (value) { T val; Deserialize (val, value); property = val; } else property = nullptr; } }; // Injection Helper class InjectionHelper { static std::vector ExtractFieldName (std::string input) { std::vector ret; std::string tmpStr; for (char ch : std::move (input) + ",") { if (isalnum (ch) || ch == '_') tmpStr += ch; else if (ch == ',') { if (!tmpStr.empty ()) ret.push_back (tmpStr); tmpStr.clear (); } } return ret; }; public: // Checking Injection template class HasInjected { template struct make_void { using type = void; }; template using void_t = typename make_void<_Types...>::type; template > struct Test : std::false_type {}; template struct Test > : std::true_type {}; public: static constexpr bool value = Test::value; static_assert (value, NO_ORMAP); }; // Proxy Function template static inline decltype (auto) Visit (C &obj, Fn fn) { return obj.__Accept (fn); } // Field Name Proxy template static inline const std::vector &FieldNames (const C &) { static const auto fieldNames = ExtractFieldName (C::__FieldNames); return fieldNames; } // Table Name Proxy template static inline const std::string &TableName (const C &) { static const std::string tableName { C::__TableName }; return tableName; } }; // Unpacking Tricks :-) // http://stackoverflow.com/questions/26902633/how-to-iterate-over-a-tuple-in-c-11/26902803#26902803 // - To avoid the unspecified order, // brace-enclosed initializer lists can be used, // which guarantee strict left-to-right order of evaluation. // - To avoid the need for a not void return type, // the comma operator can be used to // always yield 1 in each expansion element. using Expander = int[]; } namespace BOT_ORM { namespace Expression { // SetExpr struct SetExpr { SetExpr (std::string field_op_val) : _expr { std::move (field_op_val) } {} const std::string &ToString () const { return _expr; } inline SetExpr operator && (const SetExpr &rhs) const { return SetExpr { _expr + "," + rhs._expr }; } private: std::string _expr; }; // Selectable template struct Selectable { std::string fieldName; const std::string *tableName; Selectable (std::string _fieldName, const std::string *_tableName) : fieldName (std::move (_fieldName)), tableName (_tableName) {} }; // Field : Selectable template struct Field : public Selectable { Field (std::string _fieldName, const std::string *_tableName) : Selectable (std::move (_fieldName), _tableName) {} inline SetExpr operator = (T value) { std::ostringstream os; BOT_ORM_Impl::SerializationHelper:: Serialize (os << this->fieldName << "=", value); return SetExpr { os.str () }; } }; // Nullable Field : Field : Selectable template struct NullableField : public Field { NullableField (std::string _fieldName, const std::string *_tableName) : Field (std::move (_fieldName), _tableName) {} inline SetExpr operator = (T value) { std::ostringstream os; BOT_ORM_Impl::SerializationHelper:: Serialize (os << this->fieldName << "=", value); return SetExpr { os.str () }; } inline SetExpr operator = (std::nullptr_t) { return SetExpr { this->fieldName + "=null" }; } }; // Aggregate Function : Selectable template struct Aggregate : public Selectable { Aggregate (std::string function) : Selectable (std::move (function), nullptr) {} Aggregate (std::string function, const Field &field) : Selectable (std::move (function) + "(" + *(field.tableName) + "." + field.fieldName + ")", nullptr) {} }; // Expr struct Expr { template Expr (const Selectable &field, std::string op_val) : _exprs { { field.fieldName + op_val, field.tableName } } {} template Expr (const Selectable &field, std::string op, T value) { std::ostringstream os; BOT_ORM_Impl::SerializationHelper:: Serialize (os << field.fieldName << op, value); _exprs.emplace_back (os.str (), field.tableName); } template Expr (const Field &field1, std::string op, const Field &field2) : _exprs { { field1.fieldName, field1.tableName }, { std::move (op), nullptr }, { field2.fieldName, field2.tableName } } {} std::string ToString () const { std::ostringstream out; for (const auto &expr : _exprs) { if (expr.second != nullptr) out << *(expr.second) << "."; out << expr.first; } return out.str (); } inline Expr operator && (const Expr &rhs) const { return And_Or (rhs, " and "); } inline Expr operator || (const Expr &rhs) const { return And_Or (rhs, " or "); } private: std::list> _exprs; Expr And_Or (const Expr &rhs, std::string logOp) const { auto ret = *this; auto rigthExprs = rhs._exprs; ret._exprs.emplace_front ("(", nullptr); ret._exprs.emplace_back (std::move (logOp), nullptr); ret._exprs.splice (ret._exprs.cend (), std::move (rigthExprs)); ret._exprs.emplace_back (")", nullptr); return ret; } }; // Field / Aggregate ? Value template inline Expr operator == (const Selectable &op, T value) { return Expr (op, "=", std::move (value)); } template inline Expr operator != (const Selectable &op, T value) { return Expr (op, "!=", std::move (value)); } template inline Expr operator > (const Selectable &op, T value) { return Expr (op, ">", std::move (value)); } template inline Expr operator >= (const Selectable &op, T value) { return Expr (op, ">=", std::move (value)); } template inline Expr operator < (const Selectable &op, T value) { return Expr (op, "<", std::move (value)); } template inline Expr operator <= (const Selectable &op, T value) { return Expr (op, "<=", std::move (value)); } // Field ? Field template inline Expr operator == (const Field &op1, const Field &op2) { return Expr { op1 , "=", op2 }; } template inline Expr operator != (const Field &op1, const Field &op2) { return Expr { op1, "!=", op2 }; } template inline Expr operator > (const Field &op1, const Field &op2) { return Expr { op1 , ">", op2 }; } template inline Expr operator >= (const Field &op1, const Field &op2) { return Expr { op1, ">=", op2 }; } template inline Expr operator < (const Field &op1, const Field &op2) { return Expr { op1 , "<", op2 }; } template inline Expr operator <= (const Field &op1, const Field &op2) { return Expr { op1, "<=", op2 }; } // Nullable Field == / != nullptr template inline Expr operator == (const NullableField &op, std::nullptr_t) { return Expr { op, " is null" }; } template inline Expr operator != (const NullableField &op, std::nullptr_t) { return Expr { op, " is not null" }; } // String Field & / | std::string inline Expr operator & (const Field &field, std::string val) { return Expr (field, " like ", std::move (val)); } inline Expr operator | (const Field &field, std::string val) { return Expr (field, " not like ", std::move (val)); } // Aggregate Function Helpers inline auto Count () { return Aggregate { "count (*)" }; } template inline auto Count (const Field &field) { return Aggregate { "count", field }; } template inline auto Sum (const Field &field) { return Aggregate { "sum", field }; } template inline auto Avg (const Field &field) { return Aggregate { "avg", field }; } template inline auto Max (const Field &field) { return Aggregate { "max", field }; } template inline auto Min (const Field &field) { return Aggregate { "min", field }; } } class ORMapper; class Constraint { protected: std::string constraint; std::string field; Constraint (std::string &&_constraint, std::string _field = std::string {}) : constraint (_constraint), field (std::move (_field)) {} friend class ORMapper; public: struct CompositeField { std::string fieldName; const std::string *tableName; template CompositeField (const Fields & ... args) : tableName (nullptr) { (void) BOT_ORM_Impl::Expander { 0, (Extract (args), 0)... }; } private: template void Extract (const Expression::Field &field) { if (tableName != nullptr && tableName != field.tableName) throw std::runtime_error (NOT_SAME_TABLE); tableName = field.tableName; if (fieldName.empty ()) fieldName = field.fieldName; else fieldName = field.fieldName + "," + fieldName; } }; template static inline Constraint Default ( const Expression::Field &field, const T &value) { std::ostringstream os; BOT_ORM_Impl::SerializationHelper:: Serialize (os << " default ", value); return Constraint { os.str (), field.fieldName }; } static inline Constraint Check ( const Expression::Expr &expr) { return Constraint { "check (" + expr.ToString () + ")" }; } template static inline Constraint Unique ( const Expression::Field &field) { return Constraint { "unique (" + field.fieldName + ")" }; } static inline Constraint Unique ( const CompositeField &fields) { return Constraint { "unique (" + fields.fieldName + ")" }; } template static inline Constraint Reference ( const Expression::Field &field, const Expression::Field &refered) { return Constraint { std::string ("foreign key (") + field.fieldName + ") references " + *(refered.tableName) + "(" + refered.fieldName + ")" }; } static inline Constraint Reference ( const CompositeField &field, const CompositeField &refered) { return Constraint { std::string ("foreign key (") + field.fieldName + ") references " + *(refered.tableName) + "(" + refered.fieldName + ")" }; } }; template class Queryable; } namespace BOT_ORM_Impl { // Why Remove QueryableHelper from Query? // Query is a template but the Helper can be shared class QueryableHelper { template using Nullable = BOT_ORM::Nullable; template using Selectable = BOT_ORM::Expression::Selectable; protected: template friend class BOT_ORM::Queryable; // #1 Tuple Visitor // Apply 'Fn' to each of element of Tuple template static inline void TupleVisit_Impl ( TupleType &tuple, Fn fn, std::index_sequence) { (void) BOT_ORM_Impl::Expander { 0, ((void) fn (std::get (tuple)), 0)... }; } // Produce the 'index_sequence' for 'tuple' template static inline void TupleVisit (TupleType &tuple, Fn fn) { constexpr auto size = std::tuple_size::value; return TupleVisit_Impl (tuple, fn, std::make_index_sequence {}); } // #2 Join To Tuple // Type To Nullable // Get Nullable Type Wrappers for Non-nullable Types template struct TypeToNullable { using type = Nullable; }; template struct TypeToNullable > { using type = Nullable; }; // Fields To Nullable // Apply 'TypeToNullable' to each element of Fields template static inline auto FieldsToNullable (Args...) { // Unpacking Tricks :-) // Expand each of 'Args' // with 'TypeToNullable_t<...>' as a sequence return std::tuple< typename TypeToNullable::type... > {}; } // QueryResult To Nullable template static inline auto QueryResultToTuple (const C &arg) { return BOT_ORM_Impl::InjectionHelper::Visit ( arg, [] (auto ... args) { return FieldsToNullable (args...); }); } template static inline auto QueryResultToTuple ( const std::tuple& t) { // FieldsToNullable is not necessary: Nullable already return t; } // Construct Tuple from QueryResult List template static inline auto JoinToTuple (const Args & ... args) { // Unpacking Tricks :-) return decltype (std::tuple_cat ( QueryResultToTuple (args)... )) {}; } // #3 Select To Tuple // Selectable To Tuple template static inline auto SelectableToTuple (const Selectable &) { // Notes: Only 'const Selectable &' will overload... return Nullable {}; } // Construct Tuple from Selectable List template static inline auto SelectToTuple (const Args & ... args) { // Unpacking Tricks :-) return std::tuple < decltype (SelectableToTuple (args))... > {}; } // #4 Field To SQL // Return Field Strings for GroupBy, OrderBy and Select template static inline std::string FieldToSql (const Selectable &op) { if (op.tableName) return (*op.tableName) + "." + op.fieldName; else return op.fieldName; } template static inline std::string FieldToSql (const Selectable &arg, const Args & ... args) { return FieldToSql (arg) + "," + FieldToSql (args...); } }; } namespace BOT_ORM { // Queryable template class Queryable { template using HasInjected = BOT_ORM_Impl::InjectionHelper::HasInjected; protected: std::shared_ptr _connector; QueryResult _queryHelper; std::string _sqlFrom; std::string _sqlSelect; std::string _sqlTarget; std::string _sqlWhere; std::string _sqlGroupBy; std::string _sqlHaving; std::string _sqlOrderBy; std::string _sqlLimit; std::string _sqlOffset; Queryable ( std::shared_ptr connector, QueryResult queryHelper, std::string sqlFrom, std::string sqlSelect = "select ", std::string sqlTarget = "*", std::string sqlWhere = std::string {}, std::string sqlGroupBy = std::string {}, std::string sqlHaving = std::string {}, std::string sqlOrderBy = std::string {}, std::string sqlLimit = std::string {}, std::string sqlOffset = std::string {}) : _connector (std::move (connector)), _queryHelper (std::move (queryHelper)), _sqlFrom (std::move (sqlFrom)), _sqlSelect (std::move (sqlSelect)), _sqlTarget (std::move (sqlTarget)), _sqlWhere (std::move (sqlWhere)), _sqlGroupBy (std::move (sqlGroupBy)), _sqlHaving (std::move (sqlHaving)), _sqlOrderBy (std::move (sqlOrderBy)), _sqlLimit (std::move (sqlLimit)), _sqlOffset (std::move (sqlOffset)) {} template friend class Queryable; friend class ORMapper; public: // Distinct inline Queryable Distinct () const & { auto ret = *this; ret._sqlSelect = "select distinct "; return ret; } inline Queryable Distinct () && { (*this)._sqlSelect = "select distinct "; return std::move (*this); } // Where inline Queryable Where (const Expression::Expr &expr) const & { auto ret = *this; ret._sqlWhere = " where (" + expr.ToString () + ")"; return ret; } inline Queryable Where (const Expression::Expr &expr) && { (*this)._sqlWhere = " where (" + expr.ToString () + ")"; return std::move (*this); } // Group By template inline Queryable GroupBy (const Expression::Field &field) const & { auto ret = *this; ret._sqlGroupBy = " group by " + BOT_ORM_Impl::QueryableHelper::FieldToSql (field); return ret; } template inline Queryable GroupBy (const Expression::Field &field) && { (*this)._sqlGroupBy = " group by " + BOT_ORM_Impl::QueryableHelper::FieldToSql (field); return std::move (*this); } // Having inline Queryable Having (const Expression::Expr &expr) const & { auto ret = *this; ret._sqlHaving = " having " + expr.ToString (); return ret; } inline Queryable Having (const Expression::Expr &expr) && { (*this)._sqlHaving = " having " + expr.ToString (); return std::move (*this); } // Limit inline Queryable Take (size_t count) const & { auto ret = *this; ret._sqlLimit = " limit " + std::to_string (count); return ret; } inline Queryable Take (size_t count) && { (*this)._sqlLimit = " limit " + std::to_string (count); return std::move (*this); } // Offset inline Queryable Skip (size_t count) const & { auto ret = *this; if (ret._sqlLimit.empty ()) ret._sqlLimit = " limit ~0"; // ~0 is a trick :-) ret._sqlOffset = " offset " + std::to_string (count); return ret; } inline Queryable Skip (size_t count) && { if ((*this)._sqlLimit.empty ()) (*this)._sqlLimit = " limit ~0"; // ~0 is a trick :-) (*this)._sqlOffset = " offset " + std::to_string (count); return std::move (*this); } // Order By template inline Queryable OrderBy ( const Expression::Field &field) const & { auto ret = *this; if (ret._sqlOrderBy.empty ()) ret._sqlOrderBy = " order by " + BOT_ORM_Impl::QueryableHelper::FieldToSql (field); else ret._sqlOrderBy += "," + BOT_ORM_Impl::QueryableHelper::FieldToSql (field); return ret; } template inline Queryable OrderBy ( const Expression::Field &field) && { if ((*this)._sqlOrderBy.empty ()) (*this)._sqlOrderBy = " order by " + BOT_ORM_Impl::QueryableHelper::FieldToSql (field); else (*this)._sqlOrderBy += "," + BOT_ORM_Impl::QueryableHelper::FieldToSql (field); return std::move (*this); } // Order By Desc template inline Queryable OrderByDescending ( const Expression::Field &field) const & { auto ret = *this; if (ret._sqlOrderBy.empty ()) ret._sqlOrderBy = " order by " + BOT_ORM_Impl::QueryableHelper::FieldToSql (field) + " desc"; else ret._sqlOrderBy += "," + BOT_ORM_Impl::QueryableHelper::FieldToSql (field) + " desc"; return ret; } template inline Queryable OrderByDescending ( const Expression::Field &field) && { if ((*this)._sqlOrderBy.empty ()) (*this)._sqlOrderBy = " order by " + BOT_ORM_Impl::QueryableHelper::FieldToSql (field) + " desc"; else (*this)._sqlOrderBy += "," + BOT_ORM_Impl::QueryableHelper::FieldToSql (field) + " desc"; return std::move (*this); } // Select template inline auto Select (const Args & ... args) const { return _NewQuery ( BOT_ORM_Impl::QueryableHelper::FieldToSql (args...), _sqlFrom, BOT_ORM_Impl::QueryableHelper::SelectToTuple (args...) ); } // Join template inline auto Join (const C &, const Expression::Expr &, std::enable_if_t< !HasInjected::value> * = nullptr) const {} template inline auto Join (const C &queryHelper2, const Expression::Expr &onExpr, std::enable_if_t< HasInjected::value> * = nullptr) const { return _NewJoinQuery (queryHelper2, onExpr, " join "); } // Left Join template inline auto LeftJoin (const C &, const Expression::Expr &, std::enable_if_t< !HasInjected::value> * = nullptr) const {} template inline auto LeftJoin (const C &queryHelper2, const Expression::Expr &onExpr, std::enable_if_t< HasInjected::value> * = nullptr) const { return _NewJoinQuery (queryHelper2, onExpr, " left join "); } // Compound Select inline Queryable Union (const Queryable &queryable) const { return _NewCompoundQuery (queryable, " union "); } inline Queryable UnionAll (Queryable queryable) const { return _NewCompoundQuery (queryable, " union all "); } inline Queryable Intersect (Queryable queryable) const { return _NewCompoundQuery (queryable, " intersect "); } inline Queryable Except (Queryable queryable) const { return _NewCompoundQuery (queryable, " excpet "); } // Get Result template Nullable Aggregate (const Expression::Aggregate &agg) const { Nullable ret; _connector->ExecuteCallback (_sqlSelect + agg.fieldName + _GetFromSql () + _GetLimit () + ";", [&] (int argc, char **argv) { if (argc != 1) throw std::runtime_error (BAD_COLUMN_COUNT); BOT_ORM_Impl::DeserializationHelper:: Deserialize (ret, argv[0]); }); return ret; } std::vector ToVector () const { std::vector ret; _Select (_queryHelper, ret); return ret; } std::list ToList () const { std::list ret; _Select (_queryHelper, ret); return ret; } protected: // Return FROM part for Query inline std::string _GetFromSql () const { return _sqlFrom + _sqlWhere + _sqlGroupBy + _sqlHaving; } // Return ORDER BY & LIMIT part for Query inline std::string _GetLimit () const { return _sqlOrderBy + _sqlLimit + _sqlOffset; } // Return a new Queryable Object template inline Queryable> _NewQuery ( std::string sqlTarget, std::string sqlFrom, std::tuple &&newQueryHelper) const { return Queryable> ( _connector, newQueryHelper, std::move (sqlFrom), _sqlSelect, std::move (sqlTarget), _sqlWhere, _sqlGroupBy, _sqlHaving, _sqlOrderBy, _sqlLimit, _sqlOffset); } // Return a new Join Queryable Object template inline auto _NewJoinQuery (const C &queryHelper2, const Expression::Expr &onExpr, std::string joinStr) const { return _NewQuery ( _sqlTarget, _sqlFrom + std::move (joinStr) + BOT_ORM_Impl::InjectionHelper::TableName (queryHelper2) + " on " + onExpr.ToString (), BOT_ORM_Impl::QueryableHelper::JoinToTuple ( _queryHelper, queryHelper2)); } // Return a new Compound Queryable Object Queryable _NewCompoundQuery (const Queryable &queryable, std::string compoundStr) const { auto ret = *this; ret._sqlFrom = ret._GetFromSql () + std::move (compoundStr) + queryable._sqlSelect + queryable._sqlTarget + queryable._GetFromSql (); ret._sqlWhere.clear (); ret._sqlGroupBy.clear (); ret._sqlHaving.clear (); return ret; } // Select for Normal Objects template inline void _Select (const C &, Out &out) const { auto copy = _queryHelper; _connector->ExecuteCallback (_sqlSelect + _sqlTarget + _GetFromSql () + _GetLimit () + ";", [&] (int argc, char **argv) { BOT_ORM_Impl::InjectionHelper::Visit ( copy, [argc] (auto & ... args) { if (sizeof... (args) != argc) throw std::runtime_error (BAD_COLUMN_COUNT); }); BOT_ORM_Impl::InjectionHelper::Visit ( copy, [argv] (auto & ... args) { size_t index = 0; (void) BOT_ORM_Impl::Expander { 0, (BOT_ORM_Impl::DeserializationHelper:: Deserialize (args, argv[index++]), 0)... }; }); out.push_back (copy); }); } // Select for Tuples template inline void _Select (const std::tuple &, Out &out) const { auto copy = _queryHelper; _connector->ExecuteCallback (_sqlSelect + _sqlTarget + _GetFromSql () + _GetLimit () + ";", [&] (int argc, char **argv) { if (sizeof... (Args) != argc) throw std::runtime_error (BAD_COLUMN_COUNT); size_t index = 0; BOT_ORM_Impl::QueryableHelper::TupleVisit ( copy, [argv, &index] (auto &val) { BOT_ORM_Impl::DeserializationHelper:: Deserialize (val, argv[index++]); }); out.push_back (copy); }); } }; // ORMapper class ORMapper { template using HasInjected = BOT_ORM_Impl::InjectionHelper::HasInjected; public: ORMapper (const std::string &connectionString) : _connector ( std::make_shared ( connectionString)) { _connector->Execute ("PRAGMA foreign_keys = ON;"); } template void Transaction (Fn fn) { try { _connector->Execute ("begin transaction;"); fn (); _connector->Execute ("commit transaction;"); } catch (...) { _connector->Execute ("rollback transaction;"); throw; } } template std::enable_if_t::value> CreateTbl (const C &, const Args & ...) {} template std::enable_if_t::value> CreateTbl (const C &entity, const Args & ... constraints) { const auto &fieldNames = BOT_ORM_Impl::InjectionHelper::FieldNames (entity); std::unordered_map fieldFixes; auto addTypeStr = [&fieldNames, &fieldFixes] ( const auto &arg, size_t index) { // Why addTypeStr? // Walkaround the 'undefined reference' in gcc/clang constexpr const char *typeStr = BOT_ORM_Impl::TypeString< std::remove_cv_t> >::typeStr; fieldFixes.emplace (fieldNames[index], typeStr); }; (void) addTypeStr; BOT_ORM_Impl::InjectionHelper::Visit ( entity, [&addTypeStr] (const auto & ... args) { size_t index = 0; (void) BOT_ORM_Impl::Expander { 0, (addTypeStr (args, index++), 0)... }; }); fieldFixes[fieldNames[0]] += " primary key"; std::string tableFixes; _GetConstraints (tableFixes, fieldFixes, constraints...); std::string strFmt; for (const auto &field : fieldNames) strFmt += field + fieldFixes[field] + ","; strFmt += std::move (tableFixes); strFmt.pop_back (); _connector->Execute ( "create table " + BOT_ORM_Impl::InjectionHelper::TableName (entity) + "(" + strFmt + ");"); } template std::enable_if_t::value> DropTbl (const C &) {} template std::enable_if_t::value> DropTbl (const C &entity) { _connector->Execute ( "drop table " + BOT_ORM_Impl::InjectionHelper::TableName (entity) + ";"); } template std::enable_if_t::value> Insert (const C &, bool = true) {} template std::enable_if_t::value> Insert (const C &entity, bool withId = true) { std::ostringstream os; _GetInsert (os, entity, withId); _connector->Execute (os.str ()); } template std::enable_if_t::value> InsertRange (const In &, bool = true) {} template std::enable_if_t::value> InsertRange (const In &entities, bool withId = true) { std::ostringstream os; auto anyEntity = false; for (const auto &entity : entities) { _GetInsert (os, entity, withId); anyEntity = true; } if (anyEntity) _connector->Execute (os.str ()); } template std::enable_if_t::value> Update (const C &) {} template std::enable_if_t::value> Update (const C &entity) { std::ostringstream os; if (_GetUpdate (os, entity)) _connector->Execute (os.str ()); } template std::enable_if_t::value> UpdateRange (const In &) {} template std::enable_if_t::value> UpdateRange (const In &entities) { std::ostringstream os, osTmp; for (const auto &entity : entities) if (_GetUpdate (osTmp, entity)) { os << osTmp.str (); osTmp.str (std::string {}); // Flush the previous data } auto sql = os.str (); if (!sql.empty ()) _connector->Execute (sql); } template std::enable_if_t::value> Update (const C &, const Expression::SetExpr &, const Expression::Expr &) {} template std::enable_if_t::value> Update (const C &entity, const Expression::SetExpr &setExpr, const Expression::Expr &whereExpr) { _connector->Execute ( "update " + BOT_ORM_Impl::InjectionHelper::TableName (entity) + " set " + setExpr.ToString () + " where " + whereExpr.ToString () + ";"); } template std::enable_if_t::value> Delete (const C &) {} template std::enable_if_t::value> Delete (const C &entity) { const auto &fieldNames = BOT_ORM_Impl::InjectionHelper::FieldNames (entity); std::ostringstream os; os << "delete from " << BOT_ORM_Impl::InjectionHelper::TableName (entity) << " where " << fieldNames[0] << "="; // Primary Key BOT_ORM_Impl::InjectionHelper::Visit ( entity, [&os] (const auto &primaryKey, const auto & ... dummy) { // Why 'eatdummy'? // Walkaround 'fatal error c1001: // an internal error has occurred in the compiler.' on MSVC 14 auto eatdummy = [] (const auto &) {}; (void) eatdummy; // Why 'dummy'? // Walkaround 'template argument deduction/substitution failed' // on gcc 5.4 (void) BOT_ORM_Impl::Expander { 0, (eatdummy (dummy), 0)... }; if (!BOT_ORM_Impl::SerializationHelper:: Serialize (os, primaryKey)) os << "null"; }); os << ";"; _connector->Execute (os.str ()); } template std::enable_if_t::value> Delete (const C &, const Expression::Expr &) {} template std::enable_if_t::value> Delete (const C &entity, const Expression::Expr &whereExpr) { _connector->Execute ( "delete from " + BOT_ORM_Impl::InjectionHelper::TableName (entity) + " where " + whereExpr.ToString () + ";"); } template std::enable_if_t::value, Queryable> Query (C) {} template std::enable_if_t::value, Queryable> Query (C queryHelper) { return Queryable ( _connector, std::move (queryHelper), std::string (" from ") + BOT_ORM_Impl::InjectionHelper::TableName (queryHelper)); } protected: std::shared_ptr _connector; static void _GetConstraints ( std::string &, std::unordered_map &) {} template static void _GetConstraints ( std::string &tableFixes, std::unordered_map &fieldFixes, const Constraint &constraint, const Args & ... args) { if (!constraint.field.empty ()) fieldFixes[constraint.field] += constraint.constraint; else tableFixes += constraint.constraint + ","; _GetConstraints (tableFixes, fieldFixes, args...); } template static inline void _GetInsert ( std::ostream &os, const C &entity, bool withId) { BOT_ORM_Impl::InjectionHelper::Visit ( entity, [&os, &entity, withId] ( const auto &primaryKey, const auto & ... args) { const auto &fieldNames = BOT_ORM_Impl::InjectionHelper::FieldNames (entity); os << "insert into " << BOT_ORM_Impl::InjectionHelper::TableName (entity) << "("; std::ostringstream osVal; // Avoid Bad Eating of , bool anyField = false; auto serializeField = [&fieldNames, &os, &osVal, &anyField] ( const auto &val, size_t index) { if (BOT_ORM_Impl::SerializationHelper:: Serialize (osVal, val)) { os << fieldNames[index] << ","; osVal << ","; anyField = true; } }; // Priamry Key if (withId && BOT_ORM_Impl::SerializationHelper:: Serialize (osVal, primaryKey)) { os << fieldNames[0] << ","; osVal << ","; anyField = true; } // The Rest size_t index = 1; (void) BOT_ORM_Impl::Expander { 0, (serializeField (args, index++), 0)... }; (void) serializeField; if (anyField) { os.seekp (os.tellp () - std::streamoff (1)); osVal.seekp (osVal.tellp () - std::streamoff (1)); } else // Fix for No Field for Insert... { os << fieldNames[0]; osVal << "null"; } osVal << ");"; // To Enable Seekp... os << ") values (" << osVal.str (); }); } template static inline bool _GetUpdate ( std::ostream &os, const C &entity) { return BOT_ORM_Impl::InjectionHelper::Visit ( entity, [&os, &entity] ( const auto &primaryKey, const auto & ... args) { if (sizeof... (args) == 0) return false; const auto &fieldNames = BOT_ORM_Impl::InjectionHelper::FieldNames (entity); os << "update " << BOT_ORM_Impl::InjectionHelper::TableName (entity) << " set "; auto serializeField = [&fieldNames, &os] ( const auto &val, size_t index) { os << fieldNames[index] << "="; if (!BOT_ORM_Impl::SerializationHelper:: Serialize (os, val)) os << "null"; os << ","; }; // The Rest size_t index = 1; (void) BOT_ORM_Impl::Expander { 0, (serializeField (args, index++), 0)... }; (void) serializeField; os.seekp (os.tellp () - std::streamoff (1)); // Primary Key os << " where " << fieldNames[0] << "="; if (!BOT_ORM_Impl::SerializationHelper:: Serialize (os, primaryKey)) os << "null"; os << ";"; return true; }); } }; // Field Extractor class FieldExtractor { using pair_type = std::pair< const std::string &, const std::string &>; template using HasInjected = BOT_ORM_Impl::InjectionHelper::HasInjected; template std::enable_if_t::value> Extract (const C &) {} template std::enable_if_t::value> Extract (const C &helper) { BOT_ORM_Impl::InjectionHelper::Visit ( helper, [this, &helper] ( const auto & ... args) { const auto &fieldNames = BOT_ORM_Impl::InjectionHelper::FieldNames (helper); const auto &tableName = BOT_ORM_Impl::InjectionHelper::TableName (helper); size_t index = 0; (void) BOT_ORM_Impl::Expander { 0, (_map.emplace ( (const void *) &args, pair_type { fieldNames[index++], tableName } ), 0)... }; }); } public: template FieldExtractor (const Classes & ... args) { (void) BOT_ORM_Impl::Expander { 0, (Extract (args), 0)... }; } template inline Expression::Field operator () ( const T &field) const { const auto &result = Get (field); return Expression::Field { std::move (result.first), &result.second }; } template inline Expression::NullableField operator () ( const Nullable &field) const { const auto &result = Get (field); return Expression::NullableField { std::move (result.first), &result.second }; } private: std::unordered_map _map; template const pair_type &Get (const T &field) const { try { return _map.at ((const void *) &field); } catch (...) { throw std::runtime_error (NO_FIELD); } } }; } // Clear Intra Macros #undef NO_ORMAP #undef BAD_TYPE #undef NO_FIELD #undef BAD_COLUMN_COUNT #undef NULL_DESERIALIZE #undef NOT_SAME_TABLE #endif // !BOT_ORM_H ================================================ FILE: test/makefile ================================================ TARGET = test SOURCES = test.cpp ../src/sqlite3.c WARNINGFLAGS = -Wall -W CPPFLAGS = -std=c++14 LINKS = -lstdc++ -lpthread -ldl OBJS = $(patsubst %.c,%.o,$(patsubst %.cpp,%.o,$(SOURCES))) %.o: %.c $(CC) -c $< -o $@ $(WARNINGFLAGS) $(LINKS) %.o: %.cpp $(CC) -c $< -o $@ $(WARNINGFLAGS) $(LINKS) $(CPPFLAGS) $(TARGET): $(OBJS) $(CC) $(OBJS) -o $(TARGET) $(LINKS) clean: rm -rf $(OBJS) $(TARGET) *.db ================================================ FILE: test/test.cpp ================================================  // Test of ORM Lite // https://github.com/BOT-Man-JL/ORM-Lite // BOT Man, 2016 #include #include #include #include "../src/ormlite.h" using namespace BOT_ORM; using namespace BOT_ORM::Expression; #define CATCH_CONFIG_MAIN #include "catch.hpp" #define TESTDB "test.db" struct ModelA { int a_int; std::string a_string; double a_double; Nullable an_int; Nullable an_double; Nullable an_string; // Inject ORM-Lite into this Class :-) ORMAP ("ModelA", a_int, a_string, a_double, an_int, an_double, an_string); }; struct ModelB { unsigned long b_ulong; float b_float; Nullable bn_ulong; Nullable bn_float; // Inject ORM-Lite into this Class :-) ORMAP ("ModelB", b_ulong, b_float, bn_ulong, bn_float); }; struct ModelC { unsigned c_uint; int a_int; unsigned long b_ulong; // Inject ORM-Lite into this Class :-) ORMAP ("ModelC", c_uint, a_int, b_ulong); }; struct ModelD { int d_int; // Inject ORM-Lite into this Class :-) ORMAP ("ModelD", d_int); }; namespace detail { template void ResetTable (const Model &model) { ORMapper mapper (TESTDB); try { mapper.CreateTbl (model); } catch (...) { mapper.DropTbl (model); mapper.CreateTbl (model); } } } void ResetTables () {} template void ResetTables (const Model &model, const OtherModels &...models) { detail::ResetTable (model); ResetTables (models...); } TEST_CASE ("create/drop tables") { ResetTables (ModelA {}, ModelB {}, ModelC {}, ModelD {}); ORMapper mapper (TESTDB); mapper.DropTbl (ModelA {}); mapper.DropTbl (ModelB {}); mapper.DropTbl (ModelC {}); mapper.DropTbl (ModelD {}); mapper.CreateTbl (ModelA {}); mapper.CreateTbl (ModelB {}); mapper.CreateTbl (ModelC {}); mapper.CreateTbl (ModelD {}); } TEST_CASE ("normal cases") { ModelA ma; ModelD md; auto field = FieldExtractor { ma, md }; ORMapper mapper (TESTDB); mapper.Insert (ModelD { 0 }); mapper.Insert (ModelD { 0 }, false); mapper.InsertRange (std::list { ModelD { 2 }, ModelD { 3 } }); mapper.InsertRange (std::list { ModelD { 2 }, ModelD { 3 } }, false); mapper.Update (ModelD { 0 }); mapper.UpdateRange (std::list { ModelD { 2 }, ModelD { 3 } }); mapper.Update (ModelD {}, field (md.d_int) = 6, field (md.d_int) == 0); // 0 -> 6 mapper.Delete (ModelD { 1 }); mapper.Delete (ModelD {}, field (md.d_int) == 0); // No such one constexpr auto countExpected = 5; constexpr auto firstIdExpected = 2; constexpr auto lastIdExpected = 6; // Expected: 2, 3, 4, 5, 6 REQUIRE (mapper.Query (ModelD {}) .Aggregate (Count ()).Value () == countExpected); REQUIRE (mapper.Query (ModelD {}) .LeftJoin (ModelA {}, field (ma.a_int) == field (md.d_int)) .Aggregate (Count ()).Value () == countExpected); mapper.Insert (ModelA {}, false); REQUIRE (mapper.Query (ModelD {}).Select (field (md.d_int)) .Union (mapper.Query (ModelA {}).Select (field (ma.a_int))) .ToList ().size () == countExpected + 1); REQUIRE (mapper.Query (ModelD {}) .ToVector ()[countExpected - 1].d_int == lastIdExpected); auto firstTuple = mapper.Query (ModelD {}) .Select (field (md.d_int)) .ToList ().front (); REQUIRE (std::get<0> (firstTuple).Value () == firstIdExpected); } TEST_CASE ("handle existing table") { // before ResetTables (ModelD {}); { sqlite3 *db; sqlite3_open (TESTDB, &db); sqlite3_exec (db, "DROP TABLE ModelD;" "CREATE TABLE ModelD (d_int INTEGER, d_str TEXT);" "INSERT INTO ModelD values (1, 'John');", nullptr, nullptr, nullptr); sqlite3_close (db); } // test ORMapper mapper (TESTDB); REQUIRE_THROWS_WITH (mapper.Query (ModelD {}).ToList (), "SQL error: 'Bad Column Count' at 'select * from ModelD;'"); } TEST_CASE ("chinese characters") { // before ResetTables (ModelA {}); // test ORMapper mapper (TESTDB); mapper.Insert ( ModelA { 0, "你好", 0, nullptr, nullptr, nullptr }, false); mapper.Insert ( ModelA { 0, u8"世界", 0, nullptr, nullptr, nullptr }, false); auto chinese = mapper.Query (ModelA {}).ToVector (); REQUIRE (chinese.size () == 2); REQUIRE (chinese[0].a_string == "你好"); REQUIRE (chinese[1].a_string == u8"世界"); } TEST_CASE ("lifetime of mapper") { // before ResetTables (ModelA {}); { ORMapper mapper (TESTDB); mapper.Insert (ModelA {}, false); mapper.Insert (ModelA {}, false); } // test std::unique_ptr> queryable; { ORMapper mapper (TESTDB); queryable.reset (new Queryable { mapper.Query (ModelA {}) }); } REQUIRE (queryable->ToList ().size () == 2); } using InvalidModel = int; TEST_CASE ("invalid model", "[not-compile]") { ORMapper mapper (TESTDB); //mapper.CreateTbl (InvalidModel {}); //mapper.CreateTbl (InvalidModel {}, Constraint::Unique (Field {"", nullptr})); //mapper.DropTbl (InvalidModel {}); //mapper.Insert (InvalidModel {}); //mapper.Insert (InvalidModel {}, false); ////mapper.InsertRange (InvalidModel {}); //mapper.InsertRange (std::vector {}); //mapper.InsertRange (std::vector {}, false); //mapper.Update (InvalidModel {}); ////mapper.UpdateRange (InvalidModel {}); //mapper.UpdateRange (std::vector {}); //mapper.Update (InvalidModel {}, SetExpr { "" }, Expr { Selectable {"", nullptr}, "" }); //mapper.Delete (InvalidModel {}); //mapper.Delete (InvalidModel {}, Expr { Selectable {"", nullptr}, "" }); //mapper.Query (InvalidModel {}); //FieldExtractor { InvalidModel {}, double () }; ////mapper.Query (ModelA {}) //// .Join (InvalidModel {}, Expr { Selectable {"", nullptr}, "" }); ////mapper.Query (ModelA {}) //// .LeftJoin (InvalidModel {}, Expr { Selectable {"", nullptr}, "" }); } struct SickModel { ModelD dd; char ch; ORMAP ("SickModel", dd, ch); }; TEST_CASE ("invalid field", "[not-compile]") { ORMapper mapper (TESTDB); //mapper.CreateTbl (SickModel {}); //mapper.Insert (SickModel {}); //mapper.Update (SickModel {}); //mapper.Delete (SickModel {}); //mapper.Query (SickModel {}).ToList (); } ================================================ FILE: test/test.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 1781A74F1F5FED13008E6067 /* test.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1781A74E1F5FED13008E6067 /* test.cpp */; }; 1781A7531F5FEDB3008E6067 /* sqlite3.c in Sources */ = {isa = PBXBuildFile; fileRef = 1781A7511F5FEDB3008E6067 /* sqlite3.c */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ 1781A7421F5FECD1008E6067 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = /usr/share/man/man1/; dstSubfolderSpec = 0; files = ( ); runOnlyForDeploymentPostprocessing = 1; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 1781A7441F5FECD1008E6067 /* test */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = test; sourceTree = BUILT_PRODUCTS_DIR; }; 1781A74E1F5FED13008E6067 /* test.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = test.cpp; sourceTree = ""; }; 1781A7541F5FEE5D008E6067 /* nullable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = nullable.h; path = ../src/nullable.h; sourceTree = ""; }; 1781A7501F5FED8A008E6067 /* ormlite.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ormlite.h; path = ../src/ormlite.h; sourceTree = ""; }; 1781A7511F5FEDB3008E6067 /* sqlite3.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = sqlite3.c; path = ../src/sqlite3.c; sourceTree = ""; }; 1781A7521F5FEDB3008E6067 /* sqlite3.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = sqlite3.h; path = ../src/sqlite3.h; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 1781A7411F5FECD1008E6067 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 1781A73B1F5FECD1008E6067 = { isa = PBXGroup; children = ( 1781A7541F5FEE5D008E6067 /* nullable.h */, 1781A7501F5FED8A008E6067 /* ormlite.h */, 1781A7511F5FEDB3008E6067 /* sqlite3.c */, 1781A7521F5FEDB3008E6067 /* sqlite3.h */, 1781A74E1F5FED13008E6067 /* test.cpp */, 1781A7451F5FECD1008E6067 /* Products */, ); sourceTree = ""; }; 1781A7451F5FECD1008E6067 /* Products */ = { isa = PBXGroup; children = ( 1781A7441F5FECD1008E6067 /* test */, ); name = Products; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 1781A7431F5FECD1008E6067 /* test */ = { isa = PBXNativeTarget; buildConfigurationList = 1781A74B1F5FECD1008E6067 /* Build configuration list for PBXNativeTarget "test" */; buildPhases = ( 1781A7401F5FECD1008E6067 /* Sources */, 1781A7411F5FECD1008E6067 /* Frameworks */, 1781A7421F5FECD1008E6067 /* CopyFiles */, ); buildRules = ( ); dependencies = ( ); name = test; productName = test; productReference = 1781A7441F5FECD1008E6067 /* test */; productType = "com.apple.product-type.tool"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 1781A73C1F5FECD1008E6067 /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 0830; TargetAttributes = { 1781A7431F5FECD1008E6067 = { CreatedOnToolsVersion = 8.3.3; ProvisioningStyle = Automatic; }; }; }; buildConfigurationList = 1781A73F1F5FECD1008E6067 /* Build configuration list for PBXProject "test" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( en, ); mainGroup = 1781A73B1F5FECD1008E6067; productRefGroup = 1781A7451F5FECD1008E6067 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 1781A7431F5FECD1008E6067 /* test */, ); }; /* End PBXProject section */ /* Begin PBXSourcesBuildPhase section */ 1781A7401F5FECD1008E6067 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 1781A74F1F5FED13008E6067 /* test.cpp in Sources */, 1781A7531F5FEDB3008E6067 /* sqlite3.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 1781A7491F5FECD1008E6067 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.12; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; }; name = Debug; }; 1781A74A1F5FECD1008E6067 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.12; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; }; name = Release; }; 1781A74C1F5FECD1008E6067 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "c++14"; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 1781A74D1F5FECD1008E6067 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "c++14"; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 1781A73F1F5FECD1008E6067 /* Build configuration list for PBXProject "test" */ = { isa = XCConfigurationList; buildConfigurations = ( 1781A7491F5FECD1008E6067 /* Debug */, 1781A74A1F5FECD1008E6067 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 1781A74B1F5FECD1008E6067 /* Build configuration list for PBXNativeTarget "test" */ = { isa = XCConfigurationList; buildConfigurations = ( 1781A74C1F5FECD1008E6067 /* Debug */, 1781A74D1F5FECD1008E6067 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 1781A73C1F5FECD1008E6067 /* Project object */; } ================================================ FILE: test/test.vcxproj ================================================  Debug Win32 Release Win32 Debug x64 Release x64 {AD41C840-0ADD-43E1-BF17-60EC79CEEEFC} Win32Proj test Application true v141 Unicode Application false v141 true Unicode Application true v141 Unicode Application false v141 true Unicode true $(SolutionDir)$(Configuration)\test\ $(Configuration)\test\ true $(Platform)\$(Configuration)\test\ $(SolutionDir)$(Platform)\$(Configuration)\test\ false false Level3 Disabled WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) Console true Level3 Disabled _DEBUG;_CONSOLE;%(PreprocessorDefinitions) Console true Level3 MaxSpeed true true WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) Console true true true Level3 MaxSpeed true true NDEBUG;_CONSOLE;%(PreprocessorDefinitions) Console true true true