pre-sql
parent
5d5c293406
commit
3db709a484
|
@ -7,19 +7,25 @@ option(ENABLE_TSAN "Enable the thread data race sanitizer" OFF)
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
|
||||||
|
add_compile_options(-march=native)
|
||||||
|
|
||||||
|
set(SQLITE_ORM_ENABLE_CXX_17 ON)
|
||||||
|
|
||||||
add_subdirectory(libs/blt)
|
add_subdirectory(libs/blt)
|
||||||
add_subdirectory(libs/DPP-10.0.29)
|
add_subdirectory(libs/DPP-10.0.29)
|
||||||
|
add_subdirectory(libs/sqlite_orm-1.8.2)
|
||||||
|
|
||||||
include_directories(include/)
|
include_directories(include/)
|
||||||
file(GLOB_RECURSE PROJECT_BUILD_FILES "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
|
file(GLOB_RECURSE PROJECT_BUILD_FILES "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
|
||||||
|
|
||||||
add_executable(discord_bot ${PROJECT_BUILD_FILES})
|
add_executable(discord_bot ${PROJECT_BUILD_FILES})
|
||||||
|
|
||||||
target_compile_options(discord_bot PRIVATE -Wall -Werror -Wpedantic -Wno-comment)
|
target_compile_options(discord_bot PUBLIC -Wall -Wpedantic -Wno-comment -march=native)
|
||||||
target_link_options(discord_bot PRIVATE -Wall -Werror -Wpedantic -Wno-comment)
|
target_link_options(discord_bot PUBLIC -Wall -Wpedantic -Wno-comment)
|
||||||
|
|
||||||
target_link_libraries(discord_bot PUBLIC BLT)
|
target_link_libraries(discord_bot PUBLIC BLT)
|
||||||
target_link_libraries(discord_bot PUBLIC dpp)
|
target_link_libraries(discord_bot PUBLIC dpp)
|
||||||
|
target_link_libraries(discord_bot PUBLIC sqlite_orm)
|
||||||
|
|
||||||
if (${ENABLE_ADDRSAN} MATCHES ON)
|
if (${ENABLE_ADDRSAN} MATCHES ON)
|
||||||
target_compile_options(discord_bot PRIVATE -fsanitize=address)
|
target_compile_options(discord_bot PRIVATE -fsanitize=address)
|
||||||
|
|
|
@ -0,0 +1,145 @@
|
||||||
|
#pragma once
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2024 Brett Terpstra
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef DISCORD_BOT_FILEMANAGER_H
|
||||||
|
#define DISCORD_BOT_FILEMANAGER_H
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
#include <blt/std/types.h>
|
||||||
|
#include <blt/std/hashmap.h>
|
||||||
|
#include <dpp/message.h>
|
||||||
|
#include <fstream>
|
||||||
|
#include <atomic>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace db
|
||||||
|
{
|
||||||
|
// path to archive_folder/raw/guildID/current_day/channelID(name)/
|
||||||
|
// in archive_folder/raw/guildID/
|
||||||
|
// (days)
|
||||||
|
// server_name.txt (changes in server name)
|
||||||
|
// migrations.txt (changes in channel name)
|
||||||
|
// channels.txt (deleted / create channels)
|
||||||
|
|
||||||
|
// in current_day:
|
||||||
|
// channelID
|
||||||
|
|
||||||
|
// message id: (username):
|
||||||
|
class msg_fs_manager
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
std::fstream msg_file;
|
||||||
|
public:
|
||||||
|
explicit msg_fs_manager(const std::string& file): msg_file(file, std::ios::in | std::ios::out)
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
class channel_fs_manager
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
blt::u64 channelID;
|
||||||
|
HASHMAP<blt::u64, msg_fs_manager> msgs;
|
||||||
|
public:
|
||||||
|
explicit channel_fs_manager(blt::u64 channelID): channelID(channelID)
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
class channel_flusher
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
std::vector<std::unique_ptr<channel_fs_manager>> channels_to_flush;
|
||||||
|
public:
|
||||||
|
void flush_channels(HASHMAP<blt::u64, std::unique_ptr<channel_fs_manager>>& map);
|
||||||
|
};
|
||||||
|
|
||||||
|
class guild_fs_manager
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
std::string archive_path;
|
||||||
|
std::string path;
|
||||||
|
blt::u64 guildID;
|
||||||
|
HASHMAP<blt::u64, std::unique_ptr<channel_fs_manager>> channels;
|
||||||
|
dpp::cluster& bot;
|
||||||
|
channel_flusher flusher;
|
||||||
|
std::fstream f_server_name;
|
||||||
|
std::fstream f_migrations;
|
||||||
|
std::fstream f_channels;
|
||||||
|
std::atomic_int32_t complete{0};
|
||||||
|
|
||||||
|
void check_for_channel_updates();
|
||||||
|
|
||||||
|
void check_for_guild_updates();
|
||||||
|
|
||||||
|
public:
|
||||||
|
guild_fs_manager(std::string_view archive_path, std::string_view filePath, blt::u64 guildID, dpp::cluster& bot):
|
||||||
|
archive_path(archive_path), path(filePath), guildID(guildID), bot(bot),
|
||||||
|
f_server_name(this->archive_path + "server_name.txt", std::ios::out | std::ios::in | std::ios::app),
|
||||||
|
f_migrations(this->archive_path + "migrations.txt", std::ios::out | std::ios::in | std::ios::app),
|
||||||
|
f_channels(this->archive_path + "channels.txt", std::ios::out | std::ios::in | std::ios::app)
|
||||||
|
{}
|
||||||
|
|
||||||
|
void message_create(blt::u64 channel_id, blt::u64 msg_id, std::string_view content, std::string_view username,
|
||||||
|
std::string_view display_name, const std::vector<dpp::attachment>& attachments);
|
||||||
|
|
||||||
|
void message_delete(blt::u64 channel_id, blt::u64 msg_id);
|
||||||
|
|
||||||
|
void message_bulk_delete(blt::u64 channel_id, const std::vector<dpp::snowflake>& message_ids)
|
||||||
|
{
|
||||||
|
for (const auto& v : message_ids)
|
||||||
|
message_delete(channel_id, format_as(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
void message_update(blt::u64 channel_id, blt::u64 msg_id, std::string_view new_content);
|
||||||
|
|
||||||
|
void flush(std::string new_path);
|
||||||
|
|
||||||
|
void await_completions();
|
||||||
|
};
|
||||||
|
|
||||||
|
class fs_manager
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
HASHMAP<blt::u64, std::unique_ptr<guild_fs_manager>> guild_handlers;
|
||||||
|
dpp::cluster& bot;
|
||||||
|
std::string archive_path;
|
||||||
|
int current_day = 0;
|
||||||
|
std::atomic_bool is_flushing{false};
|
||||||
|
|
||||||
|
std::string create_path(blt::u64 guildID);
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit fs_manager(std::string_view archive_path, dpp::cluster& bot);
|
||||||
|
|
||||||
|
void create(blt::u64 guildID)
|
||||||
|
{
|
||||||
|
guild_handlers.insert({guildID, std::make_unique<guild_fs_manager>(archive_path, create_path(guildID), guildID, bot)});
|
||||||
|
}
|
||||||
|
|
||||||
|
void flush();
|
||||||
|
|
||||||
|
guild_fs_manager& operator[](blt::u64 guildID)
|
||||||
|
{
|
||||||
|
while (is_flushing)
|
||||||
|
{}
|
||||||
|
return *guild_handlers.at(guildID);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //DISCORD_BOT_FILEMANAGER_H
|
2
libs/blt
2
libs/blt
|
@ -1 +1 @@
|
||||||
Subproject commit 384529333c0f46ef76dced3d78769250d3e227e6
|
Subproject commit 61d46de5737b09d1eb1a6ef240f2af5e6ef2de71
|
|
@ -0,0 +1,125 @@
|
||||||
|
---
|
||||||
|
Language: Cpp
|
||||||
|
AccessModifierOffset: -2
|
||||||
|
AlignAfterOpenBracket: Align
|
||||||
|
AlignConsecutiveAssignments: false
|
||||||
|
AlignConsecutiveDeclarations: false
|
||||||
|
AlignEscapedNewlines: Right
|
||||||
|
AlignOperands: true
|
||||||
|
AlignTrailingComments: false
|
||||||
|
AllowAllParametersOfDeclarationOnNextLine: false
|
||||||
|
AllowShortLambdasOnASingleLine: Empty
|
||||||
|
AllowAllArgumentsOnNextLine: false
|
||||||
|
AllowAllConstructorInitializersOnNextLine: false
|
||||||
|
AllowShortBlocksOnASingleLine: false
|
||||||
|
AllowShortCaseLabelsOnASingleLine: false
|
||||||
|
AllowShortFunctionsOnASingleLine: Empty
|
||||||
|
AllowShortIfStatementsOnASingleLine: false
|
||||||
|
AllowShortLoopsOnASingleLine: false
|
||||||
|
AlwaysBreakAfterDefinitionReturnType: None
|
||||||
|
AlwaysBreakAfterReturnType: None
|
||||||
|
AlwaysBreakBeforeMultilineStrings: false
|
||||||
|
AlwaysBreakTemplateDeclarations: Yes
|
||||||
|
BinPackArguments: false
|
||||||
|
BinPackParameters: false
|
||||||
|
BraceWrapping:
|
||||||
|
AfterClass: false
|
||||||
|
AfterControlStatement: false
|
||||||
|
AfterEnum: false
|
||||||
|
AfterFunction: false
|
||||||
|
AfterNamespace: false
|
||||||
|
AfterObjCDeclaration: false
|
||||||
|
AfterStruct: false
|
||||||
|
AfterUnion: false
|
||||||
|
AfterExternBlock: false
|
||||||
|
BeforeCatch: false
|
||||||
|
BeforeElse: false
|
||||||
|
IndentBraces: false
|
||||||
|
SplitEmptyFunction: true
|
||||||
|
SplitEmptyRecord: true
|
||||||
|
SplitEmptyNamespace: true
|
||||||
|
BreakBeforeBinaryOperators: None
|
||||||
|
BreakBeforeBraces: Attach
|
||||||
|
BreakBeforeInheritanceComma: false
|
||||||
|
BreakInheritanceList: BeforeColon
|
||||||
|
BreakBeforeTernaryOperators: true
|
||||||
|
BreakConstructorInitializersBeforeComma: false
|
||||||
|
BreakConstructorInitializers: AfterColon
|
||||||
|
BreakAfterJavaFieldAnnotations: false
|
||||||
|
BreakStringLiterals: true
|
||||||
|
ColumnLimit: 120
|
||||||
|
CommentPragmas: '^ IWYU pragma:'
|
||||||
|
CompactNamespaces: false
|
||||||
|
ConstructorInitializerAllOnOneLineOrOnePerLine: false
|
||||||
|
ConstructorInitializerIndentWidth: 4
|
||||||
|
ContinuationIndentWidth: 4
|
||||||
|
Cpp11BracedListStyle: true
|
||||||
|
DerivePointerAlignment: false
|
||||||
|
DisableFormat: false
|
||||||
|
ExperimentalAutoDetectBinPacking: false
|
||||||
|
FixNamespaceComments: false
|
||||||
|
ForEachMacros:
|
||||||
|
- foreach
|
||||||
|
- Q_FOREACH
|
||||||
|
- BOOST_FOREACH
|
||||||
|
IncludeBlocks: Preserve
|
||||||
|
IncludeCategories:
|
||||||
|
- Regex: '^"(llvm|llvm-c|clang|clang-c)/'
|
||||||
|
Priority: 2
|
||||||
|
- Regex: '^(<|"(gtest|gmock|isl|json)/)'
|
||||||
|
Priority: 3
|
||||||
|
- Regex: '.*'
|
||||||
|
Priority: 1
|
||||||
|
IncludeIsMainRegex: '(Test)?$'
|
||||||
|
IndentCaseLabels: true
|
||||||
|
IndentPPDirectives: None
|
||||||
|
IndentWidth: 4
|
||||||
|
IndentWrappedFunctionNames: false
|
||||||
|
JavaScriptQuotes: Leave
|
||||||
|
JavaScriptWrapImports: true
|
||||||
|
KeepEmptyLinesAtTheStartOfBlocks: true
|
||||||
|
MacroBlockBegin: ''
|
||||||
|
MacroBlockEnd: ''
|
||||||
|
MaxEmptyLinesToKeep: 1
|
||||||
|
NamespaceIndentation: All
|
||||||
|
ObjCBinPackProtocolList: Auto
|
||||||
|
ObjCBlockIndentWidth: 2
|
||||||
|
ObjCSpaceAfterProperty: false
|
||||||
|
ObjCSpaceBeforeProtocolList: true
|
||||||
|
PenaltyBreakAssignment: 2
|
||||||
|
PenaltyBreakBeforeFirstCallParameter: 19
|
||||||
|
PenaltyBreakComment: 300
|
||||||
|
PenaltyBreakFirstLessLess: 120
|
||||||
|
PenaltyBreakString: 1000
|
||||||
|
PenaltyBreakTemplateDeclaration: 10
|
||||||
|
PenaltyExcessCharacter: 1000000
|
||||||
|
PenaltyReturnTypeOnItsOwnLine: 60
|
||||||
|
PointerAlignment: Left
|
||||||
|
ReflowComments: false
|
||||||
|
SortIncludes: false
|
||||||
|
SortUsingDeclarations: true
|
||||||
|
SpaceAfterCStyleCast: false
|
||||||
|
SpaceAfterTemplateKeyword: false
|
||||||
|
SpaceBeforeAssignmentOperators: true
|
||||||
|
SpaceBeforeCpp11BracedList: false
|
||||||
|
SpaceBeforeCtorInitializerColon: true
|
||||||
|
SpaceBeforeInheritanceColon: true
|
||||||
|
SpaceBeforeParens: Never
|
||||||
|
SpaceBeforeRangeBasedForLoopColon: false
|
||||||
|
SpaceInEmptyParentheses: false
|
||||||
|
SpacesBeforeTrailingComments: 2
|
||||||
|
SpacesInAngles: false
|
||||||
|
SpacesInContainerLiterals: true
|
||||||
|
SpacesInCStyleCastParentheses: false
|
||||||
|
SpacesInParentheses: false
|
||||||
|
SpacesInSquareBrackets: false
|
||||||
|
Standard: Cpp11
|
||||||
|
StatementMacros:
|
||||||
|
- __pragma
|
||||||
|
- _Pragma
|
||||||
|
- Q_UNUSED
|
||||||
|
- QT_REQUIRE_VERSION
|
||||||
|
TabWidth: 8
|
||||||
|
UseTab: Never
|
||||||
|
...
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
github: fnc12
|
||||||
|
custom: https://paypal.me/fnc12
|
|
@ -0,0 +1,14 @@
|
||||||
|
name: clang-format lint
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v1
|
||||||
|
- name: clang-format lint
|
||||||
|
uses: DoozyX/clang-format-lint-action@v0.15
|
||||||
|
with:
|
||||||
|
clangFormatVersion: 15
|
|
@ -0,0 +1,8 @@
|
||||||
|
.DS_store
|
||||||
|
examples/simple_neural_network.cpp
|
||||||
|
|
||||||
|
cmake-build-debug/
|
||||||
|
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
/compile
|
|
@ -0,0 +1,157 @@
|
||||||
|
# Defaults
|
||||||
|
os: linux
|
||||||
|
dist: focal
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- name: "[C++14] GCC-9"
|
||||||
|
addons:
|
||||||
|
apt:
|
||||||
|
sources:
|
||||||
|
- ubuntu-toolchain-r-test
|
||||||
|
packages:
|
||||||
|
- g++-9
|
||||||
|
- ninja-build
|
||||||
|
env:
|
||||||
|
- CC: gcc-9
|
||||||
|
- CXX: g++-9
|
||||||
|
|
||||||
|
- name: "[C++14] GCC-7"
|
||||||
|
addons:
|
||||||
|
apt:
|
||||||
|
sources:
|
||||||
|
- ubuntu-toolchain-r-test
|
||||||
|
packages:
|
||||||
|
- g++-7
|
||||||
|
- ninja-build
|
||||||
|
env:
|
||||||
|
- CC: gcc-7
|
||||||
|
- CXX: g++-7
|
||||||
|
|
||||||
|
- name: "[C++14] LLVM/Clang (Travis default)"
|
||||||
|
language: cpp
|
||||||
|
compiler: clang
|
||||||
|
addons:
|
||||||
|
apt:
|
||||||
|
packages:
|
||||||
|
- ninja-build
|
||||||
|
env:
|
||||||
|
- SQLITE_ORM_OMITS_CODECVT: ON
|
||||||
|
|
||||||
|
- name: "[C++14] AppleClang-10.0.1"
|
||||||
|
os: osx
|
||||||
|
osx_image: xcode10.2
|
||||||
|
language: cpp
|
||||||
|
env:
|
||||||
|
- SQLITE_ORM_OMITS_CODECVT: ON
|
||||||
|
addons:
|
||||||
|
homebrew:
|
||||||
|
packages:
|
||||||
|
- ninja
|
||||||
|
update: true
|
||||||
|
|
||||||
|
- name: "[C++14] LLVM/Clang (latest)"
|
||||||
|
os: osx
|
||||||
|
osx_image: xcode10.2
|
||||||
|
addons:
|
||||||
|
homebrew:
|
||||||
|
packages:
|
||||||
|
- llvm
|
||||||
|
- ninja
|
||||||
|
update: true
|
||||||
|
env:
|
||||||
|
- CPPFLAGS: "-I/usr/local/opt/llvm/include"
|
||||||
|
- LDFLAGS: "-L/usr/local/opt/llvm/lib -Wl,-rpath,/usr/local/opt/llvm/lib"
|
||||||
|
- CPATH: /usr/local/opt/llvm/include
|
||||||
|
- LIBRARY_PATH: /usr/local/opt/llvm/lib
|
||||||
|
- LD_LIBRARY_PATH: /usr/local/opt/llvm/lib
|
||||||
|
- CC: /usr/local/opt/llvm/bin/clang
|
||||||
|
- CXX: /usr/local/opt/llvm/bin/clang++
|
||||||
|
- SQLITE_ORM_OMITS_CODECVT: ON
|
||||||
|
|
||||||
|
- name: "[C++14] GCC-6"
|
||||||
|
os: osx
|
||||||
|
osx_image: xcode10.2
|
||||||
|
addons:
|
||||||
|
homebrew:
|
||||||
|
packages:
|
||||||
|
- gcc@6
|
||||||
|
- ninja
|
||||||
|
update: true
|
||||||
|
env:
|
||||||
|
- CC: gcc-6
|
||||||
|
- CXX: g++-6
|
||||||
|
|
||||||
|
- name: "[C++17] GCC-9"
|
||||||
|
addons:
|
||||||
|
apt:
|
||||||
|
sources:
|
||||||
|
- ubuntu-toolchain-r-test
|
||||||
|
packages:
|
||||||
|
- g++-9
|
||||||
|
- ninja-build
|
||||||
|
env:
|
||||||
|
- CC: gcc-9
|
||||||
|
- CXX: g++-9
|
||||||
|
- SQLITE_ORM_CXX_STANDARD: "-DSQLITE_ORM_ENABLE_CXX_17=ON"
|
||||||
|
|
||||||
|
- name: "[C++17] GCC-7"
|
||||||
|
addons:
|
||||||
|
apt:
|
||||||
|
sources:
|
||||||
|
- ubuntu-toolchain-r-test
|
||||||
|
packages:
|
||||||
|
- g++-7
|
||||||
|
- ninja-build
|
||||||
|
env:
|
||||||
|
- CC: gcc-7
|
||||||
|
- CXX: g++-7
|
||||||
|
- SQLITE_ORM_CXX_STANDARD: "-DSQLITE_ORM_ENABLE_CXX_17=ON"
|
||||||
|
|
||||||
|
- name: "[C++17] AppleClang-10.0.1"
|
||||||
|
os: osx
|
||||||
|
osx_image: xcode10.2
|
||||||
|
language: cpp
|
||||||
|
env:
|
||||||
|
- SQLITE_ORM_OMITS_CODECVT: ON
|
||||||
|
- SQLITE_ORM_CXX_STANDARD: "-DSQLITE_ORM_ENABLE_CXX_17=ON"
|
||||||
|
addons:
|
||||||
|
homebrew:
|
||||||
|
packages:
|
||||||
|
- ninja
|
||||||
|
update: true
|
||||||
|
|
||||||
|
- name: "[C++17] LLVM/Clang (latest)"
|
||||||
|
os: osx
|
||||||
|
osx_image: xcode10.2
|
||||||
|
addons:
|
||||||
|
homebrew:
|
||||||
|
packages:
|
||||||
|
- llvm
|
||||||
|
- ninja
|
||||||
|
update: true
|
||||||
|
env:
|
||||||
|
- CPPFLAGS: "-I/usr/local/opt/llvm/include"
|
||||||
|
- LDFLAGS: "-L/usr/local/opt/llvm/lib -Wl,-rpath,/usr/local/opt/llvm/lib"
|
||||||
|
- CPATH: /usr/local/opt/llvm/include
|
||||||
|
- LIBRARY_PATH: /usr/local/opt/llvm/lib
|
||||||
|
- LD_LIBRARY_PATH: /usr/local/opt/llvm/lib
|
||||||
|
- CC: /usr/local/opt/llvm/bin/clang
|
||||||
|
- CXX: /usr/local/opt/llvm/bin/clang++
|
||||||
|
- SQLITE_ORM_OMITS_CODECVT: ON
|
||||||
|
- SQLITE_ORM_CXX_STANDARD: "-DSQLITE_ORM_ENABLE_CXX_17=ON"
|
||||||
|
|
||||||
|
before_install:
|
||||||
|
- if [[ ${TRAVIS_OS_NAME} == "osx" ]]; then export PATH="/usr/local/opt/coreutils/libexec/gnubin:$PATH"; fi
|
||||||
|
|
||||||
|
# scripts to run before build
|
||||||
|
before_script:
|
||||||
|
- if [[ "$CXX" == *"clang"* ]]; then clang --version ; fi
|
||||||
|
- cd ${TRAVIS_BUILD_DIR}
|
||||||
|
- mkdir compile && cd compile
|
||||||
|
- cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug ${SQLITE_ORM_CXX_STANDARD} -DSQLITE_ORM_OMITS_CODECVT="${SQLITE_ORM_OMITS_CODECVT:OFF}" ..
|
||||||
|
|
||||||
|
# build examples, and run tests (ie make & make test)
|
||||||
|
script:
|
||||||
|
- cmake --build . --config Debug -- -k 10
|
||||||
|
- ctest --verbose --output-on-failure -C Debug -j $(nproc)
|
|
@ -0,0 +1,93 @@
|
||||||
|
# note: the minimum required version needs to go hand in hand with appveyor builds,
|
||||||
|
# which is CMake 3.16 for the Visual Studio 2017 build worker image
|
||||||
|
cmake_minimum_required (VERSION 3.16)
|
||||||
|
|
||||||
|
# PACKAGE_VERSION is used by cpack scripts currently
|
||||||
|
# Both sqlite_orm_VERSION and PACKAGE_VERSION should be the same for now
|
||||||
|
|
||||||
|
set(sqlite_orm_VERSION "1.8.0")
|
||||||
|
set(PACKAGE_VERSION ${sqlite_orm_VERSION})
|
||||||
|
|
||||||
|
project("sqlite_orm" VERSION ${PACKAGE_VERSION})
|
||||||
|
|
||||||
|
# Handling C++ standard version to use
|
||||||
|
option(SQLITE_ORM_ENABLE_CXX_20 "Enable C++ 20" OFF)
|
||||||
|
option(SQLITE_ORM_ENABLE_CXX_17 "Enable C++ 17" OFF)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
if(SQLITE_ORM_ENABLE_CXX_20)
|
||||||
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
|
message(STATUS "SQLITE_ORM: Build with C++20 features")
|
||||||
|
elseif(SQLITE_ORM_ENABLE_CXX_17)
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
message(STATUS "SQLITE_ORM: Build with C++17 features")
|
||||||
|
else()
|
||||||
|
# fallback to C++14 if there is no special instruction
|
||||||
|
set(CMAKE_CXX_STANDARD 14)
|
||||||
|
message(STATUS "SQLITE_ORM: Build with C++14 features")
|
||||||
|
endif()
|
||||||
|
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||||
|
|
||||||
|
|
||||||
|
set(CMAKE_VERBOSE_MAKEFILE ON)
|
||||||
|
|
||||||
|
message(STATUS "Configuring ${PROJECT_NAME} ${sqlite_orm_VERSION}")
|
||||||
|
|
||||||
|
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake")
|
||||||
|
|
||||||
|
include(CTest)
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
add_subdirectory(dependencies)
|
||||||
|
|
||||||
|
### Main Build Targets
|
||||||
|
set(SqliteOrm_INCLUDE "${CMAKE_CURRENT_SOURCE_DIR}/include")
|
||||||
|
add_library(sqlite_orm INTERFACE)
|
||||||
|
add_library(sqlite_orm::sqlite_orm ALIAS sqlite_orm)
|
||||||
|
|
||||||
|
find_package(SQLite3 REQUIRED)
|
||||||
|
target_link_libraries(sqlite_orm INTERFACE SQLite::SQLite3)
|
||||||
|
|
||||||
|
target_sources(sqlite_orm INTERFACE $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include/sqlite_orm/sqlite_orm.h>)
|
||||||
|
|
||||||
|
target_include_directories(sqlite_orm INTERFACE $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>)
|
||||||
|
|
||||||
|
include(ucm)
|
||||||
|
|
||||||
|
if (MSVC)
|
||||||
|
string(REGEX REPLACE "/RTC(su|[1su])" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
|
||||||
|
string(REGEX REPLACE "/RTC(su|[1su])" "" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}")
|
||||||
|
string(REGEX REPLACE "/RTC(su|[1su])" "" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}")
|
||||||
|
add_compile_options(/EHsc)
|
||||||
|
if (MSVC_VERSION GREATER_EQUAL 1914)
|
||||||
|
add_compile_options(/Zc:__cplusplus)
|
||||||
|
endif()
|
||||||
|
if (MSVC_VERSION GREATER_EQUAL 1910)
|
||||||
|
# VC 2017 issues a deprecation warning for `strncpy`
|
||||||
|
add_compile_definitions(_CRT_SECURE_NO_WARNINGS)
|
||||||
|
endif()
|
||||||
|
add_compile_options(/MP) # multi-processor compilation
|
||||||
|
if (CMAKE_CXX_STANDARD GREATER 14)
|
||||||
|
add_compile_definitions(_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if ("${CMAKE_GENERATOR}" MATCHES "(Win64|x64)")
|
||||||
|
message(STATUS "Add /bigobj flag to compiler")
|
||||||
|
add_compile_options(/bigobj)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
ucm_print_flags()
|
||||||
|
message(STATUS "CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}")
|
||||||
|
|
||||||
|
# Tests
|
||||||
|
if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND BUILD_TESTING)
|
||||||
|
add_subdirectory(tests)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
option(BUILD_EXAMPLES "Build code examples" OFF)
|
||||||
|
if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND BUILD_EXAMPLES)
|
||||||
|
add_subdirectory(examples)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
### Packaging
|
||||||
|
add_subdirectory(packaging)
|
|
@ -0,0 +1,20 @@
|
||||||
|
Copyright (c) 2012-2023 Eugene Zakharov and others
|
||||||
|
|
||||||
|
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.
|
|
@ -0,0 +1,94 @@
|
||||||
|
# How to Contribute #
|
||||||
|
|
||||||
|
Thank you for your interest in contributing to the sqlite_orm project!
|
||||||
|
|
||||||
|
## GitHub pull requests ##
|
||||||
|
|
||||||
|
This is the preferred method of submitting changes. When you submit a pull request through github,
|
||||||
|
it activates the continuous integration (CI) build systems at Appveyor and Travis to build your changes
|
||||||
|
on a variety of Linux, Windows and MacOS configurations and run all the test suites. Follow these requirements
|
||||||
|
for a successful pull request:
|
||||||
|
|
||||||
|
1. All significant changes require a [github issue](https://github.com/fnc12/sqlite_orm/issues). Trivial changes such as fixing a typo or a compiler warning do not.
|
||||||
|
|
||||||
|
1. The pull request title must begin with the github issue identifier if it has an associated issue, for example:
|
||||||
|
|
||||||
|
#9999 : an example pull request title
|
||||||
|
|
||||||
|
1. Commit messages must be understandable in future by different developers and must be written in english language only:
|
||||||
|
|
||||||
|
Instructions:
|
||||||
|
|
||||||
|
1. Create a fork in your GitHub account of http://github.com/fnc12/sqlite_orm
|
||||||
|
1. Clone the fork to your development system.
|
||||||
|
1. Create a branch for your changes (best practice is following git flow pattern with issue number as branch name, e.g. feature/9999-some-feature or bugfix/9999-some-bug).
|
||||||
|
1. Modify the source to include the improvement/bugfix, and:
|
||||||
|
|
||||||
|
* Remember to provide *tests* for all submitted changes!
|
||||||
|
* Use test-driven development (TDD): add a test that will isolate the bug *before* applying the change that fixes it.
|
||||||
|
* Verify that you follow current code style on sqlite_orm.
|
||||||
|
* [*optional*] Verify that your change works on other platforms by adding a GitHub service hook to [Travis CI](http://docs.travis-ci.com/user/getting-started/#Step-one%3A-Sign-in) and [AppVeyor](http://www.appveyor.com/docs). You can use this technique to run the sqlite_orm CI jobs in your account to check your changes before they are made public. Every GitHub pull request into sqlite_orm will run the full CI build and test suite on your changes.
|
||||||
|
|
||||||
|
1. Commit and push changes to your branch (please use issue name and description as commit title, e.g. "make it perfect. (fixes #9999)").
|
||||||
|
1. Use GitHub to create a pull request going from your branch to sqlite_orm:dev. Ensure that the github issue number is at the beginning of the title of your pull request.
|
||||||
|
1. Wait for other contributors or committers to review your new addition, and for a CI build to complete.
|
||||||
|
1. Wait for a owner or collaborators to commit your patch.
|
||||||
|
|
||||||
|
## If you want to build the project locally ##
|
||||||
|
|
||||||
|
See our detailed instructions on the [CMake README](https://github.com/fnc12/sqlite_orm#usage).
|
||||||
|
|
||||||
|
## If you want to review open issues... ##
|
||||||
|
|
||||||
|
1. Review the [GitHub Pull Request Backlog](https://github.com/fnc12/sqlite_orm/pulls). Code reviews are opened to all.
|
||||||
|
|
||||||
|
## If you discovered a defect... ##
|
||||||
|
|
||||||
|
1. Check to see if the issue is already in the [github issues](https://github.com/fnc12/sqlite_orm/issues).
|
||||||
|
1. If not please create an issue describing the change you're proposing in the github issues page.
|
||||||
|
1. Contribute your code changes using the GitHub pull request method:
|
||||||
|
|
||||||
|
## GitHub recipes for Pull Requests ##
|
||||||
|
|
||||||
|
Sometimes commmitters may ask you to take actions in your pull requests. Here are some recipes that will help you accomplish those requests. These examples assume you are working on github issue 9999. You should also be familiar with the [upstream](https://help.github.com/articles/syncing-a-fork/) repository concept.
|
||||||
|
|
||||||
|
### Squash your changes ###
|
||||||
|
|
||||||
|
If you have commits with adding code which is removed in a different commit within the same PR then please squash all commits to remove unnecessary add commits.
|
||||||
|
|
||||||
|
1. Use the command ``git log`` to identify how many commits you made since you began.
|
||||||
|
2. Use the command ``git rebase -i HEAD~N`` where N is the number of commits.
|
||||||
|
3. Leave "pull" in the first line.
|
||||||
|
4. Change all other lines from "pull" to "fixup".
|
||||||
|
5. All your changes are now in a single commit.
|
||||||
|
|
||||||
|
If you already have a pull request outstanding, you will need to do a "force push" to overwrite it since you changed your commit history:
|
||||||
|
|
||||||
|
git push -u origin feature/9999-make-perfect --force
|
||||||
|
|
||||||
|
A more detailed walkthrough of a squash can be found at [Git Ready](http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html).
|
||||||
|
|
||||||
|
### Rebase your pull request ###
|
||||||
|
|
||||||
|
If your pull request has a conflict with dev, it needs to be rebased:
|
||||||
|
|
||||||
|
git checkout feature/9999-make-perfect
|
||||||
|
git rebase upstream dev
|
||||||
|
(resolve any conflicts, make sure it builds)
|
||||||
|
git push -u origin feature/9999-make-perfect --force
|
||||||
|
|
||||||
|
### Fix a bad merge ###
|
||||||
|
|
||||||
|
If your pull request contains commits that are not yours, then you should use the following technique to fix the bad merge in your branch:
|
||||||
|
|
||||||
|
git checkout dev
|
||||||
|
git pull upstream dev
|
||||||
|
git checkout -b feature/9999-make-perfect-take-2
|
||||||
|
git cherry-pick ...
|
||||||
|
(pick only your commits from your original pull request in ascending chronological order)
|
||||||
|
squash your changes to a single commit if there is more than one (see above)
|
||||||
|
git push -u origin feature/9999-make-perfect-take-2:feature/9999-make-perfect
|
||||||
|
|
||||||
|
This procedure will apply only your commits in order to the current dev, then you will squash them to a single commit, and then you force push your local feature/9999-make-perfect-take-2 into remote feature/9999-make-perfect which represents your pull request, replacing all the commits with the new one.
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,661 @@
|
||||||
|
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 19 November 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The GNU Affero General Public License is a free, copyleft license for
|
||||||
|
software and other kinds of works, specifically designed to ensure
|
||||||
|
cooperation with the community in the case of network server software.
|
||||||
|
|
||||||
|
The licenses for most software and other practical works are designed
|
||||||
|
to take away your freedom to share and change the works. By contrast,
|
||||||
|
our General Public Licenses are intended to guarantee your freedom to
|
||||||
|
share and change all versions of a program--to make sure it remains free
|
||||||
|
software for all its users.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
them if you wish), that you receive source code or can get it if you
|
||||||
|
want it, that you can change the software or use pieces of it in new
|
||||||
|
free programs, and that you know you can do these things.
|
||||||
|
|
||||||
|
Developers that use our General Public Licenses protect your rights
|
||||||
|
with two steps: (1) assert copyright on the software, and (2) offer
|
||||||
|
you this License which gives you legal permission to copy, distribute
|
||||||
|
and/or modify the software.
|
||||||
|
|
||||||
|
A secondary benefit of defending all users' freedom is that
|
||||||
|
improvements made in alternate versions of the program, if they
|
||||||
|
receive widespread use, become available for other developers to
|
||||||
|
incorporate. Many developers of free software are heartened and
|
||||||
|
encouraged by the resulting cooperation. However, in the case of
|
||||||
|
software used on network servers, this result may fail to come about.
|
||||||
|
The GNU General Public License permits making a modified version and
|
||||||
|
letting the public access it on a server without ever releasing its
|
||||||
|
source code to the public.
|
||||||
|
|
||||||
|
The GNU Affero General Public License is designed specifically to
|
||||||
|
ensure that, in such cases, the modified source code becomes available
|
||||||
|
to the community. It requires the operator of a network server to
|
||||||
|
provide the source code of the modified version running there to the
|
||||||
|
users of that server. Therefore, public use of a modified version, on
|
||||||
|
a publicly accessible server, gives the public access to the source
|
||||||
|
code of the modified version.
|
||||||
|
|
||||||
|
An older license, called the Affero General Public License and
|
||||||
|
published by Affero, was designed to accomplish similar goals. This is
|
||||||
|
a different license, not a version of the Affero GPL, but Affero has
|
||||||
|
released a new version of the Affero GPL which permits relicensing under
|
||||||
|
this license.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
0. Definitions.
|
||||||
|
|
||||||
|
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||||
|
|
||||||
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
|
works, such as semiconductor masks.
|
||||||
|
|
||||||
|
"The Program" refers to any copyrightable work licensed under this
|
||||||
|
License. Each licensee is addressed as "you". "Licensees" and
|
||||||
|
"recipients" may be individuals or organizations.
|
||||||
|
|
||||||
|
To "modify" a work means to copy from or adapt all or part of the work
|
||||||
|
in a fashion requiring copyright permission, other than the making of an
|
||||||
|
exact copy. The resulting work is called a "modified version" of the
|
||||||
|
earlier work or a work "based on" the earlier work.
|
||||||
|
|
||||||
|
A "covered work" means either the unmodified Program or a work based
|
||||||
|
on the Program.
|
||||||
|
|
||||||
|
To "propagate" a work means to do anything with it that, without
|
||||||
|
permission, would make you directly or secondarily liable for
|
||||||
|
infringement under applicable copyright law, except executing it on a
|
||||||
|
computer or modifying a private copy. Propagation includes copying,
|
||||||
|
distribution (with or without modification), making available to the
|
||||||
|
public, and in some countries other activities as well.
|
||||||
|
|
||||||
|
To "convey" a work means any kind of propagation that enables other
|
||||||
|
parties to make or receive copies. Mere interaction with a user through
|
||||||
|
a computer network, with no transfer of a copy, is not conveying.
|
||||||
|
|
||||||
|
An interactive user interface displays "Appropriate Legal Notices"
|
||||||
|
to the extent that it includes a convenient and prominently visible
|
||||||
|
feature that (1) displays an appropriate copyright notice, and (2)
|
||||||
|
tells the user that there is no warranty for the work (except to the
|
||||||
|
extent that warranties are provided), that licensees may convey the
|
||||||
|
work under this License, and how to view a copy of this License. If
|
||||||
|
the interface presents a list of user commands or options, such as a
|
||||||
|
menu, a prominent item in the list meets this criterion.
|
||||||
|
|
||||||
|
1. Source Code.
|
||||||
|
|
||||||
|
The "source code" for a work means the preferred form of the work
|
||||||
|
for making modifications to it. "Object code" means any non-source
|
||||||
|
form of a work.
|
||||||
|
|
||||||
|
A "Standard Interface" means an interface that either is an official
|
||||||
|
standard defined by a recognized standards body, or, in the case of
|
||||||
|
interfaces specified for a particular programming language, one that
|
||||||
|
is widely used among developers working in that language.
|
||||||
|
|
||||||
|
The "System Libraries" of an executable work include anything, other
|
||||||
|
than the work as a whole, that (a) is included in the normal form of
|
||||||
|
packaging a Major Component, but which is not part of that Major
|
||||||
|
Component, and (b) serves only to enable use of the work with that
|
||||||
|
Major Component, or to implement a Standard Interface for which an
|
||||||
|
implementation is available to the public in source code form. A
|
||||||
|
"Major Component", in this context, means a major essential component
|
||||||
|
(kernel, window system, and so on) of the specific operating system
|
||||||
|
(if any) on which the executable work runs, or a compiler used to
|
||||||
|
produce the work, or an object code interpreter used to run it.
|
||||||
|
|
||||||
|
The "Corresponding Source" for a work in object code form means all
|
||||||
|
the source code needed to generate, install, and (for an executable
|
||||||
|
work) run the object code and to modify the work, including scripts to
|
||||||
|
control those activities. However, it does not include the work's
|
||||||
|
System Libraries, or general-purpose tools or generally available free
|
||||||
|
programs which are used unmodified in performing those activities but
|
||||||
|
which are not part of the work. For example, Corresponding Source
|
||||||
|
includes interface definition files associated with source files for
|
||||||
|
the work, and the source code for shared libraries and dynamically
|
||||||
|
linked subprograms that the work is specifically designed to require,
|
||||||
|
such as by intimate data communication or control flow between those
|
||||||
|
subprograms and other parts of the work.
|
||||||
|
|
||||||
|
The Corresponding Source need not include anything that users
|
||||||
|
can regenerate automatically from other parts of the Corresponding
|
||||||
|
Source.
|
||||||
|
|
||||||
|
The Corresponding Source for a work in source code form is that
|
||||||
|
same work.
|
||||||
|
|
||||||
|
2. Basic Permissions.
|
||||||
|
|
||||||
|
All rights granted under this License are granted for the term of
|
||||||
|
copyright on the Program, and are irrevocable provided the stated
|
||||||
|
conditions are met. This License explicitly affirms your unlimited
|
||||||
|
permission to run the unmodified Program. The output from running a
|
||||||
|
covered work is covered by this License only if the output, given its
|
||||||
|
content, constitutes a covered work. This License acknowledges your
|
||||||
|
rights of fair use or other equivalent, as provided by copyright law.
|
||||||
|
|
||||||
|
You may make, run and propagate covered works that you do not
|
||||||
|
convey, without conditions so long as your license otherwise remains
|
||||||
|
in force. You may convey covered works to others for the sole purpose
|
||||||
|
of having them make modifications exclusively for you, or provide you
|
||||||
|
with facilities for running those works, provided that you comply with
|
||||||
|
the terms of this License in conveying all material for which you do
|
||||||
|
not control copyright. Those thus making or running the covered works
|
||||||
|
for you must do so exclusively on your behalf, under your direction
|
||||||
|
and control, on terms that prohibit them from making any copies of
|
||||||
|
your copyrighted material outside their relationship with you.
|
||||||
|
|
||||||
|
Conveying under any other circumstances is permitted solely under
|
||||||
|
the conditions stated below. Sublicensing is not allowed; section 10
|
||||||
|
makes it unnecessary.
|
||||||
|
|
||||||
|
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||||
|
|
||||||
|
No covered work shall be deemed part of an effective technological
|
||||||
|
measure under any applicable law fulfilling obligations under article
|
||||||
|
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||||
|
similar laws prohibiting or restricting circumvention of such
|
||||||
|
measures.
|
||||||
|
|
||||||
|
When you convey a covered work, you waive any legal power to forbid
|
||||||
|
circumvention of technological measures to the extent such circumvention
|
||||||
|
is effected by exercising rights under this License with respect to
|
||||||
|
the covered work, and you disclaim any intention to limit operation or
|
||||||
|
modification of the work as a means of enforcing, against the work's
|
||||||
|
users, your or third parties' legal rights to forbid circumvention of
|
||||||
|
technological measures.
|
||||||
|
|
||||||
|
4. Conveying Verbatim Copies.
|
||||||
|
|
||||||
|
You may convey verbatim copies of the Program's source code as you
|
||||||
|
receive it, in any medium, provided that you conspicuously and
|
||||||
|
appropriately publish on each copy an appropriate copyright notice;
|
||||||
|
keep intact all notices stating that this License and any
|
||||||
|
non-permissive terms added in accord with section 7 apply to the code;
|
||||||
|
keep intact all notices of the absence of any warranty; and give all
|
||||||
|
recipients a copy of this License along with the Program.
|
||||||
|
|
||||||
|
You may charge any price or no price for each copy that you convey,
|
||||||
|
and you may offer support or warranty protection for a fee.
|
||||||
|
|
||||||
|
5. Conveying Modified Source Versions.
|
||||||
|
|
||||||
|
You may convey a work based on the Program, or the modifications to
|
||||||
|
produce it from the Program, in the form of source code under the
|
||||||
|
terms of section 4, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) The work must carry prominent notices stating that you modified
|
||||||
|
it, and giving a relevant date.
|
||||||
|
|
||||||
|
b) The work must carry prominent notices stating that it is
|
||||||
|
released under this License and any conditions added under section
|
||||||
|
7. This requirement modifies the requirement in section 4 to
|
||||||
|
"keep intact all notices".
|
||||||
|
|
||||||
|
c) You must license the entire work, as a whole, under this
|
||||||
|
License to anyone who comes into possession of a copy. This
|
||||||
|
License will therefore apply, along with any applicable section 7
|
||||||
|
additional terms, to the whole of the work, and all its parts,
|
||||||
|
regardless of how they are packaged. This License gives no
|
||||||
|
permission to license the work in any other way, but it does not
|
||||||
|
invalidate such permission if you have separately received it.
|
||||||
|
|
||||||
|
d) If the work has interactive user interfaces, each must display
|
||||||
|
Appropriate Legal Notices; however, if the Program has interactive
|
||||||
|
interfaces that do not display Appropriate Legal Notices, your
|
||||||
|
work need not make them do so.
|
||||||
|
|
||||||
|
A compilation of a covered work with other separate and independent
|
||||||
|
works, which are not by their nature extensions of the covered work,
|
||||||
|
and which are not combined with it such as to form a larger program,
|
||||||
|
in or on a volume of a storage or distribution medium, is called an
|
||||||
|
"aggregate" if the compilation and its resulting copyright are not
|
||||||
|
used to limit the access or legal rights of the compilation's users
|
||||||
|
beyond what the individual works permit. Inclusion of a covered work
|
||||||
|
in an aggregate does not cause this License to apply to the other
|
||||||
|
parts of the aggregate.
|
||||||
|
|
||||||
|
6. Conveying Non-Source Forms.
|
||||||
|
|
||||||
|
You may convey a covered work in object code form under the terms
|
||||||
|
of sections 4 and 5, provided that you also convey the
|
||||||
|
machine-readable Corresponding Source under the terms of this License,
|
||||||
|
in one of these ways:
|
||||||
|
|
||||||
|
a) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by the
|
||||||
|
Corresponding Source fixed on a durable physical medium
|
||||||
|
customarily used for software interchange.
|
||||||
|
|
||||||
|
b) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by a
|
||||||
|
written offer, valid for at least three years and valid for as
|
||||||
|
long as you offer spare parts or customer support for that product
|
||||||
|
model, to give anyone who possesses the object code either (1) a
|
||||||
|
copy of the Corresponding Source for all the software in the
|
||||||
|
product that is covered by this License, on a durable physical
|
||||||
|
medium customarily used for software interchange, for a price no
|
||||||
|
more than your reasonable cost of physically performing this
|
||||||
|
conveying of source, or (2) access to copy the
|
||||||
|
Corresponding Source from a network server at no charge.
|
||||||
|
|
||||||
|
c) Convey individual copies of the object code with a copy of the
|
||||||
|
written offer to provide the Corresponding Source. This
|
||||||
|
alternative is allowed only occasionally and noncommercially, and
|
||||||
|
only if you received the object code with such an offer, in accord
|
||||||
|
with subsection 6b.
|
||||||
|
|
||||||
|
d) Convey the object code by offering access from a designated
|
||||||
|
place (gratis or for a charge), and offer equivalent access to the
|
||||||
|
Corresponding Source in the same way through the same place at no
|
||||||
|
further charge. You need not require recipients to copy the
|
||||||
|
Corresponding Source along with the object code. If the place to
|
||||||
|
copy the object code is a network server, the Corresponding Source
|
||||||
|
may be on a different server (operated by you or a third party)
|
||||||
|
that supports equivalent copying facilities, provided you maintain
|
||||||
|
clear directions next to the object code saying where to find the
|
||||||
|
Corresponding Source. Regardless of what server hosts the
|
||||||
|
Corresponding Source, you remain obligated to ensure that it is
|
||||||
|
available for as long as needed to satisfy these requirements.
|
||||||
|
|
||||||
|
e) Convey the object code using peer-to-peer transmission, provided
|
||||||
|
you inform other peers where the object code and Corresponding
|
||||||
|
Source of the work are being offered to the general public at no
|
||||||
|
charge under subsection 6d.
|
||||||
|
|
||||||
|
A separable portion of the object code, whose source code is excluded
|
||||||
|
from the Corresponding Source as a System Library, need not be
|
||||||
|
included in conveying the object code work.
|
||||||
|
|
||||||
|
A "User Product" is either (1) a "consumer product", which means any
|
||||||
|
tangible personal property which is normally used for personal, family,
|
||||||
|
or household purposes, or (2) anything designed or sold for incorporation
|
||||||
|
into a dwelling. In determining whether a product is a consumer product,
|
||||||
|
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||||
|
product received by a particular user, "normally used" refers to a
|
||||||
|
typical or common use of that class of product, regardless of the status
|
||||||
|
of the particular user or of the way in which the particular user
|
||||||
|
actually uses, or expects or is expected to use, the product. A product
|
||||||
|
is a consumer product regardless of whether the product has substantial
|
||||||
|
commercial, industrial or non-consumer uses, unless such uses represent
|
||||||
|
the only significant mode of use of the product.
|
||||||
|
|
||||||
|
"Installation Information" for a User Product means any methods,
|
||||||
|
procedures, authorization keys, or other information required to install
|
||||||
|
and execute modified versions of a covered work in that User Product from
|
||||||
|
a modified version of its Corresponding Source. The information must
|
||||||
|
suffice to ensure that the continued functioning of the modified object
|
||||||
|
code is in no case prevented or interfered with solely because
|
||||||
|
modification has been made.
|
||||||
|
|
||||||
|
If you convey an object code work under this section in, or with, or
|
||||||
|
specifically for use in, a User Product, and the conveying occurs as
|
||||||
|
part of a transaction in which the right of possession and use of the
|
||||||
|
User Product is transferred to the recipient in perpetuity or for a
|
||||||
|
fixed term (regardless of how the transaction is characterized), the
|
||||||
|
Corresponding Source conveyed under this section must be accompanied
|
||||||
|
by the Installation Information. But this requirement does not apply
|
||||||
|
if neither you nor any third party retains the ability to install
|
||||||
|
modified object code on the User Product (for example, the work has
|
||||||
|
been installed in ROM).
|
||||||
|
|
||||||
|
The requirement to provide Installation Information does not include a
|
||||||
|
requirement to continue to provide support service, warranty, or updates
|
||||||
|
for a work that has been modified or installed by the recipient, or for
|
||||||
|
the User Product in which it has been modified or installed. Access to a
|
||||||
|
network may be denied when the modification itself materially and
|
||||||
|
adversely affects the operation of the network or violates the rules and
|
||||||
|
protocols for communication across the network.
|
||||||
|
|
||||||
|
Corresponding Source conveyed, and Installation Information provided,
|
||||||
|
in accord with this section must be in a format that is publicly
|
||||||
|
documented (and with an implementation available to the public in
|
||||||
|
source code form), and must require no special password or key for
|
||||||
|
unpacking, reading or copying.
|
||||||
|
|
||||||
|
7. Additional Terms.
|
||||||
|
|
||||||
|
"Additional permissions" are terms that supplement the terms of this
|
||||||
|
License by making exceptions from one or more of its conditions.
|
||||||
|
Additional permissions that are applicable to the entire Program shall
|
||||||
|
be treated as though they were included in this License, to the extent
|
||||||
|
that they are valid under applicable law. If additional permissions
|
||||||
|
apply only to part of the Program, that part may be used separately
|
||||||
|
under those permissions, but the entire Program remains governed by
|
||||||
|
this License without regard to the additional permissions.
|
||||||
|
|
||||||
|
When you convey a copy of a covered work, you may at your option
|
||||||
|
remove any additional permissions from that copy, or from any part of
|
||||||
|
it. (Additional permissions may be written to require their own
|
||||||
|
removal in certain cases when you modify the work.) You may place
|
||||||
|
additional permissions on material, added by you to a covered work,
|
||||||
|
for which you have or can give appropriate copyright permission.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, for material you
|
||||||
|
add to a covered work, you may (if authorized by the copyright holders of
|
||||||
|
that material) supplement the terms of this License with terms:
|
||||||
|
|
||||||
|
a) Disclaiming warranty or limiting liability differently from the
|
||||||
|
terms of sections 15 and 16 of this License; or
|
||||||
|
|
||||||
|
b) Requiring preservation of specified reasonable legal notices or
|
||||||
|
author attributions in that material or in the Appropriate Legal
|
||||||
|
Notices displayed by works containing it; or
|
||||||
|
|
||||||
|
c) Prohibiting misrepresentation of the origin of that material, or
|
||||||
|
requiring that modified versions of such material be marked in
|
||||||
|
reasonable ways as different from the original version; or
|
||||||
|
|
||||||
|
d) Limiting the use for publicity purposes of names of licensors or
|
||||||
|
authors of the material; or
|
||||||
|
|
||||||
|
e) Declining to grant rights under trademark law for use of some
|
||||||
|
trade names, trademarks, or service marks; or
|
||||||
|
|
||||||
|
f) Requiring indemnification of licensors and authors of that
|
||||||
|
material by anyone who conveys the material (or modified versions of
|
||||||
|
it) with contractual assumptions of liability to the recipient, for
|
||||||
|
any liability that these contractual assumptions directly impose on
|
||||||
|
those licensors and authors.
|
||||||
|
|
||||||
|
All other non-permissive additional terms are considered "further
|
||||||
|
restrictions" within the meaning of section 10. If the Program as you
|
||||||
|
received it, or any part of it, contains a notice stating that it is
|
||||||
|
governed by this License along with a term that is a further
|
||||||
|
restriction, you may remove that term. If a license document contains
|
||||||
|
a further restriction but permits relicensing or conveying under this
|
||||||
|
License, you may add to a covered work material governed by the terms
|
||||||
|
of that license document, provided that the further restriction does
|
||||||
|
not survive such relicensing or conveying.
|
||||||
|
|
||||||
|
If you add terms to a covered work in accord with this section, you
|
||||||
|
must place, in the relevant source files, a statement of the
|
||||||
|
additional terms that apply to those files, or a notice indicating
|
||||||
|
where to find the applicable terms.
|
||||||
|
|
||||||
|
Additional terms, permissive or non-permissive, may be stated in the
|
||||||
|
form of a separately written license, or stated as exceptions;
|
||||||
|
the above requirements apply either way.
|
||||||
|
|
||||||
|
8. Termination.
|
||||||
|
|
||||||
|
You may not propagate or modify a covered work except as expressly
|
||||||
|
provided under this License. Any attempt otherwise to propagate or
|
||||||
|
modify it is void, and will automatically terminate your rights under
|
||||||
|
this License (including any patent licenses granted under the third
|
||||||
|
paragraph of section 11).
|
||||||
|
|
||||||
|
However, if you cease all violation of this License, then your
|
||||||
|
license from a particular copyright holder is reinstated (a)
|
||||||
|
provisionally, unless and until the copyright holder explicitly and
|
||||||
|
finally terminates your license, and (b) permanently, if the copyright
|
||||||
|
holder fails to notify you of the violation by some reasonable means
|
||||||
|
prior to 60 days after the cessation.
|
||||||
|
|
||||||
|
Moreover, your license from a particular copyright holder is
|
||||||
|
reinstated permanently if the copyright holder notifies you of the
|
||||||
|
violation by some reasonable means, this is the first time you have
|
||||||
|
received notice of violation of this License (for any work) from that
|
||||||
|
copyright holder, and you cure the violation prior to 30 days after
|
||||||
|
your receipt of the notice.
|
||||||
|
|
||||||
|
Termination of your rights under this section does not terminate the
|
||||||
|
licenses of parties who have received copies or rights from you under
|
||||||
|
this License. If your rights have been terminated and not permanently
|
||||||
|
reinstated, you do not qualify to receive new licenses for the same
|
||||||
|
material under section 10.
|
||||||
|
|
||||||
|
9. Acceptance Not Required for Having Copies.
|
||||||
|
|
||||||
|
You are not required to accept this License in order to receive or
|
||||||
|
run a copy of the Program. Ancillary propagation of a covered work
|
||||||
|
occurring solely as a consequence of using peer-to-peer transmission
|
||||||
|
to receive a copy likewise does not require acceptance. However,
|
||||||
|
nothing other than this License grants you permission to propagate or
|
||||||
|
modify any covered work. These actions infringe copyright if you do
|
||||||
|
not accept this License. Therefore, by modifying or propagating a
|
||||||
|
covered work, you indicate your acceptance of this License to do so.
|
||||||
|
|
||||||
|
10. Automatic Licensing of Downstream Recipients.
|
||||||
|
|
||||||
|
Each time you convey a covered work, the recipient automatically
|
||||||
|
receives a license from the original licensors, to run, modify and
|
||||||
|
propagate that work, subject to this License. You are not responsible
|
||||||
|
for enforcing compliance by third parties with this License.
|
||||||
|
|
||||||
|
An "entity transaction" is a transaction transferring control of an
|
||||||
|
organization, or substantially all assets of one, or subdividing an
|
||||||
|
organization, or merging organizations. If propagation of a covered
|
||||||
|
work results from an entity transaction, each party to that
|
||||||
|
transaction who receives a copy of the work also receives whatever
|
||||||
|
licenses to the work the party's predecessor in interest had or could
|
||||||
|
give under the previous paragraph, plus a right to possession of the
|
||||||
|
Corresponding Source of the work from the predecessor in interest, if
|
||||||
|
the predecessor has it or can get it with reasonable efforts.
|
||||||
|
|
||||||
|
You may not impose any further restrictions on the exercise of the
|
||||||
|
rights granted or affirmed under this License. For example, you may
|
||||||
|
not impose a license fee, royalty, or other charge for exercise of
|
||||||
|
rights granted under this License, and you may not initiate litigation
|
||||||
|
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||||
|
any patent claim is infringed by making, using, selling, offering for
|
||||||
|
sale, or importing the Program or any portion of it.
|
||||||
|
|
||||||
|
11. Patents.
|
||||||
|
|
||||||
|
A "contributor" is a copyright holder who authorizes use under this
|
||||||
|
License of the Program or a work on which the Program is based. The
|
||||||
|
work thus licensed is called the contributor's "contributor version".
|
||||||
|
|
||||||
|
A contributor's "essential patent claims" are all patent claims
|
||||||
|
owned or controlled by the contributor, whether already acquired or
|
||||||
|
hereafter acquired, that would be infringed by some manner, permitted
|
||||||
|
by this License, of making, using, or selling its contributor version,
|
||||||
|
but do not include claims that would be infringed only as a
|
||||||
|
consequence of further modification of the contributor version. For
|
||||||
|
purposes of this definition, "control" includes the right to grant
|
||||||
|
patent sublicenses in a manner consistent with the requirements of
|
||||||
|
this License.
|
||||||
|
|
||||||
|
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||||
|
patent license under the contributor's essential patent claims, to
|
||||||
|
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||||
|
propagate the contents of its contributor version.
|
||||||
|
|
||||||
|
In the following three paragraphs, a "patent license" is any express
|
||||||
|
agreement or commitment, however denominated, not to enforce a patent
|
||||||
|
(such as an express permission to practice a patent or covenant not to
|
||||||
|
sue for patent infringement). To "grant" such a patent license to a
|
||||||
|
party means to make such an agreement or commitment not to enforce a
|
||||||
|
patent against the party.
|
||||||
|
|
||||||
|
If you convey a covered work, knowingly relying on a patent license,
|
||||||
|
and the Corresponding Source of the work is not available for anyone
|
||||||
|
to copy, free of charge and under the terms of this License, through a
|
||||||
|
publicly available network server or other readily accessible means,
|
||||||
|
then you must either (1) cause the Corresponding Source to be so
|
||||||
|
available, or (2) arrange to deprive yourself of the benefit of the
|
||||||
|
patent license for this particular work, or (3) arrange, in a manner
|
||||||
|
consistent with the requirements of this License, to extend the patent
|
||||||
|
license to downstream recipients. "Knowingly relying" means you have
|
||||||
|
actual knowledge that, but for the patent license, your conveying the
|
||||||
|
covered work in a country, or your recipient's use of the covered work
|
||||||
|
in a country, would infringe one or more identifiable patents in that
|
||||||
|
country that you have reason to believe are valid.
|
||||||
|
|
||||||
|
If, pursuant to or in connection with a single transaction or
|
||||||
|
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||||
|
covered work, and grant a patent license to some of the parties
|
||||||
|
receiving the covered work authorizing them to use, propagate, modify
|
||||||
|
or convey a specific copy of the covered work, then the patent license
|
||||||
|
you grant is automatically extended to all recipients of the covered
|
||||||
|
work and works based on it.
|
||||||
|
|
||||||
|
A patent license is "discriminatory" if it does not include within
|
||||||
|
the scope of its coverage, prohibits the exercise of, or is
|
||||||
|
conditioned on the non-exercise of one or more of the rights that are
|
||||||
|
specifically granted under this License. You may not convey a covered
|
||||||
|
work if you are a party to an arrangement with a third party that is
|
||||||
|
in the business of distributing software, under which you make payment
|
||||||
|
to the third party based on the extent of your activity of conveying
|
||||||
|
the work, and under which the third party grants, to any of the
|
||||||
|
parties who would receive the covered work from you, a discriminatory
|
||||||
|
patent license (a) in connection with copies of the covered work
|
||||||
|
conveyed by you (or copies made from those copies), or (b) primarily
|
||||||
|
for and in connection with specific products or compilations that
|
||||||
|
contain the covered work, unless you entered into that arrangement,
|
||||||
|
or that patent license was granted, prior to 28 March 2007.
|
||||||
|
|
||||||
|
Nothing in this License shall be construed as excluding or limiting
|
||||||
|
any implied license or other defenses to infringement that may
|
||||||
|
otherwise be available to you under applicable patent law.
|
||||||
|
|
||||||
|
12. No Surrender of Others' Freedom.
|
||||||
|
|
||||||
|
If conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot convey a
|
||||||
|
covered work so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you may
|
||||||
|
not convey it at all. For example, if you agree to terms that obligate you
|
||||||
|
to collect a royalty for further conveying from those to whom you convey
|
||||||
|
the Program, the only way you could satisfy both those terms and this
|
||||||
|
License would be to refrain entirely from conveying the Program.
|
||||||
|
|
||||||
|
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, if you modify the
|
||||||
|
Program, your modified version must prominently offer all users
|
||||||
|
interacting with it remotely through a computer network (if your version
|
||||||
|
supports such interaction) an opportunity to receive the Corresponding
|
||||||
|
Source of your version by providing access to the Corresponding Source
|
||||||
|
from a network server at no charge, through some standard or customary
|
||||||
|
means of facilitating copying of software. This Corresponding Source
|
||||||
|
shall include the Corresponding Source for any work covered by version 3
|
||||||
|
of the GNU General Public License that is incorporated pursuant to the
|
||||||
|
following paragraph.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, you have
|
||||||
|
permission to link or combine any covered work with a work licensed
|
||||||
|
under version 3 of the GNU General Public License into a single
|
||||||
|
combined work, and to convey the resulting work. The terms of this
|
||||||
|
License will continue to apply to the part which is the covered work,
|
||||||
|
but the work with which it is combined will remain governed by version
|
||||||
|
3 of the GNU General Public License.
|
||||||
|
|
||||||
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
|
the GNU Affero General Public License from time to time. Such new versions
|
||||||
|
will be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Program specifies that a certain numbered version of the GNU Affero General
|
||||||
|
Public License "or any later version" applies to it, you have the
|
||||||
|
option of following the terms and conditions either of that numbered
|
||||||
|
version or of any later version published by the Free Software
|
||||||
|
Foundation. If the Program does not specify a version number of the
|
||||||
|
GNU Affero General Public License, you may choose any version ever published
|
||||||
|
by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Program specifies that a proxy can decide which future
|
||||||
|
versions of the GNU Affero General Public License can be used, that proxy's
|
||||||
|
public statement of acceptance of a version permanently authorizes you
|
||||||
|
to choose that version for the Program.
|
||||||
|
|
||||||
|
Later license versions may give you additional or different
|
||||||
|
permissions. However, no additional obligations are imposed on any
|
||||||
|
author or copyright holder as a result of your choosing to follow a
|
||||||
|
later version.
|
||||||
|
|
||||||
|
15. Disclaimer of Warranty.
|
||||||
|
|
||||||
|
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||||
|
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||||
|
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||||
|
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||||
|
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||||
|
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
16. Limitation of Liability.
|
||||||
|
|
||||||
|
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||||
|
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||||
|
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||||
|
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||||
|
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||||
|
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||||
|
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||||
|
SUCH DAMAGES.
|
||||||
|
|
||||||
|
17. Interpretation of Sections 15 and 16.
|
||||||
|
|
||||||
|
If the disclaimer of warranty and limitation of liability provided
|
||||||
|
above cannot be given local legal effect according to their terms,
|
||||||
|
reviewing courts shall apply local law that most closely approximates
|
||||||
|
an absolute waiver of all civil liability in connection with the
|
||||||
|
Program, unless a warranty or assumption of liability accompanies a
|
||||||
|
copy of the Program in return for a fee.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
state the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published
|
||||||
|
by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If your software can interact with users remotely through a computer
|
||||||
|
network, you should also make sure that it provides a way for users to
|
||||||
|
get its source. For example, if your program is a web application, its
|
||||||
|
interface could display a "Source" link that leads users to an archive
|
||||||
|
of the code. There are many ways you could offer source, and different
|
||||||
|
solutions will be better for different programs; see section 13 for the
|
||||||
|
specific requirements.
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
|
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||||
|
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||||
|
<https://www.gnu.org/licenses/>.
|
|
@ -0,0 +1,813 @@
|
||||||
|
<p align="center">
|
||||||
|
<img src="https://github.com/fnc12/sqlite_orm/blob/master/logo.png" alt="Sublime's custom image" width="557"/>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
[![C++](https://img.shields.io/badge/c++-%2300599C.svg?style=for-the-badge&logo=c%2B%2B&logoColor=white)](https://en.cppreference.com/w/)
|
||||||
|
[![SQLite](https://img.shields.io/badge/sqlite-%2307405e.svg?style=for-the-badge&logo=sqlite&logoColor=white)](https://www.sqlite.org/index.html)
|
||||||
|
[![GitHub Actions](https://img.shields.io/badge/githubactions-%232671E5.svg?style=for-the-badge&logo=githubactions&logoColor=white)](https://github.com/fnc12/sqlite_orm/actions)
|
||||||
|
[![CMake](https://img.shields.io/badge/CMake-%23008FBA.svg?style=for-the-badge&logo=cmake&logoColor=white)](https://github.com/fnc12/sqlite_orm/blob/dev/CMakeLists.txt)
|
||||||
|
[![Stack Overflow](https://img.shields.io/badge/-Stackoverflow-FE7A16?style=for-the-badge&logo=stack-overflow&logoColor=white)](https://stackoverflow.com/search?q=sqlite_orm)
|
||||||
|
[![PayPal](https://img.shields.io/badge/PayPal-00457C?style=for-the-badge&logo=paypal&logoColor=white)](https://paypal.me/fnc12)
|
||||||
|
[![Twitter](https://img.shields.io/badge/sqlite_orm-%231DA1F2.svg?style=for-the-badge&logo=Twitter&logoColor=white)](https://twitter.com/sqlite_orm)
|
||||||
|
[![Patreon](https://img.shields.io/badge/Patreon-F96854?style=for-the-badge&logo=patreon&logoColor=white)](https://patreon.com/fnc12)
|
||||||
|
|
||||||
|
# SQLite ORM
|
||||||
|
SQLite ORM light header only library for modern C++. Please read the license precisely. The project has AGPL license for open source project and MIT license after purchasing it for 50$ (using [PayPal](https://paypal.me/fnc12) or any different way (contact using email fnc12@me.com)).
|
||||||
|
|
||||||
|
# Status
|
||||||
|
| Branch | Travis | Appveyor |
|
||||||
|
| :----- | :----- | :------- |
|
||||||
|
| [`master`](https://github.com/fnc12/sqlite_orm/tree/master) | [![Build Status](https://travis-ci.org/fnc12/sqlite_orm.svg?branch=master)](https://travis-ci.org/fnc12/sqlite_orm) | [![Build status](https://ci.appveyor.com/api/projects/status/github/fnc12/sqlite_orm?branch=master&svg=true)](https://ci.appveyor.com/project/fnc12/sqlite-orm/history) | | | [![Website](https://img.shields.io/badge/official-website-brightgreen.svg)](https://github.com/fnc12/sqlite_orm/) |
|
||||||
|
| [`dev`](https://github.com/fnc12/sqlite_orm/tree/dev) | [![Build Status](https://travis-ci.org/fnc12/sqlite_orm.svg?branch=dev)](https://travis-ci.org/fnc12/sqlite_orm) | [![Build status](https://ci.appveyor.com/api/projects/status/github/fnc12/sqlite_orm?branch=dev&svg=true)](https://ci.appveyor.com/project/fnc12/sqlite-orm/history) | | | [![Website](https://img.shields.io/badge/official-website-brightgreen.svg)](https://github.com/fnc12/sqlite_orm/tree/dev) |
|
||||||
|
|
||||||
|
# Advantages
|
||||||
|
|
||||||
|
* **No raw string queries**
|
||||||
|
* **Intuitive syntax**
|
||||||
|
* **Comfortable interface - one code line per single query**
|
||||||
|
* **Built with modern C++14 features (no macros and external scripts)**
|
||||||
|
* **CRUD support**
|
||||||
|
* **Pure select query support**
|
||||||
|
* **Prepared statements support**
|
||||||
|
* **UNION, EXCEPT and INTERSECT support**
|
||||||
|
* **STL compatible**
|
||||||
|
* **Custom types binding support**
|
||||||
|
* **BLOB support** - maps to `std::vector<char>` or one can bind your custom type
|
||||||
|
* **FOREIGN KEY support**
|
||||||
|
* **Composite key support**
|
||||||
|
* **JOIN support**
|
||||||
|
* **Transactions support**
|
||||||
|
* **Migrations functionality**
|
||||||
|
* **Powerful conditions**
|
||||||
|
* **ORDER BY and LIMIT, OFFSET support**
|
||||||
|
* **GROUP BY / DISTINCT support**
|
||||||
|
* **INDEX support**
|
||||||
|
* **Follows single responsibility principle** - no need write code inside your data model classes
|
||||||
|
* **Easy integration** - single header only lib.
|
||||||
|
* **The only dependency** - libsqlite3
|
||||||
|
* **C++ standard code style**
|
||||||
|
* **In memory database support** - provide `:memory:` or empty filename
|
||||||
|
* **COLLATE support**
|
||||||
|
* **Limits setting/getting support**
|
||||||
|
* **User defined functions support**
|
||||||
|
|
||||||
|
`sqlite_orm` library allows to create easy data model mappings to your database schema. It is built to manage (CRUD) objects with a primary key and without it. It also allows you to specify table names and column names explicitly no matter how your classes actually named. Take a look at example:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
|
||||||
|
struct User{
|
||||||
|
int id;
|
||||||
|
std::string firstName;
|
||||||
|
std::string lastName;
|
||||||
|
int birthDate;
|
||||||
|
std::unique_ptr<std::string> imageUrl;
|
||||||
|
int typeId;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UserType {
|
||||||
|
int id;
|
||||||
|
std::string name;
|
||||||
|
};
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
So we have database with predefined schema like
|
||||||
|
|
||||||
|
`CREATE TABLE users (id integer primary key autoincrement, first_name text not null, last_name text not null, birth_date integer not null, image_url text, type_id integer not null)`
|
||||||
|
|
||||||
|
`CREATE TABLE user_types (id integer primary key autoincrement, name text not null DEFAULT 'name_placeholder')`
|
||||||
|
|
||||||
|
Now we tell `sqlite_orm` library about our schema and provide database filename. We create `storage` service object that has CRUD interface. Also we create every table and every column. All code is intuitive and minimalistic.
|
||||||
|
|
||||||
|
```c++
|
||||||
|
|
||||||
|
using namespace sqlite_orm;
|
||||||
|
auto storage = make_storage("db.sqlite",
|
||||||
|
make_table("users",
|
||||||
|
make_column("id", &User::id, primary_key().autoincrement()),
|
||||||
|
make_column("first_name", &User::firstName),
|
||||||
|
make_column("last_name", &User::lastName),
|
||||||
|
make_column("birth_date", &User::birthDate),
|
||||||
|
make_column("image_url", &User::imageUrl),
|
||||||
|
make_column("type_id", &User::typeId)),
|
||||||
|
make_table("user_types",
|
||||||
|
make_column("id", &UserType::id, primary_key().autoincrement()),
|
||||||
|
make_column("name", &UserType::name, default_value("name_placeholder"))));
|
||||||
|
```
|
||||||
|
|
||||||
|
Too easy isn't it? You do not have to specify mapped type explicitly - it is deduced from your member pointers you pass during making a column (for example: `&User::id`). To create a column you have to pass two arguments at least: its name in the table and your mapped class member pointer. You can also add extra arguments to tell your storage about column's constraints like `primary_key`, `autoincrement`, `default_value`, `unique` or `generated_always_as` (order isn't important; `not_null` is deduced from type automatically).
|
||||||
|
|
||||||
|
More details about making storage can be found in [tutorial](https://github.com/fnc12/sqlite_orm/wiki/Making-a-storage).
|
||||||
|
|
||||||
|
If your datamodel classes have private or protected members to map to sqlite then you can make a storage with setter and getter functions. More info in the [example](https://github.com/fnc12/sqlite_orm/blob/master/examples/private_class_members.cpp).
|
||||||
|
|
||||||
|
# CRUD
|
||||||
|
|
||||||
|
Let's create and insert new `User` into our database. First we need to create a `User` object with any id and call `insert` function. It will return id of just created user or throw exception if something goes wrong.
|
||||||
|
|
||||||
|
```c++
|
||||||
|
User user{-1, "Jonh", "Doe", 664416000, std::make_unique<std::string>("url_to_heaven"), 3 };
|
||||||
|
|
||||||
|
auto insertedId = storage.insert(user);
|
||||||
|
cout << "insertedId = " << insertedId << endl; // insertedId = 8
|
||||||
|
user.id = insertedId;
|
||||||
|
|
||||||
|
User secondUser{-1, "Alice", "Inwonder", 831168000, {} , 2};
|
||||||
|
insertedId = storage.insert(secondUser);
|
||||||
|
secondUser.id = insertedId;
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: if we need to insert a new user with specified id call `storage.replace(user);` instead of `insert`.
|
||||||
|
|
||||||
|
Next let's get our user by id.
|
||||||
|
|
||||||
|
```c++
|
||||||
|
try{
|
||||||
|
auto user = storage.get<User>(insertedId);
|
||||||
|
cout << "user = " << user.firstName << " " << user.lastName << endl;
|
||||||
|
}catch(std::system_error e) {
|
||||||
|
cout << e.what() << endl;
|
||||||
|
}catch(...){
|
||||||
|
cout << "unknown exeption" << endl;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Probably you may not like throwing exceptions. Me too. Exception `std::system_error` is thrown because return type in `get` function is not nullable. You can use alternative version `get_pointer` which returns `std::unique_ptr` and doesn't throw `not_found_exception` if nothing found - just returns `nullptr`.
|
||||||
|
|
||||||
|
```c++
|
||||||
|
if(auto user = storage.get_pointer<User>(insertedId)){
|
||||||
|
cout << "user = " << user->firstName << " " << user->lastName << endl;
|
||||||
|
}else{
|
||||||
|
cout << "no user with id " << insertedId << endl;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`std::unique_ptr` is used as optional in `sqlite_orm`. Of course there is class optional in C++14 located at `std::experimental::optional`. But we don't want to use it until it is `experimental`.
|
||||||
|
|
||||||
|
We can also update our user. It updates row by id provided in `user` object and sets all other non `primary_key` fields to values stored in the passed `user` object. So you can just assign members to `user` object you want and call `update`
|
||||||
|
|
||||||
|
```c++
|
||||||
|
user.firstName = "Nicholas";
|
||||||
|
user.imageUrl = "https://cdn1.iconfinder.com/data/icons/man-icon-set/100/man_icon-21-512.png"
|
||||||
|
storage.update(user);
|
||||||
|
```
|
||||||
|
|
||||||
|
Also there is a non-CRUD update version `update_all`:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
storage.update_all(set(c(&User::lastName) = "Hardey",
|
||||||
|
c(&User::typeId) = 2),
|
||||||
|
where(c(&User::firstName) == "Tom"));
|
||||||
|
```
|
||||||
|
|
||||||
|
And delete. To delete you have to pass id only, not whole object. Also we need to explicitly tell which class of object we want to delete. Function name is `remove` not `delete` cause `delete` is a reserved word in C++.
|
||||||
|
|
||||||
|
```c++
|
||||||
|
storage.remove<User>(insertedId)
|
||||||
|
```
|
||||||
|
|
||||||
|
Also we can extract all objects into `std::vector`.
|
||||||
|
|
||||||
|
```c++
|
||||||
|
auto allUsers = storage.get_all<User>();
|
||||||
|
cout << "allUsers (" << allUsers.size() << "):" << endl;
|
||||||
|
for(auto &user : allUsers) {
|
||||||
|
cout << storage.dump(user) << endl; // dump returns std::string with json-like style object info. For example: { id : '1', first_name : 'Jonh', last_name : 'Doe', birth_date : '664416000', image_url : 'https://cdn1.iconfinder.com/data/icons/man-icon-set/100/man_icon-21-512.png', type_id : '3' }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
And one can specify return container type explicitly: let's get all users in `std::list`, not `std::vector`:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
auto allUsersList = storage.get_all<User, std::list<User>>();
|
||||||
|
```
|
||||||
|
|
||||||
|
Container must be STL compatible (must have `push_back(T&&)` function in this case).
|
||||||
|
|
||||||
|
`get_all` can be too heavy for memory so you can iterate row by row (i.e. object by object):
|
||||||
|
|
||||||
|
```c++
|
||||||
|
for(auto &user : storage.iterate<User>()) {
|
||||||
|
cout << storage.dump(user) << endl;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`iterate` member function returns adapter object that has `begin` and `end` member functions returning iterators that fetch object on dereference operator call.
|
||||||
|
|
||||||
|
CRUD functions `get`, `get_pointer`, `remove`, `update` (not `insert`) work only if your type has a primary key column. If you try to `get` an object that is mapped to your storage but has no primary key column a `std::system_error` will be thrown cause `sqlite_orm` cannot detect an id. If you want to know how to perform a storage without primary key take a look at `date_time.cpp` example in `examples` folder.
|
||||||
|
|
||||||
|
# Prepared statements
|
||||||
|
|
||||||
|
Prepared statements are strongly typed.
|
||||||
|
|
||||||
|
```c++
|
||||||
|
// SELECT doctor_id
|
||||||
|
// FROM visits
|
||||||
|
// WHERE LENGTH(patient_name) > 8
|
||||||
|
auto selectStatement = storage.prepare(select(&Visit::doctor_id, where(length(&Visit::patient_name) > 8)));
|
||||||
|
cout << "selectStatement = " << selectStatement.sql() << endl; // prints "SELECT doctor_id FROM ..."
|
||||||
|
auto rows = storage.execute(selectStatement); // rows is std::vector<decltype(Visit::doctor_id)>
|
||||||
|
|
||||||
|
// SELECT doctor_id
|
||||||
|
// FROM visits
|
||||||
|
// WHERE LENGTH(patient_name) > 11
|
||||||
|
get<0>(selectStatement) = 11;
|
||||||
|
auto rows2 = storage.execute(selectStatement);
|
||||||
|
```
|
||||||
|
`get<N>(statement)` function call allows you to access fields to bind them to your statement.
|
||||||
|
|
||||||
|
# Aggregate Functions
|
||||||
|
|
||||||
|
```c++
|
||||||
|
// SELECT AVG(id) FROM users
|
||||||
|
auto averageId = storage.avg(&User::id);
|
||||||
|
cout << "averageId = " << averageId << endl; // averageId = 4.5
|
||||||
|
|
||||||
|
// SELECT AVG(birth_date) FROM users
|
||||||
|
auto averageBirthDate = storage.avg(&User::birthDate);
|
||||||
|
cout << "averageBirthDate = " << averageBirthDate << endl; // averageBirthDate = 6.64416e+08
|
||||||
|
|
||||||
|
// SELECT COUNT(*) FROM users
|
||||||
|
auto usersCount = storage.count<User>();
|
||||||
|
cout << "users count = " << usersCount << endl; // users count = 8
|
||||||
|
|
||||||
|
// SELECT COUNT(id) FROM users
|
||||||
|
auto countId = storage.count(&User::id);
|
||||||
|
cout << "countId = " << countId << endl; // countId = 8
|
||||||
|
|
||||||
|
// SELECT COUNT(image_url) FROM users
|
||||||
|
auto countImageUrl = storage.count(&User::imageUrl);
|
||||||
|
cout << "countImageUrl = " << countImageUrl << endl; // countImageUrl = 5
|
||||||
|
|
||||||
|
// SELECT GROUP_CONCAT(id) FROM users
|
||||||
|
auto concatedUserId = storage.group_concat(&User::id);
|
||||||
|
cout << "concatedUserId = " << concatedUserId << endl; // concatedUserId = 1,2,3,4,5,6,7,8
|
||||||
|
|
||||||
|
// SELECT GROUP_CONCAT(id, "---") FROM users
|
||||||
|
auto concatedUserIdWithDashes = storage.group_concat(&User::id, "---");
|
||||||
|
cout << "concatedUserIdWithDashes = " << concatedUserIdWithDashes << endl; // concatedUserIdWithDashes = 1---2---3---4---5---6---7---8
|
||||||
|
|
||||||
|
// SELECT MAX(id) FROM users
|
||||||
|
if(auto maxId = storage.max(&User::id)){
|
||||||
|
cout << "maxId = " << *maxId <<endl; // maxId = 12 (maxId is std::unique_ptr<int>)
|
||||||
|
}else{
|
||||||
|
cout << "maxId is null" << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SELECT MAX(first_name) FROM users
|
||||||
|
if(auto maxFirstName = storage.max(&User::firstName)){
|
||||||
|
cout << "maxFirstName = " << *maxFirstName << endl; // maxFirstName = Jonh (maxFirstName is std::unique_ptr<std::string>)
|
||||||
|
}else{
|
||||||
|
cout << "maxFirstName is null" << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SELECT MIN(id) FROM users
|
||||||
|
if(auto minId = storage.min(&User::id)){
|
||||||
|
cout << "minId = " << *minId << endl; // minId = 1 (minId is std::unique_ptr<int>)
|
||||||
|
}else{
|
||||||
|
cout << "minId is null" << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SELECT MIN(last_name) FROM users
|
||||||
|
if(auto minLastName = storage.min(&User::lastName)){
|
||||||
|
cout << "minLastName = " << *minLastName << endl; // minLastName = Doe
|
||||||
|
}else{
|
||||||
|
cout << "minLastName is null" << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SELECT SUM(id) FROM users
|
||||||
|
if(auto sumId = storage.sum(&User::id)){ // sumId is std::unique_ptr<int>
|
||||||
|
cout << "sumId = " << *sumId << endl;
|
||||||
|
}else{
|
||||||
|
cout << "sumId is null" << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SELECT TOTAL(id) FROM users
|
||||||
|
auto totalId = storage.total(&User::id);
|
||||||
|
cout << "totalId = " << totalId << endl; // totalId is double (always)
|
||||||
|
```
|
||||||
|
|
||||||
|
# Where conditions
|
||||||
|
|
||||||
|
You also can select objects with custom where conditions with `=`, `!=`, `>`, `>=`, `<`, `<=`, `IN`, `BETWEEN` and `LIKE`.
|
||||||
|
|
||||||
|
For example: let's select users with id lesser than 10:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
// SELECT * FROM users WHERE id < 10
|
||||||
|
auto idLesserThan10 = storage.get_all<User>(where(c(&User::id) < 10));
|
||||||
|
cout << "idLesserThan10 count = " << idLesserThan10.size() << endl;
|
||||||
|
for(auto &user : idLesserThan10) {
|
||||||
|
cout << storage.dump(user) << endl;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Or select all users who's first name is not equal "John":
|
||||||
|
|
||||||
|
```c++
|
||||||
|
// SELECT * FROM users WHERE first_name != 'John'
|
||||||
|
auto notJohn = storage.get_all<User>(where(c(&User::firstName) != "John"));
|
||||||
|
cout << "notJohn count = " << notJohn.size() << endl;
|
||||||
|
for(auto &user : notJohn) {
|
||||||
|
cout << storage.dump(user) << endl;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
By the way one can implement not equal in a different way using C++ negation operator:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
auto notJohn2 = storage.get_all<User>(where(not (c(&User::firstName) == "John")));
|
||||||
|
```
|
||||||
|
|
||||||
|
You can use `!` and `not` in this case cause they are equal. Also you can chain several conditions with `and` and `or` operators. Let's try to get users with query with conditions like `where id >= 5 and id <= 7 and not id = 6`:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
auto id5and7 = storage.get_all<User>(where(c(&User::id) <= 7 and c(&User::id) >= 5 and not (c(&User::id) == 6)));
|
||||||
|
cout << "id5and7 count = " << id5and7.size() << endl;
|
||||||
|
for(auto &user : id5and7) {
|
||||||
|
cout << storage.dump(user) << endl;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Or let's just export two users with id 10 or id 16 (of course if these users exist):
|
||||||
|
|
||||||
|
```c++
|
||||||
|
auto id10or16 = storage.get_all<User>(where(c(&User::id) == 10 or c(&User::id) == 16));
|
||||||
|
cout << "id10or16 count = " << id10or16.size() << endl;
|
||||||
|
for(auto &user : id10or16) {
|
||||||
|
cout << storage.dump(user) << endl;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In fact you can chain together any number of different conditions with any operator from `and`, `or` and `not`. All conditions are templated so there is no runtime overhead. And this makes `sqlite_orm` the most powerful **sqlite** C++ ORM library!
|
||||||
|
|
||||||
|
Moreover you can use parentheses to set the priority of query conditions:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
auto cuteConditions = storage.get_all<User>(where((c(&User::firstName) == "John" or c(&User::firstName) == "Alex") and c(&User::id) == 4)); // where (first_name = 'John' or first_name = 'Alex') and id = 4
|
||||||
|
cout << "cuteConditions count = " << cuteConditions.size() << endl; // cuteConditions count = 1
|
||||||
|
cuteConditions = storage.get_all<User>(where(c(&User::firstName) == "John" or (c(&User::firstName) == "Alex" and c(&User::id) == 4))); // where first_name = 'John' or (first_name = 'Alex' and id = 4)
|
||||||
|
cout << "cuteConditions count = " << cuteConditions.size() << endl; // cuteConditions count = 2
|
||||||
|
```
|
||||||
|
|
||||||
|
Also we can implement `get` by id with `get_all` and `where` like this:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
// SELECT * FROM users WHERE ( 2 = id )
|
||||||
|
auto idEquals2 = storage.get_all<User>(where(2 == c(&User::id)));
|
||||||
|
cout << "idEquals2 count = " << idEquals2.size() << endl;
|
||||||
|
if(idEquals2.size()){
|
||||||
|
cout << storage.dump(idEquals2.front()) << endl;
|
||||||
|
}else{
|
||||||
|
cout << "user with id 2 doesn't exist" << endl;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Lets try the `IN` operator:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
// SELECT * FROM users WHERE id IN (2, 4, 6, 8, 10)
|
||||||
|
auto evenLesserTen10 = storage.get_all<User>(where(in(&User::id, {2, 4, 6, 8, 10})));
|
||||||
|
cout << "evenLesserTen10 count = " << evenLesserTen10.size() << endl;
|
||||||
|
for(auto &user : evenLesserTen10) {
|
||||||
|
cout << storage.dump(user) << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SELECT * FROM users WHERE last_name IN ("Doe", "White")
|
||||||
|
auto doesAndWhites = storage.get_all<User>(where(in(&User::lastName, {"Doe", "White"})));
|
||||||
|
cout << "doesAndWhites count = " << doesAndWhites.size() << endl;
|
||||||
|
for(auto &user : doesAndWhites) {
|
||||||
|
cout << storage.dump(user) << endl;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
And `BETWEEN`:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
// SELECT * FROM users WHERE id BETWEEN 66 AND 68
|
||||||
|
auto betweenId = storage.get_all<User>(where(between(&User::id, 66, 68)));
|
||||||
|
cout << "betweenId = " << betweenId.size() << endl;
|
||||||
|
for(auto &user : betweenId) {
|
||||||
|
cout << storage.dump(user) << endl;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
And even `LIKE`:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
// SELECT * FROM users WHERE last_name LIKE 'D%'
|
||||||
|
auto whereNameLike = storage.get_all<User>(where(like(&User::lastName, "D%")));
|
||||||
|
cout << "whereNameLike = " << whereNameLike.size() << endl;
|
||||||
|
for(auto &user : whereNameLike) {
|
||||||
|
cout << storage.dump(user) << endl;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Looks like magic but it works very simple. Cute function `c` (column) takes a class member pointer and returns a special expression middle object that can be used with operators overloaded in `::sqlite_orm` namespace. Operator overloads act just like functions
|
||||||
|
|
||||||
|
* is_equal
|
||||||
|
* is_not_equal
|
||||||
|
* greater_than
|
||||||
|
* greater_or_equal
|
||||||
|
* lesser_than
|
||||||
|
* lesser_or_equal
|
||||||
|
* is_null
|
||||||
|
* is_not_null
|
||||||
|
|
||||||
|
that simulate binary comparison operator so they take 2 arguments: left hand side and right hand side. Arguments may be either member pointer of mapped class or any other expression (core/aggregate function, literal or subexpression). Binary comparison functions map arguments to text to be passed to sqlite engine to process query. Member pointers are being mapped to column names and literals/variables/constants to '?' and then are bound automatically. Next `where` function places brackets around condition and adds "WHERE" keyword before condition text. Next resulted string appends to a query string and is being processed further.
|
||||||
|
|
||||||
|
If you omit `where` function in `get_all` it will return all objects from a table:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
auto allUsers = storage.get_all<User>();
|
||||||
|
```
|
||||||
|
|
||||||
|
Also you can use `remove_all` function to perform `DELETE FROM ... WHERE` query with the same type of conditions.
|
||||||
|
|
||||||
|
```c++
|
||||||
|
storage.remove_all<User>(where(c(&User::id) < 100));
|
||||||
|
```
|
||||||
|
|
||||||
|
# Raw select
|
||||||
|
|
||||||
|
If you need to extract only a single column (`SELECT %column_name% FROM %table_name% WHERE %conditions%`) you can use a non-CRUD `select` function:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
|
||||||
|
// SELECT id FROM users
|
||||||
|
auto allIds = storage.select(&User::id);
|
||||||
|
cout << "allIds count = " << allIds.size() << endl; // allIds is std::vector<int>
|
||||||
|
for(auto &id : allIds) {
|
||||||
|
cout << id << " ";
|
||||||
|
}
|
||||||
|
cout << endl;
|
||||||
|
|
||||||
|
// SELECT id FROM users WHERE last_name = 'Doe'
|
||||||
|
auto doeIds = storage.select(&User::id, where(c(&User::lastName) == "Doe"));
|
||||||
|
cout << "doeIds count = " << doeIds.size() << endl; // doeIds is std::vector<int>
|
||||||
|
for(auto &doeId : doeIds) {
|
||||||
|
cout << doeId << " ";
|
||||||
|
}
|
||||||
|
cout << endl;
|
||||||
|
|
||||||
|
// SELECT last_name FROM users WHERE id < 300
|
||||||
|
auto allLastNames = storage.select(&User::lastName, where(c(&User::id) < 300));
|
||||||
|
cout << "allLastNames count = " << allLastNames.size() << endl; // allLastNames is std::vector<std::string>
|
||||||
|
for(auto &lastName : allLastNames) {
|
||||||
|
cout << lastName << " ";
|
||||||
|
}
|
||||||
|
cout << endl;
|
||||||
|
|
||||||
|
// SELECT id FROM users WHERE image_url IS NULL
|
||||||
|
auto idsWithoutUrls = storage.select(&User::id, where(is_null(&User::imageUrl)));
|
||||||
|
for(auto id : idsWithoutUrls) {
|
||||||
|
cout << "id without image url " << id << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SELECT id FROM users WHERE image_url IS NOT NULL
|
||||||
|
auto idsWithUrl = storage.select(&User::id, where(is_not_null(&User::imageUrl)));
|
||||||
|
for(auto id : idsWithUrl) {
|
||||||
|
cout << "id with image url " << id << endl;
|
||||||
|
}
|
||||||
|
auto idsWithUrl2 = storage.select(&User::id, where(not is_null(&User::imageUrl)));
|
||||||
|
assert(std::equal(idsWithUrl2.begin(),
|
||||||
|
idsWithUrl2.end(),
|
||||||
|
idsWithUrl.begin()));
|
||||||
|
```
|
||||||
|
|
||||||
|
Also you're able to select several column in a vector of tuples. Example:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
// `SELECT first_name, last_name FROM users WHERE id > 250 ORDER BY id`
|
||||||
|
auto partialSelect = storage.select(columns(&User::firstName, &User::lastName),
|
||||||
|
where(c(&User::id) > 250),
|
||||||
|
order_by(&User::id));
|
||||||
|
cout << "partialSelect count = " << partialSelect.size() << endl;
|
||||||
|
for(auto &t : partialSelect) {
|
||||||
|
auto &firstName = std::get<0>(t);
|
||||||
|
auto &lastName = std::get<1>(t);
|
||||||
|
cout << firstName << " " << lastName << endl;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# ORDER BY support
|
||||||
|
|
||||||
|
ORDER BY query option can be applied to `get_all` and `select` functions just like `where` but with `order_by` function. It can be mixed with WHERE in a single query. Examples:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
// `SELECT * FROM users ORDER BY id`
|
||||||
|
auto orderedUsers = storage.get_all<User>(order_by(&User::id));
|
||||||
|
cout << "orderedUsers count = " << orderedUsers.size() << endl;
|
||||||
|
for(auto &user : orderedUsers) {
|
||||||
|
cout << storage.dump(user) << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// `SELECT * FROM users WHERE id < 250 ORDER BY first_name`
|
||||||
|
auto orderedUsers2 = storage.get_all<User>(where(c(&User::id) < 250), order_by(&User::firstName));
|
||||||
|
cout << "orderedUsers2 count = " << orderedUsers2.size() << endl;
|
||||||
|
for(auto &user : orderedUsers2) {
|
||||||
|
cout << storage.dump(user) << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// `SELECT * FROM users WHERE id > 100 ORDER BY first_name ASC`
|
||||||
|
auto orderedUsers3 = storage.get_all<User>(where(c(&User::id) > 100), order_by(&User::firstName).asc());
|
||||||
|
cout << "orderedUsers3 count = " << orderedUsers3.size() << endl;
|
||||||
|
for(auto &user : orderedUsers3) {
|
||||||
|
cout << storage.dump(user) << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// `SELECT * FROM users ORDER BY id DESC`
|
||||||
|
auto orderedUsers4 = storage.get_all<User>(order_by(&User::id).desc());
|
||||||
|
cout << "orderedUsers4 count = " << orderedUsers4.size() << endl;
|
||||||
|
for(auto &user : orderedUsers4) {
|
||||||
|
cout << storage.dump(user) << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// `SELECT first_name FROM users ORDER BY ID DESC`
|
||||||
|
auto orderedFirstNames = storage.select(&User::firstName, order_by(&User::id).desc());
|
||||||
|
cout << "orderedFirstNames count = " << orderedFirstNames.size() << endl;
|
||||||
|
for(auto &firstName : orderedFirstNames) {
|
||||||
|
cout << "firstName = " << firstName << endl;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# LIMIT and OFFSET
|
||||||
|
|
||||||
|
There are three available versions of `LIMIT`/`OFFSET` options:
|
||||||
|
|
||||||
|
- LIMIT %limit%
|
||||||
|
- LIMIT %limit% OFFSET %offset%
|
||||||
|
- LIMIT %offset%, %limit%
|
||||||
|
|
||||||
|
All these versions available with the same interface:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
// `SELECT * FROM users WHERE id > 250 ORDER BY id LIMIT 5`
|
||||||
|
auto limited5 = storage.get_all<User>(where(c(&User::id) > 250),
|
||||||
|
order_by(&User::id),
|
||||||
|
limit(5));
|
||||||
|
cout << "limited5 count = " << limited5.size() << endl;
|
||||||
|
for(auto &user : limited5) {
|
||||||
|
cout << storage.dump(user) << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// `SELECT * FROM users WHERE id > 250 ORDER BY id LIMIT 5, 10`
|
||||||
|
auto limited5comma10 = storage.get_all<User>(where(c(&User::id) > 250),
|
||||||
|
order_by(&User::id),
|
||||||
|
limit(5, 10));
|
||||||
|
cout << "limited5comma10 count = " << limited5comma10.size() << endl;
|
||||||
|
for(auto &user : limited5comma10) {
|
||||||
|
cout << storage.dump(user) << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// `SELECT * FROM users WHERE id > 250 ORDER BY id LIMIT 5 OFFSET 10`
|
||||||
|
auto limit5offset10 = storage.get_all<User>(where(c(&User::id) > 250),
|
||||||
|
order_by(&User::id),
|
||||||
|
limit(5, offset(10)));
|
||||||
|
cout << "limit5offset10 count = " << limit5offset10.size() << endl;
|
||||||
|
for(auto &user : limit5offset10) {
|
||||||
|
cout << storage.dump(user) << endl;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Please beware that queries `LIMIT 5, 10` and `LIMIT 5 OFFSET 10` mean different. `LIMIT 5, 10` means `LIMIT 10 OFFSET 5`.
|
||||||
|
|
||||||
|
# JOIN support
|
||||||
|
|
||||||
|
You can perform simple `JOIN`, `CROSS JOIN`, `INNER JOIN`, `LEFT JOIN` or `LEFT OUTER JOIN` in your query. Instead of joined table specify mapped type. Example for doctors and visits:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
// SELECT a.doctor_id, a.doctor_name,
|
||||||
|
// c.patient_name, c.vdate
|
||||||
|
// FROM doctors a
|
||||||
|
// LEFT JOIN visits c
|
||||||
|
// ON a.doctor_id=c.doctor_id;
|
||||||
|
auto rows = storage2.select(columns(&Doctor::id, &Doctor::name, &Visit::patientName, &Visit::vdate),
|
||||||
|
left_join<Visit>(on(c(&Doctor::id) == &Visit::doctorId))); // one `c` call is enough cause operator overloads are templated
|
||||||
|
for(auto &row : rows) {
|
||||||
|
cout << std::get<0>(row) << '\t' << std::get<1>(row) << '\t' << std::get<2>(row) << '\t' << std::get<3>(row) << endl;
|
||||||
|
}
|
||||||
|
cout << endl;
|
||||||
|
```
|
||||||
|
|
||||||
|
Simple `JOIN`:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
// SELECT a.doctor_id,a.doctor_name,
|
||||||
|
// c.patient_name,c.vdate
|
||||||
|
// FROM doctors a
|
||||||
|
// JOIN visits c
|
||||||
|
// ON a.doctor_id=c.doctor_id;
|
||||||
|
rows = storage2.select(columns(&Doctor::id, &Doctor::name, &Visit::patientName, &Visit::vdate),
|
||||||
|
join<Visit>(on(c(&Doctor::id) == &Visit::doctorId)));
|
||||||
|
for(auto &row : rows) {
|
||||||
|
cout << std::get<0>(row) << '\t' << std::get<1>(row) << '\t' << std::get<2>(row) << '\t' << std::get<3>(row) << endl;
|
||||||
|
}
|
||||||
|
cout << endl;
|
||||||
|
```
|
||||||
|
|
||||||
|
Two `INNER JOIN`s in one query:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
// SELECT
|
||||||
|
// trackid,
|
||||||
|
// tracks.name AS Track,
|
||||||
|
// albums.title AS Album,
|
||||||
|
// artists.name AS Artist
|
||||||
|
// FROM
|
||||||
|
// tracks
|
||||||
|
// INNER JOIN albums ON albums.albumid = tracks.albumid
|
||||||
|
// INNER JOIN artists ON artists.artistid = albums.artistid;
|
||||||
|
auto innerJoinRows2 = storage.select(columns(&Track::trackId, &Track::name, &Album::title, &Artist::name),
|
||||||
|
inner_join<Album>(on(c(&Album::albumId) == &Track::albumId)),
|
||||||
|
inner_join<Artist>(on(c(&Artist::artistId) == &Album::artistId)));
|
||||||
|
// innerJoinRows2 is std::vector<std::tuple<decltype(Track::trackId), decltype(Track::name), decltype(Album::title), decltype(Artist::name)>>
|
||||||
|
```
|
||||||
|
|
||||||
|
More join examples can be found in [examples folder](https://github.com/fnc12/sqlite_orm/blob/master/examples/left_and_inner_join.cpp).
|
||||||
|
|
||||||
|
# Migrations functionality
|
||||||
|
|
||||||
|
There are no explicit `up` and `down` functions that are used to be used in migrations. Instead `sqlite_orm` offers `sync_schema` function that takes responsibility of comparing actual db file schema with one you specified in `make_storage` call and if something is not equal it alters or drops/creates schema.
|
||||||
|
|
||||||
|
```c++
|
||||||
|
storage.sync_schema();
|
||||||
|
// or
|
||||||
|
storage.sync_schema(true);
|
||||||
|
```
|
||||||
|
|
||||||
|
Please beware that `sync_schema` doesn't guarantee that data will be saved. It *tries* to save it only. Below you can see rules list that `sync_schema` follows during call:
|
||||||
|
* if there are excess tables exist in db they are ignored (not dropped)
|
||||||
|
* every table from storage is compared with it's db analog and
|
||||||
|
* if table doesn't exist it is created
|
||||||
|
* if table exists its colums are being compared with table_info from db and
|
||||||
|
* if there are columns in db that do not exist in storage (excess) table will be dropped and recreated if `preserve` is `false`, and table will be copied into temporary table without excess columns, source table will be dropped, copied table will be renamed to source table (sqlite remove column technique) if `preserve` is `true`. `preserve` is the first argument in `sync_schema` function. It's default value is `false`. Beware that setting it to `true` may take time for copying table rows.
|
||||||
|
* if there are columns in storage that do not exist in db they will be added using 'ALTER TABLE ... ADD COLUMN ...' command and table data will not be dropped but if any of added columns is null but has not default value table will be dropped and recreated
|
||||||
|
* if there is any column existing in both db and storage but differs by any of properties (type, pk, notnull) table will be dropped and recreated (dflt_value isn't checked cause there can be ambiguity in default values, please beware).
|
||||||
|
|
||||||
|
The best practice is to call this function right after storage creation.
|
||||||
|
|
||||||
|
# Transactions
|
||||||
|
|
||||||
|
There are three ways to begin and commit/rollback transactions:
|
||||||
|
* explicitly call `begin_transaction();`, `rollback();` or `commit();` functions
|
||||||
|
* use `transaction` function which begins transaction implicitly and takes a lambda argument which returns true for commit and false for rollback. All storage calls performed in lambda can be commited or rollbacked by returning `true` or `false`.
|
||||||
|
* use `transaction_guard` function which returns a guard object which works just like `lock_guard` for `std::mutex`.
|
||||||
|
|
||||||
|
Example for explicit call:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
auto secondUser = storage.get<User>(2);
|
||||||
|
|
||||||
|
storage.begin_transaction();
|
||||||
|
secondUser.typeId = 3;
|
||||||
|
storage.update(secondUser);
|
||||||
|
storage.rollback(); // or storage.commit();
|
||||||
|
|
||||||
|
secondUser = storage.get<decltype(secondUser)>(secondUser.id);
|
||||||
|
assert(secondUser.typeId != 3);
|
||||||
|
```
|
||||||
|
|
||||||
|
Example for implicit call:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
storage.transaction([&] () mutable { // mutable keyword allows make non-const function calls
|
||||||
|
auto secondUser = storage.get<User>(2);
|
||||||
|
secondUser.typeId = 1;
|
||||||
|
storage.update(secondUser);
|
||||||
|
auto gottaRollback = bool(rand() % 2);
|
||||||
|
if(gottaRollback){ // dummy condition for test
|
||||||
|
return false; // exits lambda and calls ROLLBACK
|
||||||
|
}
|
||||||
|
return true; // exits lambda and calls COMMIT
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
The second way guarantees that `commit` or `rollback` will be called. You can use either way.
|
||||||
|
|
||||||
|
Trancations are useful with `changes` sqlite function that returns number of rows modified.
|
||||||
|
|
||||||
|
```c++
|
||||||
|
storage.transaction([&] () mutable {
|
||||||
|
storage.remove_all<User>(where(c(&User::id) < 100));
|
||||||
|
auto usersRemoved = storage.changes();
|
||||||
|
cout << "usersRemoved = " << usersRemoved << endl;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
It will print a number of deleted users (rows). But if you call `changes` without a transaction and your database is located in file not in RAM the result will be 0 always cause `sqlite_orm` opens and closes connection every time you call a function without a transaction.
|
||||||
|
|
||||||
|
Also a `transaction` function returns `true` if transaction is commited and `false` if it is rollbacked. It can be useful if your next actions depend on transaction result:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
auto commited = storage.transaction([&] () mutable {
|
||||||
|
auto secondUser = storage.get<User>(2);
|
||||||
|
secondUser.typeId = 1;
|
||||||
|
storage.update(secondUser);
|
||||||
|
auto gottaRollback = bool(rand() % 2);
|
||||||
|
if(gottaRollback){ // dummy condition for test
|
||||||
|
return false; // exits lambda and calls ROLLBACK
|
||||||
|
}
|
||||||
|
return true; // exits lambda and calls COMMIT
|
||||||
|
});
|
||||||
|
if(commited){
|
||||||
|
cout << "Commited successfully, go on." << endl;
|
||||||
|
}else{
|
||||||
|
cerr << "Commit failed, process an error" << endl;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Example for `transaction_guard` function:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
try{
|
||||||
|
auto guard = storage.transaction_guard(); // calls BEGIN TRANSACTION and returns guard object
|
||||||
|
user.name = "Paul";
|
||||||
|
auto notExisting = storage.get<User>(-1); // exception is thrown here, guard calls ROLLBACK in its destructor
|
||||||
|
guard.commit();
|
||||||
|
}catch(...){
|
||||||
|
cerr << "exception" << endl;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# In memory database
|
||||||
|
|
||||||
|
To manage in memory database just provide `:memory:` or `""` instead as filename to `make_storage`.
|
||||||
|
|
||||||
|
# Comparison with other C++ libs
|
||||||
|
|
||||||
|
| |sqlite_orm|[SQLiteCpp](https://github.com/SRombauts/SQLiteCpp)|[hiberlite](https://github.com/paulftw/hiberlite)|[ODB](https://www.codesynthesis.com/products/odb/)|
|
||||||
|
|---|:---:|:---:|:---:|:---:|
|
||||||
|
|Schema sync|yes|no|yes|no|
|
||||||
|
|Single responsibility principle|yes|yes|no|no|
|
||||||
|
|STL compatible|yes|no|no|no|
|
||||||
|
|No raw string queries|yes|no|yes|yes|
|
||||||
|
|Transactions|yes|yes|no|yes|
|
||||||
|
|Custom types binding|yes|no|yes|yes|
|
||||||
|
|Doesn't use macros and/or external codegen scripts|yes|yes|no|no|
|
||||||
|
|Aggregate functions|yes|yes|no|yes|
|
||||||
|
|Prepared statements|yes|yes|no|no|
|
||||||
|
|
||||||
|
# Notes
|
||||||
|
|
||||||
|
To work well your data model class must be default constructable and must not have const fields mapped to database cause they are assigned during queries. Otherwise code won't compile on line with member assignment operator.
|
||||||
|
|
||||||
|
For more details please check the project [wiki](https://github.com/fnc12/sqlite_orm/wiki).
|
||||||
|
|
||||||
|
# Installation
|
||||||
|
|
||||||
|
**Note**: Installation is not necessary if you plan to use the fetchContent method, see below in Usage.
|
||||||
|
|
||||||
|
Use a popular package manager like [vcpkg](https://github.com/Microsoft/vcpkg) and just install it with the `vcpkg install sqlite-orm` command.
|
||||||
|
|
||||||
|
Or you build it from source:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/fnc12/sqlite_orm.git sqlite_orm
|
||||||
|
cd sqlite_orm
|
||||||
|
cmake -B build
|
||||||
|
cmake --build build --target install
|
||||||
|
```
|
||||||
|
You might need admin rights for the last command.
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
|
||||||
|
## CMake
|
||||||
|
|
||||||
|
If you use cmake, there are two supported ways how to use it with cmake (if another works as well or should be supported, open an issue).
|
||||||
|
|
||||||
|
Either way you choose, the include path as well as the dependency sqlite3 will be set automatically on your target. So usage is straight forward, but you need to have installed sqlite3 on your system (see Requirements below)
|
||||||
|
|
||||||
|
## Find Package
|
||||||
|
|
||||||
|
If you have installed the lib system wide and it's in your PATH, you can use find_package to include it in cmake. It will make a target `sqlite_orm::sqlite_orm` available which you can link against. Have a look at examples/find_package for a full example.
|
||||||
|
|
||||||
|
```cmake
|
||||||
|
find_package(SqliteOrm REQUIRED)
|
||||||
|
|
||||||
|
target_link_libraries(main PRIVATE sqlite_orm::sqlite_orm)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fetch Content (Recommended)
|
||||||
|
|
||||||
|
Alternatively, cmake can download the project directly from github during configure stage and therefore you don't need to install the lib before.
|
||||||
|
Againt a target `sqlite_orm::sqlite_orm` will be available which you can link against. Have a look at examples/fetch_content for a full example.
|
||||||
|
|
||||||
|
## No CMake
|
||||||
|
|
||||||
|
If you want to use the lib directly with Make or something else, just set the inlcude path correctly (should be correct on Linux already), so `sqlite_orm/sqlite_orm.h` is found. As this is a header only lib, there is nothing more you have to do.
|
||||||
|
|
||||||
|
# Requirements
|
||||||
|
|
||||||
|
* C++14 compatible compiler (not C++11 cause of templated lambdas in the lib).
|
||||||
|
* Sqlite3 installed on your system and in the path, so cmake can find it (or linked to you project if you don't use cmake)
|
||||||
|
|
||||||
|
# Video from conference
|
||||||
|
|
||||||
|
[![Video from conference](https://img.youtube.com/vi/ngsilquWgpo/0.jpg)](https://www.youtube.com/watch?v=ngsilquWgpo)
|
||||||
|
|
||||||
|
# SqliteMan
|
||||||
|
|
||||||
|
In case you need a native SQLite client for macOS or Windows 10 you can use SqliteMan https://sqliteman.dev. It is not a commercial. It is a free native client being developed by the maintainer of this repo.
|
|
@ -0,0 +1,27 @@
|
||||||
|
# To do list
|
||||||
|
|
||||||
|
`sqlite_orm` is a wonderful library but there are still features that are not implemented. Here you can find a list of them:
|
||||||
|
|
||||||
|
* `FOREIGN KEY` - sync_schema fk comparison and ability of two tables to have fk to each other (`PRAGMA foreign_key_list(%table_name%);` may be useful)
|
||||||
|
* rest of core functions(https://sqlite.org/lang_corefunc.html)
|
||||||
|
* `ATTACH`
|
||||||
|
* blob incremental I/O https://sqlite.org/c3ref/blob_open.html
|
||||||
|
* CREATE VIEW and other view operations https://sqlite.org/lang_createview.html
|
||||||
|
* query static check for correct order (e.g. `GROUP BY` after `WHERE`)
|
||||||
|
* `WINDOW`
|
||||||
|
* `SAVEPOINT` https://www.sqlite.org/lang_savepoint.html
|
||||||
|
* add `static_assert` in crud `get*` functions in case user passes `where_t` instead of id to make compilation error more clear (example https://github.com/fnc12/sqlite_orm/issues/485)
|
||||||
|
* named constraints: constraint can have name `CREATE TABLE heroes(id INTEGER CONSTRAINT pk PRIMARY KEY)`
|
||||||
|
* `FILTER` clause https://sqlite.org/lang_aggfunc.html#aggfilter
|
||||||
|
* scalar math functions https://sqlite.org/lang_mathfunc.html
|
||||||
|
* improve DROP COLUMN in `sync_schema` https://sqlite.org/lang_altertable.html#altertabdropcol
|
||||||
|
* `UPDATE FROM` support https://sqlite.org/lang_update.html#upfrom
|
||||||
|
* `iif()` function https://sqlite.org/lang_corefunc.html#iif
|
||||||
|
* add strong typed collate syntax (more info [here](https://github.com/fnc12/sqlite_orm/issues/767#issuecomment-887689672))
|
||||||
|
* strict tables https://sqlite.org/stricttables.html
|
||||||
|
* static assert when UPDATE is called with no PKs
|
||||||
|
* `json_each` and `json_tree` functions for JSON1 extension
|
||||||
|
* update hook
|
||||||
|
* `RAISE`
|
||||||
|
|
||||||
|
Please feel free to add any feature that isn't listed here and not implemented yet.
|
|
@ -0,0 +1 @@
|
||||||
|
theme: jekyll-theme-minimal
|
|
@ -0,0 +1,184 @@
|
||||||
|
# build format
|
||||||
|
version: "{build}"
|
||||||
|
|
||||||
|
skip_branch_with_pr: true
|
||||||
|
skip_commits:
|
||||||
|
files:
|
||||||
|
- .git*
|
||||||
|
- .travis.yml
|
||||||
|
- _config.yml
|
||||||
|
- LICENSE
|
||||||
|
- '*.md'
|
||||||
|
- '*.png'
|
||||||
|
- '*.sh'
|
||||||
|
|
||||||
|
# configurations to add to build matrix
|
||||||
|
configuration:
|
||||||
|
#- Debug
|
||||||
|
- Release
|
||||||
|
|
||||||
|
environment:
|
||||||
|
appveyor_yml_disable_ps_linux: true
|
||||||
|
matrix:
|
||||||
|
- job_name: clang, C++14
|
||||||
|
appveyor_build_worker_image: Ubuntu
|
||||||
|
CC: clang
|
||||||
|
CXX: clang++
|
||||||
|
SQLITE_ORM_CXX_STANDARD: "-DSQLITE_ORM_ENABLE_CXX_14=ON"
|
||||||
|
cmake_build_parallel: --parallel
|
||||||
|
|
||||||
|
- job_name: gcc, C++14
|
||||||
|
appveyor_build_worker_image: Ubuntu
|
||||||
|
CC: gcc
|
||||||
|
CXX: g++
|
||||||
|
SQLITE_ORM_CXX_STANDARD: "-DSQLITE_ORM_ENABLE_CXX_14=ON"
|
||||||
|
# gcc was stuck with a parallel build
|
||||||
|
cmake_build_parallel: ""
|
||||||
|
|
||||||
|
# Representative for C++14
|
||||||
|
- job_name: Visual Studio 2017, x64, C++14
|
||||||
|
appveyor_build_worker_image: Visual Studio 2017
|
||||||
|
platform: x64
|
||||||
|
SQLITE_ORM_CXX_STANDARD: ""
|
||||||
|
|
||||||
|
- job_name: clang, C++17
|
||||||
|
appveyor_build_worker_image: Ubuntu
|
||||||
|
CC: clang
|
||||||
|
CXX: clang++
|
||||||
|
SQLITE_ORM_CXX_STANDARD: "-DSQLITE_ORM_ENABLE_CXX_17=ON"
|
||||||
|
cmake_build_parallel: --parallel
|
||||||
|
|
||||||
|
- job_name: gcc, C++17
|
||||||
|
appveyor_build_worker_image: Ubuntu
|
||||||
|
CC: gcc
|
||||||
|
CXX: g++
|
||||||
|
SQLITE_ORM_CXX_STANDARD: "-DSQLITE_ORM_ENABLE_CXX_17=ON"
|
||||||
|
cmake_build_parallel: ""
|
||||||
|
|
||||||
|
- job_name: clang, C++20 (with examples)
|
||||||
|
appveyor_build_worker_image: Ubuntu
|
||||||
|
CC: clang
|
||||||
|
CXX: clang++
|
||||||
|
SQLITE_ORM_CXX_STANDARD: "-DSQLITE_ORM_ENABLE_CXX_20=ON"
|
||||||
|
# clang was stuck with a parallel build of examples
|
||||||
|
cmake_build_parallel: ""
|
||||||
|
cmake_build_examples: "-DBUILD_EXAMPLES=ON"
|
||||||
|
|
||||||
|
- job_name: gcc, C++20
|
||||||
|
appveyor_build_worker_image: Ubuntu
|
||||||
|
CC: gcc
|
||||||
|
CXX: g++
|
||||||
|
SQLITE_ORM_CXX_STANDARD: "-DSQLITE_ORM_ENABLE_CXX_20=ON"
|
||||||
|
cmake_build_parallel: ""
|
||||||
|
|
||||||
|
- job_name: Visual Studio 2022, x64, C++17
|
||||||
|
appveyor_build_worker_image: Visual Studio 2022
|
||||||
|
platform: x64
|
||||||
|
SQLITE_ORM_CXX_STANDARD: "-DSQLITE_ORM_ENABLE_CXX_17=ON"
|
||||||
|
|
||||||
|
- job_name: Visual Studio 2022, x64, C++20
|
||||||
|
appveyor_build_worker_image: Visual Studio 2022
|
||||||
|
platform: x64
|
||||||
|
SQLITE_ORM_CXX_STANDARD: "-DSQLITE_ORM_ENABLE_CXX_20=ON"
|
||||||
|
|
||||||
|
- job_name: Visual Studio 2022, x86, C++20
|
||||||
|
appveyor_build_worker_image: Visual Studio 2022
|
||||||
|
platform: x86
|
||||||
|
SQLITE_ORM_CXX_STANDARD: "-DSQLITE_ORM_ENABLE_CXX_20=ON"
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
fast_finish: true
|
||||||
|
|
||||||
|
for:
|
||||||
|
-
|
||||||
|
# Windows
|
||||||
|
matrix:
|
||||||
|
only:
|
||||||
|
- appveyor_build_worker_image: Visual Studio 2017
|
||||||
|
- appveyor_build_worker_image: Visual Studio 2022
|
||||||
|
init:
|
||||||
|
- |-
|
||||||
|
echo %appveyor_build_worker_image% - %platform% - %configuration%
|
||||||
|
cmake --version
|
||||||
|
if "%platform%"=="x64" (set architecture=-A x64)
|
||||||
|
if "%platform%"=="x86" (set architecture=-A Win32)
|
||||||
|
if "%appveyor_build_worker_image%"=="Visual Studio 2022" (set generator="Visual Studio 17 2022" %architecture%)
|
||||||
|
if "%appveyor_build_worker_image%"=="Visual Studio 2017" (set generator="Visual Studio 15 2017" %architecture%)
|
||||||
|
install:
|
||||||
|
- |-
|
||||||
|
cd C:\Tools\vcpkg
|
||||||
|
git fetch --tags && git checkout 2023.01.09
|
||||||
|
cd %APPVEYOR_BUILD_FOLDER%
|
||||||
|
C:\Tools\vcpkg\bootstrap-vcpkg.bat -disableMetrics
|
||||||
|
C:\Tools\vcpkg\vcpkg integrate install
|
||||||
|
set VCPKG_DEFAULT_TRIPLET=%platform%-windows
|
||||||
|
vcpkg install sqlite3
|
||||||
|
rem The Visual Studio 2017 build worker image comes with CMake 3.16 only, and sqlite_orm will build the Catch2 dependency from source
|
||||||
|
if not "%appveyor_build_worker_image%"=="Visual Studio 2017" (vcpkg install catch2)
|
||||||
|
before_build:
|
||||||
|
- |-
|
||||||
|
mkdir compile
|
||||||
|
cd compile
|
||||||
|
cmake %SQLITE_ORM_CXX_STANDARD% -G %generator% -DCMAKE_TOOLCHAIN_FILE=C:/Tools/vcpkg/scripts/buildsystems/vcpkg.cmake ..
|
||||||
|
# build examples, and run tests (ie make & make test)
|
||||||
|
build_script:
|
||||||
|
- |-
|
||||||
|
cmake --build . --config %configuration% -- /m
|
||||||
|
ctest --verbose --output-on-failure --build-config %configuration%
|
||||||
|
|
||||||
|
-
|
||||||
|
# Linux
|
||||||
|
matrix:
|
||||||
|
only:
|
||||||
|
- appveyor_build_worker_image: Ubuntu
|
||||||
|
init:
|
||||||
|
- |-
|
||||||
|
echo $appveyor_build_worker_image
|
||||||
|
$CXX --version
|
||||||
|
cmake --version
|
||||||
|
# using custom vcpkg triplets for building and linking dynamic dependent libraries
|
||||||
|
install:
|
||||||
|
- |-
|
||||||
|
pushd $HOME/vcpkg
|
||||||
|
git fetch --tags && git checkout 2023.01.09
|
||||||
|
popd
|
||||||
|
$HOME/vcpkg/bootstrap-vcpkg.sh -disableMetrics
|
||||||
|
$HOME/vcpkg/vcpkg integrate install --overlay-triplets=vcpkg/triplets
|
||||||
|
vcpkg install sqlite3 catch2 --overlay-triplets=vcpkg/triplets
|
||||||
|
before_build:
|
||||||
|
- |-
|
||||||
|
mkdir compile
|
||||||
|
cd compile
|
||||||
|
cmake $SQLITE_ORM_CXX_STANDARD $cmake_build_examples --toolchain $HOME/vcpkg/scripts/buildsystems/vcpkg.cmake ..
|
||||||
|
# build examples, and run tests (ie make & make test)
|
||||||
|
build_script:
|
||||||
|
- |-
|
||||||
|
cmake --build . $cmake_build_parallel
|
||||||
|
ctest --verbose --output-on-failure
|
||||||
|
-
|
||||||
|
# macOS
|
||||||
|
matrix:
|
||||||
|
only:
|
||||||
|
- appveyor_build_worker_image: macOS
|
||||||
|
init:
|
||||||
|
- |-
|
||||||
|
echo $appveyor_build_worker_image
|
||||||
|
$CXX --version
|
||||||
|
cmake --version
|
||||||
|
# using custom vcpkg triplets for building and linking dynamic dependent libraries
|
||||||
|
install:
|
||||||
|
- |-
|
||||||
|
git clone --depth 1 --branch 2023.01.09 https://github.com/microsoft/vcpkg.git $HOME/vcpkg
|
||||||
|
$HOME/vcpkg/bootstrap-vcpkg.sh -disableMetrics
|
||||||
|
$HOME/vcpkg/vcpkg integrate install --overlay-triplets=vcpkg/triplets
|
||||||
|
vcpkg install sqlite3 catch2 --overlay-triplets=vcpkg/triplets
|
||||||
|
before_build:
|
||||||
|
- |-
|
||||||
|
mkdir compile
|
||||||
|
cd compile
|
||||||
|
cmake $SQLITE_ORM_CXX_STANDARD --toolchain $HOME/vcpkg/scripts/buildsystems/vcpkg.cmake ..
|
||||||
|
# build examples, and run tests (ie make & make test)
|
||||||
|
build_script:
|
||||||
|
- |-
|
||||||
|
cmake --build . --parallel
|
||||||
|
ctest --verbose --output-on-failure
|
|
@ -0,0 +1,4 @@
|
||||||
|
include(CMakeFindDependencyMacro)
|
||||||
|
find_dependency(SQLite3)
|
||||||
|
|
||||||
|
include(${CMAKE_CURRENT_LIST_DIR}/SqliteOrmTargets.cmake)
|
|
@ -0,0 +1,663 @@
|
||||||
|
#
|
||||||
|
# ucm.cmake - useful cmake macros
|
||||||
|
#
|
||||||
|
# Copyright (c) 2016 Viktor Kirilov
|
||||||
|
#
|
||||||
|
# Distributed under the MIT Software License
|
||||||
|
# See accompanying file LICENSE.txt or copy at
|
||||||
|
# https://opensource.org/licenses/MIT
|
||||||
|
#
|
||||||
|
# The documentation can be found at the library's page:
|
||||||
|
# https://github.com/onqtam/ucm
|
||||||
|
|
||||||
|
cmake_minimum_required(VERSION 2.8.12)
|
||||||
|
|
||||||
|
include(CMakeParseArguments)
|
||||||
|
|
||||||
|
# optionally include cotire - the git submodule might not be inited (or the user might have already included it)
|
||||||
|
if(NOT COMMAND cotire)
|
||||||
|
include(${CMAKE_CURRENT_LIST_DIR}/../cotire/CMake/cotire.cmake OPTIONAL)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(COMMAND cotire AND "1.7.9" VERSION_LESS "${COTIRE_CMAKE_MODULE_VERSION}")
|
||||||
|
set(ucm_with_cotire 1)
|
||||||
|
else()
|
||||||
|
set(ucm_with_cotire 0)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
option(UCM_UNITY_BUILD "Enable unity build for targets registered with the ucm_add_target() macro" OFF)
|
||||||
|
option(UCM_NO_COTIRE_FOLDER "Do not use a cotire folder in the solution explorer for all unity and cotire related targets" ON)
|
||||||
|
|
||||||
|
# ucm_add_flags
|
||||||
|
# Adds compiler flags to CMAKE_<LANG>_FLAGS or to a specific config
|
||||||
|
macro(ucm_add_flags)
|
||||||
|
cmake_parse_arguments(ARG "C;CXX;CLEAR_OLD" "" "CONFIG" ${ARGN})
|
||||||
|
|
||||||
|
if(NOT ARG_CONFIG)
|
||||||
|
set(ARG_CONFIG " ")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
foreach(CONFIG ${ARG_CONFIG})
|
||||||
|
# determine to which flags to add
|
||||||
|
if(NOT ${CONFIG} STREQUAL " ")
|
||||||
|
string(TOUPPER ${CONFIG} CONFIG)
|
||||||
|
set(CXX_FLAGS CMAKE_CXX_FLAGS_${CONFIG})
|
||||||
|
set(C_FLAGS CMAKE_C_FLAGS_${CONFIG})
|
||||||
|
else()
|
||||||
|
set(CXX_FLAGS CMAKE_CXX_FLAGS)
|
||||||
|
set(C_FLAGS CMAKE_C_FLAGS)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# clear the old flags
|
||||||
|
if(${ARG_CLEAR_OLD})
|
||||||
|
if("${ARG_CXX}" OR NOT "${ARG_C}")
|
||||||
|
set(${CXX_FLAGS} "")
|
||||||
|
endif()
|
||||||
|
if("${ARG_C}" OR NOT "${ARG_CXX}")
|
||||||
|
set(${C_FLAGS} "")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# add all the passed flags
|
||||||
|
foreach(flag ${ARG_UNPARSED_ARGUMENTS})
|
||||||
|
if("${ARG_CXX}" OR NOT "${ARG_C}")
|
||||||
|
set(${CXX_FLAGS} "${${CXX_FLAGS}} ${flag}")
|
||||||
|
endif()
|
||||||
|
if("${ARG_C}" OR NOT "${ARG_CXX}")
|
||||||
|
set(${C_FLAGS} "${${C_FLAGS}} ${flag}")
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
endmacro()
|
||||||
|
|
||||||
|
# ucm_set_flags
|
||||||
|
# Sets the CMAKE_<LANG>_FLAGS compiler flags or for a specific config
|
||||||
|
macro(ucm_set_flags)
|
||||||
|
ucm_add_flags(CLEAR_OLD ${ARGN})
|
||||||
|
endmacro()
|
||||||
|
|
||||||
|
# ucm_add_linker_flags
|
||||||
|
# Adds linker flags to CMAKE_<TYPE>_LINKER_FLAGS or to a specific config
|
||||||
|
macro(ucm_add_linker_flags)
|
||||||
|
cmake_parse_arguments(ARG "CLEAR_OLD;EXE;MODULE;SHARED;STATIC" "" "CONFIG" ${ARGN})
|
||||||
|
|
||||||
|
if(NOT ARG_CONFIG)
|
||||||
|
set(ARG_CONFIG " ")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
foreach(CONFIG ${ARG_CONFIG})
|
||||||
|
string(TOUPPER "${CONFIG}" CONFIG)
|
||||||
|
|
||||||
|
if(NOT ${ARG_EXE} AND NOT ${ARG_MODULE} AND NOT ${ARG_SHARED} AND NOT ${ARG_STATIC})
|
||||||
|
set(ARG_EXE 1)
|
||||||
|
set(ARG_MODULE 1)
|
||||||
|
set(ARG_SHARED 1)
|
||||||
|
set(ARG_STATIC 1)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(flags_configs "")
|
||||||
|
if(${ARG_EXE})
|
||||||
|
if(NOT "${CONFIG}" STREQUAL " ")
|
||||||
|
list(APPEND flags_configs CMAKE_EXE_LINKER_FLAGS_${CONFIG})
|
||||||
|
else()
|
||||||
|
list(APPEND flags_configs CMAKE_EXE_LINKER_FLAGS)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
if(${ARG_MODULE})
|
||||||
|
if(NOT "${CONFIG}" STREQUAL " ")
|
||||||
|
list(APPEND flags_configs CMAKE_MODULE_LINKER_FLAGS_${CONFIG})
|
||||||
|
else()
|
||||||
|
list(APPEND flags_configs CMAKE_MODULE_LINKER_FLAGS)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
if(${ARG_SHARED})
|
||||||
|
if(NOT "${CONFIG}" STREQUAL " ")
|
||||||
|
list(APPEND flags_configs CMAKE_SHARED_LINKER_FLAGS_${CONFIG})
|
||||||
|
else()
|
||||||
|
list(APPEND flags_configs CMAKE_SHARED_LINKER_FLAGS)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
if(${ARG_STATIC})
|
||||||
|
if(NOT "${CONFIG}" STREQUAL " ")
|
||||||
|
list(APPEND flags_configs CMAKE_STATIC_LINKER_FLAGS_${CONFIG})
|
||||||
|
else()
|
||||||
|
list(APPEND flags_configs CMAKE_STATIC_LINKER_FLAGS)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# clear the old flags
|
||||||
|
if(${ARG_CLEAR_OLD})
|
||||||
|
foreach(flags ${flags_configs})
|
||||||
|
set(${flags} "")
|
||||||
|
endforeach()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# add all the passed flags
|
||||||
|
foreach(flag ${ARG_UNPARSED_ARGUMENTS})
|
||||||
|
foreach(flags ${flags_configs})
|
||||||
|
set(${flags} "${${flags}} ${flag}")
|
||||||
|
endforeach()
|
||||||
|
endforeach()
|
||||||
|
endforeach()
|
||||||
|
endmacro()
|
||||||
|
|
||||||
|
# ucm_set_linker_flags
|
||||||
|
# Sets the CMAKE_<TYPE>_LINKER_FLAGS linker flags or for a specific config
|
||||||
|
macro(ucm_set_linker_flags)
|
||||||
|
ucm_add_linker_flags(CLEAR_OLD ${ARGN})
|
||||||
|
endmacro()
|
||||||
|
|
||||||
|
# ucm_gather_flags
|
||||||
|
# Gathers all lists of flags for printing or manipulation
|
||||||
|
macro(ucm_gather_flags with_linker result)
|
||||||
|
set(${result} "")
|
||||||
|
# add the main flags without a config
|
||||||
|
list(APPEND ${result} CMAKE_C_FLAGS)
|
||||||
|
list(APPEND ${result} CMAKE_CXX_FLAGS)
|
||||||
|
if(${with_linker})
|
||||||
|
list(APPEND ${result} CMAKE_EXE_LINKER_FLAGS)
|
||||||
|
list(APPEND ${result} CMAKE_MODULE_LINKER_FLAGS)
|
||||||
|
list(APPEND ${result} CMAKE_SHARED_LINKER_FLAGS)
|
||||||
|
list(APPEND ${result} CMAKE_STATIC_LINKER_FLAGS)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if("${CMAKE_CONFIGURATION_TYPES}" STREQUAL "" AND NOT "${CMAKE_BUILD_TYPE}" STREQUAL "")
|
||||||
|
# handle single config generators - like makefiles/ninja - when CMAKE_BUILD_TYPE is set
|
||||||
|
string(TOUPPER ${CMAKE_BUILD_TYPE} config)
|
||||||
|
list(APPEND ${result} CMAKE_C_FLAGS_${config})
|
||||||
|
list(APPEND ${result} CMAKE_CXX_FLAGS_${config})
|
||||||
|
if(${with_linker})
|
||||||
|
list(APPEND ${result} CMAKE_EXE_LINKER_FLAGS_${config})
|
||||||
|
list(APPEND ${result} CMAKE_MODULE_LINKER_FLAGS_${config})
|
||||||
|
list(APPEND ${result} CMAKE_SHARED_LINKER_FLAGS_${config})
|
||||||
|
list(APPEND ${result} CMAKE_STATIC_LINKER_FLAGS_${config})
|
||||||
|
endif()
|
||||||
|
else()
|
||||||
|
# handle multi config generators (like msvc, xcode)
|
||||||
|
foreach(config ${CMAKE_CONFIGURATION_TYPES})
|
||||||
|
string(TOUPPER ${config} config)
|
||||||
|
list(APPEND ${result} CMAKE_C_FLAGS_${config})
|
||||||
|
list(APPEND ${result} CMAKE_CXX_FLAGS_${config})
|
||||||
|
if(${with_linker})
|
||||||
|
list(APPEND ${result} CMAKE_EXE_LINKER_FLAGS_${config})
|
||||||
|
list(APPEND ${result} CMAKE_MODULE_LINKER_FLAGS_${config})
|
||||||
|
list(APPEND ${result} CMAKE_SHARED_LINKER_FLAGS_${config})
|
||||||
|
list(APPEND ${result} CMAKE_STATIC_LINKER_FLAGS_${config})
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
endif()
|
||||||
|
endmacro()
|
||||||
|
|
||||||
|
# ucm_set_runtime
|
||||||
|
# Sets the runtime (static/dynamic) for msvc/gcc
|
||||||
|
macro(ucm_set_runtime)
|
||||||
|
cmake_parse_arguments(ARG "STATIC;DYNAMIC" "" "" ${ARGN})
|
||||||
|
|
||||||
|
if(ARG_UNPARSED_ARGUMENTS)
|
||||||
|
message(FATAL_ERROR "unrecognized arguments: ${ARG_UNPARSED_ARGUMENTS}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" STREQUAL "")
|
||||||
|
message(AUTHOR_WARNING "ucm_set_runtime() does not support clang yet!")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
ucm_gather_flags(0 flags_configs)
|
||||||
|
|
||||||
|
# add/replace the flags
|
||||||
|
# note that if the user has messed with the flags directly this function might fail
|
||||||
|
# - for example if with MSVC and the user has removed the flags - here we just switch/replace them
|
||||||
|
if("${ARG_STATIC}")
|
||||||
|
foreach(flags ${flags_configs})
|
||||||
|
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU")
|
||||||
|
if(NOT ${flags} MATCHES "-static-libstdc\\+\\+")
|
||||||
|
set(${flags} "${${flags}} -static-libstdc++")
|
||||||
|
endif()
|
||||||
|
if(NOT ${flags} MATCHES "-static-libgcc")
|
||||||
|
set(${flags} "${${flags}} -static-libgcc")
|
||||||
|
endif()
|
||||||
|
elseif(MSVC)
|
||||||
|
if(${flags} MATCHES "/MD")
|
||||||
|
string(REGEX REPLACE "/MD" "/MT" ${flags} "${${flags}}")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
elseif("${ARG_DYNAMIC}")
|
||||||
|
foreach(flags ${flags_configs})
|
||||||
|
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU")
|
||||||
|
if(${flags} MATCHES "-static-libstdc\\+\\+")
|
||||||
|
string(REGEX REPLACE "-static-libstdc\\+\\+" "" ${flags} "${${flags}}")
|
||||||
|
endif()
|
||||||
|
if(${flags} MATCHES "-static-libgcc")
|
||||||
|
string(REGEX REPLACE "-static-libgcc" "" ${flags} "${${flags}}")
|
||||||
|
endif()
|
||||||
|
elseif(MSVC)
|
||||||
|
if(${flags} MATCHES "/MT")
|
||||||
|
string(REGEX REPLACE "/MT" "/MD" ${flags} "${${flags}}")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
endif()
|
||||||
|
endmacro()
|
||||||
|
|
||||||
|
# ucm_print_flags
|
||||||
|
# Prints all compiler flags for all configurations
|
||||||
|
macro(ucm_print_flags)
|
||||||
|
ucm_gather_flags(1 flags_configs)
|
||||||
|
message("")
|
||||||
|
foreach(flags ${flags_configs})
|
||||||
|
message("${flags}: ${${flags}}")
|
||||||
|
endforeach()
|
||||||
|
message("")
|
||||||
|
endmacro()
|
||||||
|
|
||||||
|
# ucm_set_xcode_attrib
|
||||||
|
# Set xcode attributes - name value CONFIG config1 conifg2..
|
||||||
|
macro(ucm_set_xcode_attrib)
|
||||||
|
cmake_parse_arguments(ARG "" "CLEAR" "CONFIG" ${ARGN})
|
||||||
|
|
||||||
|
if(NOT ARG_CONFIG)
|
||||||
|
set(ARG_CONFIG " ")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
foreach(CONFIG ${ARG_CONFIG})
|
||||||
|
# determine to which attributes to add
|
||||||
|
if(${CONFIG} STREQUAL " ")
|
||||||
|
if(${ARG_CLEAR})
|
||||||
|
# clear the old flags
|
||||||
|
unset(CMAKE_XCODE_ATTRIBUTE_${ARGV0})
|
||||||
|
else()
|
||||||
|
set(CMAKE_XCODE_ATTRIBUTE_${ARGV0} ${ARGV1})
|
||||||
|
endif()
|
||||||
|
else()
|
||||||
|
if(${ARG_CLEAR})
|
||||||
|
# clear the old flags
|
||||||
|
unset(CMAKE_XCODE_ATTRIBUTE_${ARGV0}[variant=${CONFIG}])
|
||||||
|
else()
|
||||||
|
set(CMAKE_XCODE_ATTRIBUTE_${ARGV0}[variant=${CONFIG}] ${ARGV1})
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
endmacro()
|
||||||
|
|
||||||
|
# ucm_count_sources
|
||||||
|
# Counts the number of source files
|
||||||
|
macro(ucm_count_sources)
|
||||||
|
cmake_parse_arguments(ARG "" "RESULT" "" ${ARGN})
|
||||||
|
if(${ARG_RESULT} STREQUAL "")
|
||||||
|
message(FATAL_ERROR "Need to pass RESULT and a variable name to ucm_count_sources()")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(result 0)
|
||||||
|
foreach(SOURCE_FILE ${ARG_UNPARSED_ARGUMENTS})
|
||||||
|
if("${SOURCE_FILE}" MATCHES \\.\(c|C|cc|cp|cpp|CPP|c\\+\\+|cxx|i|ii\)$)
|
||||||
|
math(EXPR result "${result} + 1")
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
set(${ARG_RESULT} ${result})
|
||||||
|
endmacro()
|
||||||
|
|
||||||
|
# ucm_include_file_in_sources
|
||||||
|
# Includes the file to the source with compiler flags
|
||||||
|
macro(ucm_include_file_in_sources)
|
||||||
|
cmake_parse_arguments(ARG "" "HEADER" "" ${ARGN})
|
||||||
|
if(${ARG_HEADER} STREQUAL "")
|
||||||
|
message(FATAL_ERROR "Need to pass HEADER and a header file to ucm_include_file_in_sources()")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
foreach(src ${ARG_UNPARSED_ARGUMENTS})
|
||||||
|
if(${src} MATCHES \\.\(c|C|cc|cp|cpp|CPP|c\\+\\+|cxx\)$)
|
||||||
|
# get old flags
|
||||||
|
get_source_file_property(old_compile_flags ${src} COMPILE_FLAGS)
|
||||||
|
if(old_compile_flags STREQUAL "NOTFOUND")
|
||||||
|
set(old_compile_flags "")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# update flags
|
||||||
|
if(MSVC)
|
||||||
|
set_source_files_properties(${src} PROPERTIES COMPILE_FLAGS
|
||||||
|
"${old_compile_flags} /FI\"${CMAKE_CURRENT_SOURCE_DIR}/${ARG_HEADER}\"")
|
||||||
|
else()
|
||||||
|
set_source_files_properties(${src} PROPERTIES COMPILE_FLAGS
|
||||||
|
"${old_compile_flags} -include \"${CMAKE_CURRENT_SOURCE_DIR}/${ARG_HEADER}\"")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
endmacro()
|
||||||
|
|
||||||
|
# ucm_dir_list
|
||||||
|
# Returns a list of subdirectories for a given directory
|
||||||
|
macro(ucm_dir_list thedir result)
|
||||||
|
file(GLOB sub-dir "${thedir}/*")
|
||||||
|
set(list_of_dirs "")
|
||||||
|
foreach(dir ${sub-dir})
|
||||||
|
if(IS_DIRECTORY ${dir})
|
||||||
|
get_filename_component(DIRNAME ${dir} NAME)
|
||||||
|
LIST(APPEND list_of_dirs ${DIRNAME})
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
set(${result} ${list_of_dirs})
|
||||||
|
endmacro()
|
||||||
|
|
||||||
|
# ucm_trim_front_words
|
||||||
|
# Trims X times the front word from a string separated with "/" and removes
|
||||||
|
# the front "/" characters after that (used for filters for visual studio)
|
||||||
|
macro(ucm_trim_front_words source out num_filter_trims)
|
||||||
|
set(result "${source}")
|
||||||
|
set(counter 0)
|
||||||
|
while(${counter} LESS ${num_filter_trims})
|
||||||
|
MATH(EXPR counter "${counter} + 1")
|
||||||
|
# removes everything at the front up to a "/" character
|
||||||
|
string(REGEX REPLACE "^([^/]+)" "" result "${result}")
|
||||||
|
# removes all consecutive "/" characters from the front
|
||||||
|
string(REGEX REPLACE "^(/+)" "" result "${result}")
|
||||||
|
endwhile()
|
||||||
|
set(${out} ${result})
|
||||||
|
endmacro()
|
||||||
|
|
||||||
|
# ucm_remove_files
|
||||||
|
# Removes source files from a list of sources (path is the relative path for it to be found)
|
||||||
|
macro(ucm_remove_files)
|
||||||
|
cmake_parse_arguments(ARG "" "FROM" "" ${ARGN})
|
||||||
|
|
||||||
|
if("${ARG_UNPARSED_ARGUMENTS}" STREQUAL "")
|
||||||
|
message(FATAL_ERROR "Need to pass some relative files to ucm_remove_files()")
|
||||||
|
endif()
|
||||||
|
if(${ARG_FROM} STREQUAL "")
|
||||||
|
message(FATAL_ERROR "Need to pass FROM and a variable name to ucm_remove_files()")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
foreach(cur_file ${ARG_UNPARSED_ARGUMENTS})
|
||||||
|
list(REMOVE_ITEM ${ARG_FROM} ${cur_file})
|
||||||
|
endforeach()
|
||||||
|
endmacro()
|
||||||
|
|
||||||
|
# ucm_remove_directories
|
||||||
|
# Removes all source files from the given directories from the sources list
|
||||||
|
macro(ucm_remove_directories)
|
||||||
|
cmake_parse_arguments(ARG "" "FROM" "MATCHES" ${ARGN})
|
||||||
|
|
||||||
|
if("${ARG_UNPARSED_ARGUMENTS}" STREQUAL "")
|
||||||
|
message(FATAL_ERROR "Need to pass some relative directories to ucm_remove_directories()")
|
||||||
|
endif()
|
||||||
|
if(${ARG_FROM} STREQUAL "")
|
||||||
|
message(FATAL_ERROR "Need to pass FROM and a variable name to ucm_remove_directories()")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
foreach(cur_dir ${ARG_UNPARSED_ARGUMENTS})
|
||||||
|
foreach(cur_file ${${ARG_FROM}})
|
||||||
|
string(REGEX MATCH ${cur_dir} res ${cur_file})
|
||||||
|
if(NOT "${res}" STREQUAL "")
|
||||||
|
if("${ARG_MATCHES}" STREQUAL "")
|
||||||
|
list(REMOVE_ITEM ${ARG_FROM} ${cur_file})
|
||||||
|
else()
|
||||||
|
foreach(curr_ptrn ${ARG_MATCHES})
|
||||||
|
string(REGEX MATCH ${curr_ptrn} res ${cur_file})
|
||||||
|
if(NOT "${res}" STREQUAL "")
|
||||||
|
list(REMOVE_ITEM ${ARG_FROM} ${cur_file})
|
||||||
|
break()
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
endforeach()
|
||||||
|
endmacro()
|
||||||
|
|
||||||
|
# ucm_add_files_impl
|
||||||
|
macro(ucm_add_files_impl result trim files)
|
||||||
|
foreach(cur_file ${files})
|
||||||
|
SET(${result} ${${result}} ${cur_file})
|
||||||
|
get_filename_component(FILEPATH ${cur_file} PATH)
|
||||||
|
ucm_trim_front_words("${FILEPATH}" FILEPATH "${trim}")
|
||||||
|
# replacing forward slashes with back slashes so filters can be generated (back slash used in parsing...)
|
||||||
|
STRING(REPLACE "/" "\\" FILTERS "${FILEPATH}")
|
||||||
|
SOURCE_GROUP("${FILTERS}" FILES ${cur_file})
|
||||||
|
endforeach()
|
||||||
|
endmacro()
|
||||||
|
|
||||||
|
# ucm_add_files
|
||||||
|
# Adds files to a list of sources
|
||||||
|
macro(ucm_add_files)
|
||||||
|
cmake_parse_arguments(ARG "" "TO;FILTER_POP" "" ${ARGN})
|
||||||
|
|
||||||
|
if("${ARG_UNPARSED_ARGUMENTS}" STREQUAL "")
|
||||||
|
message(FATAL_ERROR "Need to pass some relative files to ucm_add_files()")
|
||||||
|
endif()
|
||||||
|
if(${ARG_TO} STREQUAL "")
|
||||||
|
message(FATAL_ERROR "Need to pass TO and a variable name to ucm_add_files()")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if("${ARG_FILTER_POP}" STREQUAL "")
|
||||||
|
set(ARG_FILTER_POP 0)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
ucm_add_files_impl(${ARG_TO} ${ARG_FILTER_POP} "${ARG_UNPARSED_ARGUMENTS}")
|
||||||
|
endmacro()
|
||||||
|
|
||||||
|
# ucm_add_dir_impl
|
||||||
|
macro(ucm_add_dir_impl result rec trim dirs_in additional_ext)
|
||||||
|
set(dirs "${dirs_in}")
|
||||||
|
|
||||||
|
# handle the "" and "." cases
|
||||||
|
if("${dirs}" STREQUAL "" OR "${dirs}" STREQUAL ".")
|
||||||
|
set(dirs "./")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
foreach(cur_dir ${dirs})
|
||||||
|
# to circumvent some linux/cmake/path issues - barely made it work...
|
||||||
|
if(cur_dir STREQUAL "./")
|
||||||
|
set(cur_dir "")
|
||||||
|
else()
|
||||||
|
set(cur_dir "${cur_dir}/")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# since unix is case sensitive - add these valid extensions too
|
||||||
|
# we don't use "UNIX" but instead "CMAKE_HOST_UNIX" because we might be cross
|
||||||
|
# compiling (for example emscripten) under windows and UNIX may be set to 1
|
||||||
|
# Also OSX is case insensitive like windows...
|
||||||
|
set(additional_file_extensions "")
|
||||||
|
if(CMAKE_HOST_UNIX AND NOT APPLE)
|
||||||
|
set(additional_file_extensions
|
||||||
|
"${cur_dir}*.CPP"
|
||||||
|
"${cur_dir}*.C"
|
||||||
|
"${cur_dir}*.H"
|
||||||
|
"${cur_dir}*.HPP"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
foreach(ext ${additional_ext})
|
||||||
|
list(APPEND additional_file_extensions "${cur_dir}*.${ext}")
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
# find all sources and set them as result
|
||||||
|
FILE(GLOB found_sources RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||||
|
# https://gcc.gnu.org/onlinedocs/gcc-4.4.1/gcc/Overall-Options.html#index-file-name-suffix-71
|
||||||
|
# sources
|
||||||
|
"${cur_dir}*.cpp"
|
||||||
|
"${cur_dir}*.cxx"
|
||||||
|
"${cur_dir}*.c++"
|
||||||
|
"${cur_dir}*.cc"
|
||||||
|
"${cur_dir}*.cp"
|
||||||
|
"${cur_dir}*.c"
|
||||||
|
"${cur_dir}*.i"
|
||||||
|
"${cur_dir}*.ii"
|
||||||
|
# headers
|
||||||
|
"${cur_dir}*.h"
|
||||||
|
"${cur_dir}*.h++"
|
||||||
|
"${cur_dir}*.hpp"
|
||||||
|
"${cur_dir}*.hxx"
|
||||||
|
"${cur_dir}*.hh"
|
||||||
|
"${cur_dir}*.inl"
|
||||||
|
"${cur_dir}*.inc"
|
||||||
|
"${cur_dir}*.ipp"
|
||||||
|
"${cur_dir}*.ixx"
|
||||||
|
"${cur_dir}*.txx"
|
||||||
|
"${cur_dir}*.tpp"
|
||||||
|
"${cur_dir}*.tcc"
|
||||||
|
"${cur_dir}*.tpl"
|
||||||
|
${additional_file_extensions})
|
||||||
|
SET(${result} ${${result}} ${found_sources})
|
||||||
|
|
||||||
|
# set the proper filters
|
||||||
|
ucm_trim_front_words("${cur_dir}" cur_dir "${trim}")
|
||||||
|
# replacing forward slashes with back slashes so filters can be generated (back slash used in parsing...)
|
||||||
|
STRING(REPLACE "/" "\\" FILTERS "${cur_dir}")
|
||||||
|
SOURCE_GROUP("${FILTERS}" FILES ${found_sources})
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
if(${rec})
|
||||||
|
foreach(cur_dir ${dirs})
|
||||||
|
ucm_dir_list("${cur_dir}" subdirs)
|
||||||
|
foreach(subdir ${subdirs})
|
||||||
|
ucm_add_dir_impl(${result} ${rec} ${trim} "${cur_dir}/${subdir}" "${additional_ext}")
|
||||||
|
endforeach()
|
||||||
|
endforeach()
|
||||||
|
endif()
|
||||||
|
endmacro()
|
||||||
|
|
||||||
|
# ucm_add_dirs
|
||||||
|
# Adds all files from directories traversing them recursively to a list of sources
|
||||||
|
# and generates filters according to their location (accepts relative paths only).
|
||||||
|
# Also this macro trims X times the front word from the filter string for visual studio filters.
|
||||||
|
macro(ucm_add_dirs)
|
||||||
|
cmake_parse_arguments(ARG "RECURSIVE" "TO;FILTER_POP" "ADDITIONAL_EXT" ${ARGN})
|
||||||
|
|
||||||
|
if(${ARG_TO} STREQUAL "")
|
||||||
|
message(FATAL_ERROR "Need to pass TO and a variable name to ucm_add_dirs()")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if("${ARG_FILTER_POP}" STREQUAL "")
|
||||||
|
set(ARG_FILTER_POP 0)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
ucm_add_dir_impl(${ARG_TO} ${ARG_RECURSIVE} ${ARG_FILTER_POP} "${ARG_UNPARSED_ARGUMENTS}" "${ARG_ADDITIONAL_EXT}")
|
||||||
|
endmacro()
|
||||||
|
|
||||||
|
# ucm_add_target
|
||||||
|
# Adds a target eligible for cotiring - unity build and/or precompiled header
|
||||||
|
macro(ucm_add_target)
|
||||||
|
cmake_parse_arguments(ARG "UNITY" "NAME;TYPE;PCH_FILE;CPP_PER_UNITY" "UNITY_EXCLUDED;SOURCES" ${ARGN})
|
||||||
|
|
||||||
|
if(NOT "${ARG_UNPARSED_ARGUMENTS}" STREQUAL "")
|
||||||
|
message(FATAL_ERROR "Unrecognized options passed to ucm_add_target()")
|
||||||
|
endif()
|
||||||
|
if("${ARG_NAME}" STREQUAL "")
|
||||||
|
message(FATAL_ERROR "Need to pass NAME and a name for the target to ucm_add_target()")
|
||||||
|
endif()
|
||||||
|
set(valid_types EXECUTABLE STATIC SHARED MODULE)
|
||||||
|
list(FIND valid_types "${ARG_TYPE}" is_type_valid)
|
||||||
|
if(${is_type_valid} STREQUAL "-1")
|
||||||
|
message(FATAL_ERROR "Need to pass TYPE and the type for the target [EXECUTABLE/STATIC/SHARED/MODULE] to ucm_add_target()")
|
||||||
|
endif()
|
||||||
|
if("${ARG_SOURCES}" STREQUAL "")
|
||||||
|
message(FATAL_ERROR "Need to pass SOURCES and a list of source files to ucm_add_target()")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# init with the global unity flag
|
||||||
|
set(do_unity ${UCM_UNITY_BUILD})
|
||||||
|
|
||||||
|
# check the UNITY argument
|
||||||
|
if(NOT ARG_UNITY)
|
||||||
|
set(do_unity FALSE)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# if target is excluded through the exclusion list
|
||||||
|
list(FIND UCM_UNITY_BUILD_EXCLUDE_TARGETS ${ARG_NAME} is_target_excluded)
|
||||||
|
if(NOT ${is_target_excluded} STREQUAL "-1")
|
||||||
|
set(do_unity FALSE)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# unity build only for targets with > 1 source file (otherwise there will be an additional unnecessary target)
|
||||||
|
if(do_unity) # optimization
|
||||||
|
ucm_count_sources(${ARG_SOURCES} RESULT num_sources)
|
||||||
|
if(${num_sources} LESS 2)
|
||||||
|
set(do_unity FALSE)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(wanted_cotire ${do_unity})
|
||||||
|
|
||||||
|
# if cotire cannot be used
|
||||||
|
if(do_unity AND NOT ucm_with_cotire)
|
||||||
|
set(do_unity FALSE)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# inform the developer that the current target might benefit from a unity build
|
||||||
|
if(NOT ARG_UNITY AND ${UCM_UNITY_BUILD})
|
||||||
|
ucm_count_sources(${ARG_SOURCES} RESULT num_sources)
|
||||||
|
if(${num_sources} GREATER 1)
|
||||||
|
message(AUTHOR_WARNING "Target '${ARG_NAME}' may benefit from a unity build.\nIt has ${num_sources} sources - enable with UNITY flag")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# prepare for the unity build
|
||||||
|
set(orig_target ${ARG_NAME})
|
||||||
|
if(do_unity)
|
||||||
|
# the original target will be added with a different name than the requested
|
||||||
|
set(orig_target ${ARG_NAME}_ORIGINAL)
|
||||||
|
|
||||||
|
# exclude requested files from unity build of the current target
|
||||||
|
foreach(excluded_file "${ARG_UNITY_EXCLUDED}")
|
||||||
|
set_source_files_properties(${excluded_file} PROPERTIES COTIRE_EXCLUDED TRUE)
|
||||||
|
endforeach()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# add the original target
|
||||||
|
if(${ARG_TYPE} STREQUAL "EXECUTABLE")
|
||||||
|
add_executable(${orig_target} ${ARG_SOURCES})
|
||||||
|
else()
|
||||||
|
add_library(${orig_target} ${ARG_TYPE} ${ARG_SOURCES})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(do_unity)
|
||||||
|
# set the number of unity cpp files to be used for the unity target
|
||||||
|
if(NOT "${ARG_CPP_PER_UNITY}" STREQUAL "")
|
||||||
|
set_property(TARGET ${orig_target} PROPERTY COTIRE_UNITY_SOURCE_MAXIMUM_NUMBER_OF_INCLUDES "${ARG_CPP_PER_UNITY}")
|
||||||
|
else()
|
||||||
|
set_property(TARGET ${orig_target} PROPERTY COTIRE_UNITY_SOURCE_MAXIMUM_NUMBER_OF_INCLUDES "100")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT "${ARG_PCH_FILE}" STREQUAL "")
|
||||||
|
set_target_properties(${orig_target} PROPERTIES COTIRE_CXX_PREFIX_HEADER_INIT "${ARG_PCH_FILE}")
|
||||||
|
else()
|
||||||
|
set_target_properties(${orig_target} PROPERTIES COTIRE_ENABLE_PRECOMPILED_HEADER FALSE)
|
||||||
|
endif()
|
||||||
|
# add a unity target for the original one with the name intended for the original
|
||||||
|
set_target_properties(${orig_target} PROPERTIES COTIRE_UNITY_TARGET_NAME ${ARG_NAME})
|
||||||
|
|
||||||
|
# this is the library call that does the magic
|
||||||
|
cotire(${orig_target})
|
||||||
|
set_target_properties(clean_cotire PROPERTIES FOLDER "CMakePredefinedTargets")
|
||||||
|
|
||||||
|
# disable the original target and enable the unity one
|
||||||
|
get_target_property(unity_target_name ${orig_target} COTIRE_UNITY_TARGET_NAME)
|
||||||
|
set_target_properties(${orig_target} PROPERTIES EXCLUDE_FROM_ALL 1 EXCLUDE_FROM_DEFAULT_BUILD 1)
|
||||||
|
set_target_properties(${unity_target_name} PROPERTIES EXCLUDE_FROM_ALL 0 EXCLUDE_FROM_DEFAULT_BUILD 0)
|
||||||
|
|
||||||
|
# also set the name of the target output as the original one
|
||||||
|
set_target_properties(${unity_target_name} PROPERTIES OUTPUT_NAME ${ARG_NAME})
|
||||||
|
if(UCM_NO_COTIRE_FOLDER)
|
||||||
|
# reset the folder property so all unity targets dont end up in a single folder in the solution explorer of VS
|
||||||
|
set_target_properties(${unity_target_name} PROPERTIES FOLDER "")
|
||||||
|
endif()
|
||||||
|
set_target_properties(all_unity PROPERTIES FOLDER "CMakePredefinedTargets")
|
||||||
|
elseif(NOT "${ARG_PCH_FILE}" STREQUAL "")
|
||||||
|
set(wanted_cotire TRUE)
|
||||||
|
if(ucm_with_cotire)
|
||||||
|
set_target_properties(${orig_target} PROPERTIES COTIRE_ADD_UNITY_BUILD FALSE)
|
||||||
|
set_target_properties(${orig_target} PROPERTIES COTIRE_CXX_PREFIX_HEADER_INIT "${ARG_PCH_FILE}")
|
||||||
|
cotire(${orig_target})
|
||||||
|
set_target_properties(clean_cotire PROPERTIES FOLDER "CMakePredefinedTargets")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# print a message if the target was requested to be cotired but it couldn't
|
||||||
|
if(wanted_cotire AND NOT ucm_with_cotire)
|
||||||
|
if(NOT COMMAND cotire)
|
||||||
|
message(AUTHOR_WARNING "Target \"${ARG_NAME}\" not cotired because cotire isn't loaded")
|
||||||
|
else()
|
||||||
|
message(AUTHOR_WARNING "Target \"${ARG_NAME}\" not cotired because cotire is older than the required version")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
endmacro()
|
|
@ -0,0 +1,28 @@
|
||||||
|
include(FetchContent)
|
||||||
|
|
||||||
|
if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND BUILD_TESTING)
|
||||||
|
# FIND_PACKAGE_ARGS is available since 3.24
|
||||||
|
if(CMAKE_VERSION VERSION_LESS 3.24)
|
||||||
|
FetchContent_Declare(
|
||||||
|
Catch2
|
||||||
|
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
|
||||||
|
GIT_TAG v3.2.1
|
||||||
|
)
|
||||||
|
else()
|
||||||
|
FetchContent_Declare(
|
||||||
|
Catch2
|
||||||
|
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
|
||||||
|
GIT_TAG v3.2.1
|
||||||
|
# prefer find_package() over building from source
|
||||||
|
FIND_PACKAGE_ARGS 3 CONFIG
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND BUILD_TESTING)
|
||||||
|
# CMake <3.24: exposes targets only locally, but caches them. So call FetchContent_MakeAvailable again in the directory of usage
|
||||||
|
FetchContent_MakeAvailable(Catch2)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# CMake <3.24: exposes targets only locally, but caches them. So call find_package again in the directory of usage
|
||||||
|
find_package(SQLite3 REQUIRED)
|
|
@ -0,0 +1,196 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <type_traits> // std::enable_if, std::is_base_of, std::is_member_pointer, std::remove_const
|
||||||
|
#include <utility> // std::index_sequence, std::make_index_sequence
|
||||||
|
#include <string> // std::string
|
||||||
|
#include <sstream> // std::stringstream
|
||||||
|
#include <algorithm> // std::copy_n
|
||||||
|
|
||||||
|
#include "functional/cxx_universal.h" // ::size_t
|
||||||
|
#include "functional/cxx_type_traits_polyfill.h"
|
||||||
|
#include "type_traits.h"
|
||||||
|
#include "alias_traits.h"
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a common built-in class used for character based table aliases.
|
||||||
|
* For convenience there exist public type aliases `alias_a`, `alias_b`, ...
|
||||||
|
* The easiest way to create a table alias is using `"z"_alias.for_<Object>()`.
|
||||||
|
*/
|
||||||
|
template<class T, char A, char... X>
|
||||||
|
struct recordset_alias : alias_tag {
|
||||||
|
using type = T;
|
||||||
|
|
||||||
|
static std::string get() {
|
||||||
|
return {A, X...};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Column expression with table alias attached like 'C.ID'. This is not a column alias
|
||||||
|
*/
|
||||||
|
template<class T, class C>
|
||||||
|
struct alias_column_t {
|
||||||
|
using alias_type = T;
|
||||||
|
using column_type = C;
|
||||||
|
|
||||||
|
column_type column;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Encapsulates extracting the alias identifier of a non-alias.
|
||||||
|
*/
|
||||||
|
template<class T, class SFINAE = void>
|
||||||
|
struct alias_extractor {
|
||||||
|
static std::string extract() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string as_alias() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Encapsulates extracting the alias identifier of an alias.
|
||||||
|
*
|
||||||
|
* `extract()` always returns the alias identifier.
|
||||||
|
* `as_alias()` is used in contexts where a table is aliased.
|
||||||
|
*/
|
||||||
|
template<class A>
|
||||||
|
struct alias_extractor<A, match_if<is_alias, A>> {
|
||||||
|
static std::string extract() {
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << A::get();
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
// for column and regular table aliases -> alias identifier
|
||||||
|
template<class T = A, satisfies_not<std::is_same, polyfill::detected_t<type_t, T>, A> = true>
|
||||||
|
static std::string as_alias() {
|
||||||
|
return alias_extractor::extract();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to store alias for expression
|
||||||
|
*/
|
||||||
|
template<class T, class E>
|
||||||
|
struct as_t {
|
||||||
|
using alias_type = T;
|
||||||
|
using expression_type = E;
|
||||||
|
|
||||||
|
expression_type expression;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a common built-in class used for custom single-character column aliases.
|
||||||
|
* For convenience there exist type aliases `colalias_a`, `colalias_b`, ...
|
||||||
|
* The easiest way to create a column alias is using `"xyz"_col`.
|
||||||
|
*/
|
||||||
|
template<char A, char... X>
|
||||||
|
struct column_alias : alias_tag {
|
||||||
|
static std::string get() {
|
||||||
|
return {A, X...};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct alias_holder {
|
||||||
|
using type = T;
|
||||||
|
|
||||||
|
alias_holder() = default;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return column with table alias attached. Place it instead of a column statement in case you need to specify a
|
||||||
|
* column with table alias prefix like 'a.column'.
|
||||||
|
*/
|
||||||
|
template<class A, class C, std::enable_if_t<internal::is_table_alias_v<A>, bool> = true>
|
||||||
|
internal::alias_column_t<A, C> alias_column(C c) {
|
||||||
|
using aliased_type = internal::type_t<A>;
|
||||||
|
static_assert(std::is_same<polyfill::detected_t<internal::table_type_of_t, C>, aliased_type>::value,
|
||||||
|
"Column must be from aliased table");
|
||||||
|
return {c};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias a column expression.
|
||||||
|
*/
|
||||||
|
template<class A, class E, internal::satisfies<internal::is_column_alias, A> = true>
|
||||||
|
internal::as_t<A, E> as(E expression) {
|
||||||
|
return {std::move(expression)};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class A, internal::satisfies<internal::is_column_alias, A> = true>
|
||||||
|
internal::alias_holder<A> get() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
using alias_a = internal::recordset_alias<T, 'a'>;
|
||||||
|
template<class T>
|
||||||
|
using alias_b = internal::recordset_alias<T, 'b'>;
|
||||||
|
template<class T>
|
||||||
|
using alias_c = internal::recordset_alias<T, 'c'>;
|
||||||
|
template<class T>
|
||||||
|
using alias_d = internal::recordset_alias<T, 'd'>;
|
||||||
|
template<class T>
|
||||||
|
using alias_e = internal::recordset_alias<T, 'e'>;
|
||||||
|
template<class T>
|
||||||
|
using alias_f = internal::recordset_alias<T, 'f'>;
|
||||||
|
template<class T>
|
||||||
|
using alias_g = internal::recordset_alias<T, 'g'>;
|
||||||
|
template<class T>
|
||||||
|
using alias_h = internal::recordset_alias<T, 'h'>;
|
||||||
|
template<class T>
|
||||||
|
using alias_i = internal::recordset_alias<T, 'i'>;
|
||||||
|
template<class T>
|
||||||
|
using alias_j = internal::recordset_alias<T, 'j'>;
|
||||||
|
template<class T>
|
||||||
|
using alias_k = internal::recordset_alias<T, 'k'>;
|
||||||
|
template<class T>
|
||||||
|
using alias_l = internal::recordset_alias<T, 'l'>;
|
||||||
|
template<class T>
|
||||||
|
using alias_m = internal::recordset_alias<T, 'm'>;
|
||||||
|
template<class T>
|
||||||
|
using alias_n = internal::recordset_alias<T, 'n'>;
|
||||||
|
template<class T>
|
||||||
|
using alias_o = internal::recordset_alias<T, 'o'>;
|
||||||
|
template<class T>
|
||||||
|
using alias_p = internal::recordset_alias<T, 'p'>;
|
||||||
|
template<class T>
|
||||||
|
using alias_q = internal::recordset_alias<T, 'q'>;
|
||||||
|
template<class T>
|
||||||
|
using alias_r = internal::recordset_alias<T, 'r'>;
|
||||||
|
template<class T>
|
||||||
|
using alias_s = internal::recordset_alias<T, 's'>;
|
||||||
|
template<class T>
|
||||||
|
using alias_t = internal::recordset_alias<T, 't'>;
|
||||||
|
template<class T>
|
||||||
|
using alias_u = internal::recordset_alias<T, 'u'>;
|
||||||
|
template<class T>
|
||||||
|
using alias_v = internal::recordset_alias<T, 'v'>;
|
||||||
|
template<class T>
|
||||||
|
using alias_w = internal::recordset_alias<T, 'w'>;
|
||||||
|
template<class T>
|
||||||
|
using alias_x = internal::recordset_alias<T, 'x'>;
|
||||||
|
template<class T>
|
||||||
|
using alias_y = internal::recordset_alias<T, 'y'>;
|
||||||
|
template<class T>
|
||||||
|
using alias_z = internal::recordset_alias<T, 'z'>;
|
||||||
|
|
||||||
|
using colalias_a = internal::column_alias<'a'>;
|
||||||
|
using colalias_b = internal::column_alias<'b'>;
|
||||||
|
using colalias_c = internal::column_alias<'c'>;
|
||||||
|
using colalias_d = internal::column_alias<'d'>;
|
||||||
|
using colalias_e = internal::column_alias<'e'>;
|
||||||
|
using colalias_f = internal::column_alias<'f'>;
|
||||||
|
using colalias_g = internal::column_alias<'g'>;
|
||||||
|
using colalias_h = internal::column_alias<'h'>;
|
||||||
|
using colalias_i = internal::column_alias<'i'>;
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <type_traits> // std::remove_const, std::is_base_of, std::is_same
|
||||||
|
|
||||||
|
#include "functional/cxx_universal.h"
|
||||||
|
#include "functional/cxx_type_traits_polyfill.h"
|
||||||
|
#include "type_traits.h"
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
|
||||||
|
/** @short Base class for a custom table alias, column alias or expression alias.
|
||||||
|
*/
|
||||||
|
struct alias_tag {};
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
template<class A>
|
||||||
|
SQLITE_ORM_INLINE_VAR constexpr bool is_alias_v = std::is_base_of<alias_tag, A>::value;
|
||||||
|
|
||||||
|
template<class A>
|
||||||
|
using is_alias = polyfill::bool_constant<is_alias_v<A>>;
|
||||||
|
|
||||||
|
/** @short Alias of a column in a record set, see `orm_column_alias`.
|
||||||
|
*/
|
||||||
|
template<class A>
|
||||||
|
SQLITE_ORM_INLINE_VAR constexpr bool is_column_alias_v =
|
||||||
|
polyfill::conjunction_v<is_alias<A>, polyfill::negation<polyfill::is_detected<type_t, A>>>;
|
||||||
|
|
||||||
|
template<class A>
|
||||||
|
using is_column_alias = is_alias<A>;
|
||||||
|
|
||||||
|
/** @short Alias of any type of record set, see `orm_recordset_alias`.
|
||||||
|
*/
|
||||||
|
template<class A>
|
||||||
|
SQLITE_ORM_INLINE_VAR constexpr bool is_recordset_alias_v =
|
||||||
|
polyfill::conjunction_v<is_alias<A>, polyfill::is_detected<type_t, A>>;
|
||||||
|
|
||||||
|
template<class A>
|
||||||
|
using is_recordset_alias = polyfill::bool_constant<is_recordset_alias_v<A>>;
|
||||||
|
|
||||||
|
/** @short Alias of a concrete table, see `orm_table_alias`.
|
||||||
|
*/
|
||||||
|
template<class A>
|
||||||
|
SQLITE_ORM_INLINE_VAR constexpr bool is_table_alias_v = polyfill::conjunction_v<
|
||||||
|
is_recordset_alias<A>,
|
||||||
|
polyfill::negation<std::is_same<polyfill::detected_t<type_t, A>, std::remove_const_t<A>>>>;
|
||||||
|
|
||||||
|
template<class A>
|
||||||
|
using is_table_alias = polyfill::bool_constant<is_table_alias_v<A>>;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,145 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <sqlite3.h>
|
||||||
|
|
||||||
|
#include "row_extractor.h"
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
|
||||||
|
struct arg_value {
|
||||||
|
|
||||||
|
arg_value() : arg_value(nullptr) {}
|
||||||
|
|
||||||
|
arg_value(sqlite3_value* value_) : value(value_) {}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
T get() const {
|
||||||
|
return row_extractor<T>().extract(this->value);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_null() const {
|
||||||
|
auto type = sqlite3_value_type(this->value);
|
||||||
|
return type == SQLITE_NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_text() const {
|
||||||
|
auto type = sqlite3_value_type(this->value);
|
||||||
|
return type == SQLITE_TEXT;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_integer() const {
|
||||||
|
auto type = sqlite3_value_type(this->value);
|
||||||
|
return type == SQLITE_INTEGER;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_float() const {
|
||||||
|
auto type = sqlite3_value_type(this->value);
|
||||||
|
return type == SQLITE_FLOAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_blob() const {
|
||||||
|
auto type = sqlite3_value_type(this->value);
|
||||||
|
return type == SQLITE_BLOB;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool empty() const {
|
||||||
|
return this->value == nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
sqlite3_value* value = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct arg_values {
|
||||||
|
|
||||||
|
struct iterator {
|
||||||
|
|
||||||
|
iterator(const arg_values& container_, int index_) :
|
||||||
|
container(container_), index(index_),
|
||||||
|
currentValue(index_ < int(container_.size()) ? container_[index_] : arg_value()) {}
|
||||||
|
|
||||||
|
iterator& operator++() {
|
||||||
|
++this->index;
|
||||||
|
if(this->index < int(this->container.size())) {
|
||||||
|
this->currentValue = this->container[this->index];
|
||||||
|
} else {
|
||||||
|
this->currentValue = {};
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator operator++(int) {
|
||||||
|
auto res = *this;
|
||||||
|
++this->index;
|
||||||
|
if(this->index < int(this->container.size())) {
|
||||||
|
this->currentValue = this->container[this->index];
|
||||||
|
} else {
|
||||||
|
this->currentValue = {};
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
arg_value operator*() const {
|
||||||
|
if(this->index < int(this->container.size()) && this->index >= 0) {
|
||||||
|
return this->currentValue;
|
||||||
|
} else {
|
||||||
|
throw std::system_error{orm_error_code::index_is_out_of_bounds};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
arg_value* operator->() const {
|
||||||
|
return &this->currentValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const iterator& other) const {
|
||||||
|
return &other.container == &this->container && other.index == this->index;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator!=(const iterator& other) const {
|
||||||
|
return !(*this == other);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const arg_values& container;
|
||||||
|
int index = 0;
|
||||||
|
mutable arg_value currentValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
arg_values() : arg_values(0, nullptr) {}
|
||||||
|
|
||||||
|
arg_values(int argsCount_, sqlite3_value** values_) : argsCount(argsCount_), values(values_) {}
|
||||||
|
|
||||||
|
size_t size() const {
|
||||||
|
return this->argsCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool empty() const {
|
||||||
|
return 0 == this->argsCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
arg_value operator[](int index) const {
|
||||||
|
if(index < this->argsCount && index >= 0) {
|
||||||
|
sqlite3_value* value = this->values[index];
|
||||||
|
return {value};
|
||||||
|
} else {
|
||||||
|
throw std::system_error{orm_error_code::index_is_out_of_bounds};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
arg_value at(int index) const {
|
||||||
|
return this->operator[](index);
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator begin() const {
|
||||||
|
return {*this, 0};
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator end() const {
|
||||||
|
return {*this, this->argsCount};
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
int argsCount = 0;
|
||||||
|
sqlite3_value** values = nullptr;
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
#pragma once
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper classes used by statement_binder and row_extractor.
|
||||||
|
*/
|
||||||
|
struct int_or_smaller_tag {};
|
||||||
|
struct bigint_tag {};
|
||||||
|
struct real_tag {};
|
||||||
|
|
||||||
|
template<class V>
|
||||||
|
using arithmetic_tag_t =
|
||||||
|
std::conditional_t<std::is_integral<V>::value,
|
||||||
|
// Integer class
|
||||||
|
std::conditional_t<sizeof(V) <= sizeof(int), int_or_smaller_tag, bigint_tag>,
|
||||||
|
// Floating-point class
|
||||||
|
real_tag>;
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <utility> // std::move
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct excluded_t {
|
||||||
|
using expression_type = T;
|
||||||
|
|
||||||
|
expression_type expression;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
internal::excluded_t<T> excluded(T expression) {
|
||||||
|
return {std::move(expression)};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <utility> // std::move
|
||||||
|
|
||||||
|
#include "../tags.h"
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct exists_t : condition_t, negatable_t {
|
||||||
|
using expression_type = T;
|
||||||
|
using self = exists_t<expression_type>;
|
||||||
|
|
||||||
|
expression_type expression;
|
||||||
|
|
||||||
|
exists_t(expression_type expression_) : expression(std::move(expression_)) {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EXISTS(condition).
|
||||||
|
* Example: storage.select(columns(&Agent::code, &Agent::name, &Agent::workingArea, &Agent::comission),
|
||||||
|
where(exists(select(asterisk<Customer>(),
|
||||||
|
where(is_equal(&Customer::grade, 3) and
|
||||||
|
is_equal(&Agent::code, &Customer::agentCode))))),
|
||||||
|
order_by(&Agent::comission));
|
||||||
|
*/
|
||||||
|
template<class T>
|
||||||
|
internal::exists_t<T> exists(T expression) {
|
||||||
|
return {std::move(expression)};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <tuple> // std::tuple, std::make_tuple
|
||||||
|
#include <type_traits> // std::true_type, std::false_type
|
||||||
|
#include <utility> // std::forward, std::move
|
||||||
|
|
||||||
|
#include "../functional/cxx_type_traits_polyfill.h"
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
template<class T, class... Args>
|
||||||
|
struct group_by_with_having {
|
||||||
|
using args_type = std::tuple<Args...>;
|
||||||
|
using expression_type = T;
|
||||||
|
|
||||||
|
args_type args;
|
||||||
|
expression_type expression;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GROUP BY pack holder.
|
||||||
|
*/
|
||||||
|
template<class... Args>
|
||||||
|
struct group_by_t {
|
||||||
|
using args_type = std::tuple<Args...>;
|
||||||
|
|
||||||
|
args_type args;
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
group_by_with_having<T, Args...> having(T expression) {
|
||||||
|
return {std::move(this->args), std::move(expression)};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
using is_group_by = polyfill::disjunction<polyfill::is_specialization_of<T, group_by_t>,
|
||||||
|
polyfill::is_specialization_of<T, group_by_with_having>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HAVING holder.
|
||||||
|
* T is having argument type.
|
||||||
|
*/
|
||||||
|
template<class T>
|
||||||
|
struct having_t {
|
||||||
|
using expression_type = T;
|
||||||
|
|
||||||
|
expression_type expression;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
using is_having = polyfill::is_specialization_of<T, having_t>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GROUP BY column.
|
||||||
|
* Example: storage.get_all<Employee>(group_by(&Employee::name))
|
||||||
|
*/
|
||||||
|
template<class... Args>
|
||||||
|
internal::group_by_t<Args...> group_by(Args&&... args) {
|
||||||
|
return {std::make_tuple(std::forward<Args>(args)...)};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [Deprecation notice]: this function is deprecated and will be removed in v1.9. Please use `group_by(...).having(...)` instead.
|
||||||
|
*
|
||||||
|
* HAVING(expression).
|
||||||
|
* Example: storage.get_all<Employee>(group_by(&Employee::name), having(greater_than(count(&Employee::name), 2)));
|
||||||
|
*/
|
||||||
|
template<class T>
|
||||||
|
[[deprecated("Use group_by(...).having(...) instead")]] internal::having_t<T> having(T expression) {
|
||||||
|
return {std::move(expression)};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../functional/cxx_type_traits_polyfill.h"
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct into_t {
|
||||||
|
using type = T;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
using is_into = polyfill::is_specialization_of<T, into_t>;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
internal::into_t<T> into() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,108 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <tuple> // std::tuple, std::tuple_size
|
||||||
|
#include <string> // std::string
|
||||||
|
#include <vector> // std::vector
|
||||||
|
#include <sstream> // std::stringstream
|
||||||
|
#include <type_traits> // std::false_type, std::true_type
|
||||||
|
|
||||||
|
#include "../table_name_collector.h"
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
template<class T, class L>
|
||||||
|
void iterate_ast(const T& t, L&& lambda);
|
||||||
|
|
||||||
|
template<class... Args>
|
||||||
|
struct set_t {
|
||||||
|
using assigns_type = std::tuple<Args...>;
|
||||||
|
|
||||||
|
assigns_type assigns;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct is_set : std::false_type {};
|
||||||
|
|
||||||
|
template<class... Args>
|
||||||
|
struct is_set<set_t<Args...>> : std::true_type {};
|
||||||
|
|
||||||
|
struct dynamic_set_entry {
|
||||||
|
std::string serialized_value;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class C>
|
||||||
|
struct dynamic_set_t {
|
||||||
|
using context_t = C;
|
||||||
|
using entry_t = dynamic_set_entry;
|
||||||
|
using const_iterator = typename std::vector<entry_t>::const_iterator;
|
||||||
|
|
||||||
|
dynamic_set_t(const context_t& context_) : context(context_), collector(this->context.db_objects) {}
|
||||||
|
|
||||||
|
dynamic_set_t(const dynamic_set_t& other) = default;
|
||||||
|
dynamic_set_t(dynamic_set_t&& other) = default;
|
||||||
|
dynamic_set_t& operator=(const dynamic_set_t& other) = default;
|
||||||
|
dynamic_set_t& operator=(dynamic_set_t&& other) = default;
|
||||||
|
|
||||||
|
template<class L, class R>
|
||||||
|
void push_back(assign_t<L, R> assign) {
|
||||||
|
auto newContext = this->context;
|
||||||
|
newContext.skip_table_name = true;
|
||||||
|
iterate_ast(assign, this->collector);
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << serialize(assign.lhs, newContext) << ' ' << assign.serialize() << ' '
|
||||||
|
<< serialize(assign.rhs, context);
|
||||||
|
this->entries.push_back({ss.str()});
|
||||||
|
}
|
||||||
|
|
||||||
|
const_iterator begin() const {
|
||||||
|
return this->entries.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
const_iterator end() const {
|
||||||
|
return this->entries.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear() {
|
||||||
|
this->entries.clear();
|
||||||
|
this->collector.table_names.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<entry_t> entries;
|
||||||
|
context_t context;
|
||||||
|
table_name_collector<typename context_t::db_objects_type> collector;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class C>
|
||||||
|
struct is_set<dynamic_set_t<C>> : std::true_type {};
|
||||||
|
|
||||||
|
template<class C>
|
||||||
|
struct is_dynamic_set : std::false_type {};
|
||||||
|
|
||||||
|
template<class C>
|
||||||
|
struct is_dynamic_set<dynamic_set_t<C>> : std::true_type {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SET keyword used in UPDATE ... SET queries.
|
||||||
|
* Args must have `assign_t` type. E.g. set(assign(&User::id, 5)) or set(c(&User::id) = 5)
|
||||||
|
*/
|
||||||
|
template<class... Args>
|
||||||
|
internal::set_t<Args...> set(Args... args) {
|
||||||
|
using arg_tuple = std::tuple<Args...>;
|
||||||
|
static_assert(std::tuple_size<arg_tuple>::value ==
|
||||||
|
internal::count_tuple<arg_tuple, internal::is_assign_t>::value,
|
||||||
|
"set function accepts assign operators only");
|
||||||
|
return {std::make_tuple(std::forward<Args>(args)...)};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SET keyword used in UPDATE ... SET queries. It is dynamic version. It means use can add amount of arguments now known at compilation time but known at runtime.
|
||||||
|
*/
|
||||||
|
template<class S>
|
||||||
|
internal::dynamic_set_t<internal::serializer_context<typename S::db_objects_type>> dynamic_set(const S& storage) {
|
||||||
|
internal::serializer_context_builder<S> builder(storage);
|
||||||
|
return builder();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if SQLITE_VERSION_NUMBER >= 3024000
|
||||||
|
#include <tuple> // std::tuple
|
||||||
|
#include <utility> // std::forward, std::move
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "../functional/cxx_type_traits_polyfill.h"
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
namespace internal {
|
||||||
|
#if SQLITE_VERSION_NUMBER >= 3024000
|
||||||
|
template<class T, class A>
|
||||||
|
struct upsert_clause;
|
||||||
|
|
||||||
|
template<class... Args>
|
||||||
|
struct conflict_target {
|
||||||
|
using args_tuple = std::tuple<Args...>;
|
||||||
|
|
||||||
|
args_tuple args;
|
||||||
|
|
||||||
|
upsert_clause<args_tuple, std::tuple<>> do_nothing() {
|
||||||
|
return {std::move(this->args), {}};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class... ActionsArgs>
|
||||||
|
upsert_clause<args_tuple, std::tuple<ActionsArgs...>> do_update(ActionsArgs... actions) {
|
||||||
|
return {std::move(this->args), {std::forward<ActionsArgs>(actions)...}};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class... TargetArgs, class... ActionsArgs>
|
||||||
|
struct upsert_clause<std::tuple<TargetArgs...>, std::tuple<ActionsArgs...>> {
|
||||||
|
using target_args_tuple = std::tuple<TargetArgs...>;
|
||||||
|
using actions_tuple = std::tuple<ActionsArgs...>;
|
||||||
|
|
||||||
|
target_args_tuple target_args;
|
||||||
|
|
||||||
|
actions_tuple actions;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
using is_upsert_clause = polyfill::is_specialization_of<T, upsert_clause>;
|
||||||
|
#else
|
||||||
|
template<class T>
|
||||||
|
struct is_upsert_clause : polyfill::bool_constant<false> {};
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#if SQLITE_VERSION_NUMBER >= 3024000
|
||||||
|
/**
|
||||||
|
* ON CONFLICT upsert clause builder function.
|
||||||
|
* @example
|
||||||
|
* storage.insert(into<Employee>(),
|
||||||
|
* columns(&Employee::id, &Employee::name, &Employee::age, &Employee::address, &Employee::salary),
|
||||||
|
* values(std::make_tuple(3, "Sofia", 26, "Madrid", 15000.0),
|
||||||
|
* std::make_tuple(4, "Doja", 26, "LA", 25000.0)),
|
||||||
|
* on_conflict(&Employee::id).do_update(set(c(&Employee::name) = excluded(&Employee::name),
|
||||||
|
* c(&Employee::age) = excluded(&Employee::age),
|
||||||
|
* c(&Employee::address) = excluded(&Employee::address),
|
||||||
|
* c(&Employee::salary) = excluded(&Employee::salary))));
|
||||||
|
*/
|
||||||
|
template<class... Args>
|
||||||
|
internal::conflict_target<Args...> on_conflict(Args... args) {
|
||||||
|
return {std::tuple<Args...>(std::forward<Args>(args)...)};
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <type_traits> // std::false_type, std::true_type
|
||||||
|
#include <utility> // std::move
|
||||||
|
|
||||||
|
#include "../functional/cxx_universal.h"
|
||||||
|
#include "../functional/cxx_type_traits_polyfill.h"
|
||||||
|
#include "../serialize_result_type.h"
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
struct where_string {
|
||||||
|
serialize_result_type serialize() const {
|
||||||
|
return "WHERE";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WHERE argument holder.
|
||||||
|
* C is expression type. Can be any expression like: is_equal_t, is_null_t, exists_t etc
|
||||||
|
* Don't construct it manually. Call `where(...)` function instead.
|
||||||
|
*/
|
||||||
|
template<class C>
|
||||||
|
struct where_t : where_string {
|
||||||
|
using expression_type = C;
|
||||||
|
|
||||||
|
expression_type expression;
|
||||||
|
|
||||||
|
where_t(expression_type expression_) : expression(std::move(expression_)) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
SQLITE_ORM_INLINE_VAR constexpr bool is_where_v = polyfill::is_specialization_of_v<T, where_t>;
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
using is_where = polyfill::bool_constant<is_where_v<T>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WHERE clause. Use it to add WHERE conditions wherever you like.
|
||||||
|
* C is expression type. Can be any expression like: is_equal_t, is_null_t, exists_t etc
|
||||||
|
* @example
|
||||||
|
* // SELECT name
|
||||||
|
* // FROM letters
|
||||||
|
* // WHERE id > 3
|
||||||
|
* auto rows = storage.select(&Letter::name, where(greater_than(&Letter::id, 3)));
|
||||||
|
*/
|
||||||
|
template<class C>
|
||||||
|
internal::where_t<C> where(C expression) {
|
||||||
|
return {std::move(expression)};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,682 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector> // std::vector
|
||||||
|
#include <functional> // std::reference_wrapper
|
||||||
|
|
||||||
|
#include "tuple_helper/tuple_iteration.h"
|
||||||
|
#include "type_traits.h"
|
||||||
|
#include "conditions.h"
|
||||||
|
#include "alias.h"
|
||||||
|
#include "select_constraints.h"
|
||||||
|
#include "operators.h"
|
||||||
|
#include "core_functions.h"
|
||||||
|
#include "prepared_statement.h"
|
||||||
|
#include "values.h"
|
||||||
|
#include "function.h"
|
||||||
|
#include "ast/excluded.h"
|
||||||
|
#include "ast/upsert_clause.h"
|
||||||
|
#include "ast/where.h"
|
||||||
|
#include "ast/into.h"
|
||||||
|
#include "ast/group_by.h"
|
||||||
|
#include "ast/exists.h"
|
||||||
|
#include "ast/set.h"
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ast_iterator accepts any expression and a callable object
|
||||||
|
* which will be called for any node of provided expression.
|
||||||
|
* E.g. if we pass `where(is_equal(5, max(&User::id, 10))` then
|
||||||
|
* callable object will be called with 5, &User::id and 10.
|
||||||
|
* ast_iterator is used in finding literals to be bound to
|
||||||
|
* a statement, and to collect table names.
|
||||||
|
*
|
||||||
|
* Note that not all leaves of the expression tree are always visited:
|
||||||
|
* Column expressions can be more complex, but are passed as a whole to the callable.
|
||||||
|
* Examples are `column_pointer<>` and `alias_column_t<>`.
|
||||||
|
*
|
||||||
|
* To use `ast_iterator` call `iterate_ast(object, callable);`
|
||||||
|
*
|
||||||
|
* `T` is an ast element, e.g. where_t
|
||||||
|
*/
|
||||||
|
template<class T, class SFINAE = void>
|
||||||
|
struct ast_iterator {
|
||||||
|
using node_type = T;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* L is a callable type. Mostly is a templated lambda
|
||||||
|
*/
|
||||||
|
template<class L>
|
||||||
|
void operator()(const T& t, L& lambda) const {
|
||||||
|
lambda(t);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simplified API
|
||||||
|
*/
|
||||||
|
template<class T, class L>
|
||||||
|
void iterate_ast(const T& t, L&& lambda) {
|
||||||
|
ast_iterator<T> iterator;
|
||||||
|
iterator(t, lambda);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef SQLITE_ORM_OPTIONAL_SUPPORTED
|
||||||
|
template<class T>
|
||||||
|
struct ast_iterator<as_optional_t<T>, void> {
|
||||||
|
using node_type = as_optional_t<T>;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void operator()(const node_type& node, L& lambda) const {
|
||||||
|
iterate_ast(node.value, lambda);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#endif // SQLITE_ORM_OPTIONAL_SUPPORTED
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct ast_iterator<std::reference_wrapper<T>, void> {
|
||||||
|
using node_type = std::reference_wrapper<T>;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void operator()(const node_type& expression, L& lambda) const {
|
||||||
|
iterate_ast(expression.get(), lambda);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class... Args>
|
||||||
|
struct ast_iterator<group_by_t<Args...>, void> {
|
||||||
|
using node_type = group_by_t<Args...>;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void operator()(const node_type& expression, L& lambda) const {
|
||||||
|
iterate_ast(expression.args, lambda);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct ast_iterator<excluded_t<T>, void> {
|
||||||
|
using node_type = excluded_t<T>;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void operator()(const node_type& expression, L& lambda) const {
|
||||||
|
iterate_ast(expression.expression, lambda);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct ast_iterator<T, match_if<is_upsert_clause, T>> {
|
||||||
|
using node_type = T;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void operator()(const node_type& expression, L& lambda) const {
|
||||||
|
iterate_ast(expression.actions, lambda);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class C>
|
||||||
|
struct ast_iterator<where_t<C>, void> {
|
||||||
|
using node_type = where_t<C>;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void operator()(const node_type& expression, L& lambda) const {
|
||||||
|
iterate_ast(expression.expression, lambda);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct ast_iterator<T, match_if<is_binary_condition, T>> {
|
||||||
|
using node_type = T;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void operator()(const node_type& binaryCondition, L& lambda) const {
|
||||||
|
iterate_ast(binaryCondition.l, lambda);
|
||||||
|
iterate_ast(binaryCondition.r, lambda);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class L, class R, class... Ds>
|
||||||
|
struct ast_iterator<binary_operator<L, R, Ds...>, void> {
|
||||||
|
using node_type = binary_operator<L, R, Ds...>;
|
||||||
|
|
||||||
|
template<class C>
|
||||||
|
void operator()(const node_type& binaryOperator, C& lambda) const {
|
||||||
|
iterate_ast(binaryOperator.lhs, lambda);
|
||||||
|
iterate_ast(binaryOperator.rhs, lambda);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class... Args>
|
||||||
|
struct ast_iterator<columns_t<Args...>, void> {
|
||||||
|
using node_type = columns_t<Args...>;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void operator()(const node_type& cols, L& lambda) const {
|
||||||
|
iterate_ast(cols.columns, lambda);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class L, class A>
|
||||||
|
struct ast_iterator<dynamic_in_t<L, A>, void> {
|
||||||
|
using node_type = dynamic_in_t<L, A>;
|
||||||
|
|
||||||
|
template<class C>
|
||||||
|
void operator()(const node_type& in, C& lambda) const {
|
||||||
|
iterate_ast(in.left, lambda);
|
||||||
|
iterate_ast(in.argument, lambda);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class L, class... Args>
|
||||||
|
struct ast_iterator<in_t<L, Args...>, void> {
|
||||||
|
using node_type = in_t<L, Args...>;
|
||||||
|
|
||||||
|
template<class C>
|
||||||
|
void operator()(const node_type& in, C& lambda) const {
|
||||||
|
iterate_ast(in.left, lambda);
|
||||||
|
iterate_ast(in.argument, lambda);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct ast_iterator<std::vector<T>, void> {
|
||||||
|
using node_type = std::vector<T>;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void operator()(const node_type& vec, L& lambda) const {
|
||||||
|
for(auto& i: vec) {
|
||||||
|
iterate_ast(i, lambda);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct ast_iterator<std::vector<char>, void> {
|
||||||
|
using node_type = std::vector<char>;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void operator()(const node_type& vec, L& lambda) const {
|
||||||
|
lambda(vec);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct ast_iterator<T, match_if<is_compound_operator, T>> {
|
||||||
|
using node_type = T;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void operator()(const node_type& c, L& lambda) const {
|
||||||
|
iterate_ast(c.left, lambda);
|
||||||
|
iterate_ast(c.right, lambda);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct ast_iterator<into_t<T>, void> {
|
||||||
|
using node_type = into_t<T>;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void operator()(const node_type& /*node*/, L& /*lambda*/) const {
|
||||||
|
//..
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class... Args>
|
||||||
|
struct ast_iterator<insert_raw_t<Args...>, void> {
|
||||||
|
using node_type = insert_raw_t<Args...>;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void operator()(const node_type& node, L& lambda) const {
|
||||||
|
iterate_ast(node.args, lambda);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class... Args>
|
||||||
|
struct ast_iterator<replace_raw_t<Args...>, void> {
|
||||||
|
using node_type = replace_raw_t<Args...>;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void operator()(const node_type& node, L& lambda) const {
|
||||||
|
iterate_ast(node.args, lambda);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T, class... Args>
|
||||||
|
struct ast_iterator<select_t<T, Args...>, void> {
|
||||||
|
using node_type = select_t<T, Args...>;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void operator()(const node_type& sel, L& lambda) const {
|
||||||
|
iterate_ast(sel.col, lambda);
|
||||||
|
iterate_ast(sel.conditions, lambda);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T, class R, class... Args>
|
||||||
|
struct ast_iterator<get_all_t<T, R, Args...>, void> {
|
||||||
|
using node_type = get_all_t<T, R, Args...>;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void operator()(const node_type& get, L& lambda) const {
|
||||||
|
iterate_ast(get.conditions, lambda);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T, class... Args>
|
||||||
|
struct ast_iterator<get_all_pointer_t<T, Args...>, void> {
|
||||||
|
using node_type = get_all_pointer_t<T, Args...>;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void operator()(const node_type& get, L& lambda) const {
|
||||||
|
iterate_ast(get.conditions, lambda);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef SQLITE_ORM_OPTIONAL_SUPPORTED
|
||||||
|
template<class T, class... Args>
|
||||||
|
struct ast_iterator<get_all_optional_t<T, Args...>, void> {
|
||||||
|
using node_type = get_all_optional_t<T, Args...>;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void operator()(const node_type& get, L& lambda) const {
|
||||||
|
iterate_ast(get.conditions, lambda);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#endif // SQLITE_ORM_OPTIONAL_SUPPORTED
|
||||||
|
|
||||||
|
template<class S, class... Wargs>
|
||||||
|
struct ast_iterator<update_all_t<S, Wargs...>, void> {
|
||||||
|
using node_type = update_all_t<S, Wargs...>;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void operator()(const node_type& u, L& lambda) const {
|
||||||
|
iterate_ast(u.set, lambda);
|
||||||
|
iterate_ast(u.conditions, lambda);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T, class... Args>
|
||||||
|
struct ast_iterator<remove_all_t<T, Args...>, void> {
|
||||||
|
using node_type = remove_all_t<T, Args...>;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void operator()(const node_type& r, L& lambda) const {
|
||||||
|
iterate_ast(r.conditions, lambda);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class... Args>
|
||||||
|
struct ast_iterator<set_t<Args...>, void> {
|
||||||
|
using node_type = set_t<Args...>;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void operator()(const node_type& node, L& lambda) const {
|
||||||
|
iterate_ast(node.assigns, lambda);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class S>
|
||||||
|
struct ast_iterator<dynamic_set_t<S>, void> {
|
||||||
|
using node_type = dynamic_set_t<S>;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void operator()(const node_type& node, L& lambda) const {
|
||||||
|
iterate_ast(node.entries, lambda);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class... Args>
|
||||||
|
struct ast_iterator<std::tuple<Args...>, void> {
|
||||||
|
using node_type = std::tuple<Args...>;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void operator()(const node_type& node, L& lambda) const {
|
||||||
|
iterate_tuple(node, [&lambda](auto& v) {
|
||||||
|
iterate_ast(v, lambda);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T, class... Args>
|
||||||
|
struct ast_iterator<group_by_with_having<T, Args...>, void> {
|
||||||
|
using node_type = group_by_with_having<T, Args...>;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void operator()(const node_type& node, L& lambda) const {
|
||||||
|
iterate_ast(node.args, lambda);
|
||||||
|
iterate_ast(node.expression, lambda);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct ast_iterator<having_t<T>, void> {
|
||||||
|
using node_type = having_t<T>;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void operator()(const node_type& node, L& lambda) const {
|
||||||
|
iterate_ast(node.expression, lambda);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T, class E>
|
||||||
|
struct ast_iterator<cast_t<T, E>, void> {
|
||||||
|
using node_type = cast_t<T, E>;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void operator()(const node_type& c, L& lambda) const {
|
||||||
|
iterate_ast(c.expression, lambda);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct ast_iterator<exists_t<T>, void> {
|
||||||
|
using node_type = exists_t<T>;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void operator()(const node_type& node, L& lambda) const {
|
||||||
|
iterate_ast(node.expression, lambda);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class A, class T, class E>
|
||||||
|
struct ast_iterator<like_t<A, T, E>, void> {
|
||||||
|
using node_type = like_t<A, T, E>;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void operator()(const node_type& lk, L& lambda) const {
|
||||||
|
iterate_ast(lk.arg, lambda);
|
||||||
|
iterate_ast(lk.pattern, lambda);
|
||||||
|
lk.arg3.apply([&lambda](auto& value) {
|
||||||
|
iterate_ast(value, lambda);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class A, class T>
|
||||||
|
struct ast_iterator<glob_t<A, T>, void> {
|
||||||
|
using node_type = glob_t<A, T>;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void operator()(const node_type& lk, L& lambda) const {
|
||||||
|
iterate_ast(lk.arg, lambda);
|
||||||
|
iterate_ast(lk.pattern, lambda);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class A, class T>
|
||||||
|
struct ast_iterator<between_t<A, T>, void> {
|
||||||
|
using node_type = between_t<A, T>;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void operator()(const node_type& b, L& lambda) const {
|
||||||
|
iterate_ast(b.expr, lambda);
|
||||||
|
iterate_ast(b.b1, lambda);
|
||||||
|
iterate_ast(b.b2, lambda);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct ast_iterator<named_collate<T>, void> {
|
||||||
|
using node_type = named_collate<T>;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void operator()(const node_type& col, L& lambda) const {
|
||||||
|
iterate_ast(col.expr, lambda);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class C>
|
||||||
|
struct ast_iterator<negated_condition_t<C>, void> {
|
||||||
|
using node_type = negated_condition_t<C>;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void operator()(const node_type& neg, L& lambda) const {
|
||||||
|
iterate_ast(neg.c, lambda);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct ast_iterator<is_null_t<T>, void> {
|
||||||
|
using node_type = is_null_t<T>;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void operator()(const node_type& i, L& lambda) const {
|
||||||
|
iterate_ast(i.t, lambda);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct ast_iterator<is_not_null_t<T>, void> {
|
||||||
|
using node_type = is_not_null_t<T>;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void operator()(const node_type& i, L& lambda) const {
|
||||||
|
iterate_ast(i.t, lambda);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class F, class... Args>
|
||||||
|
struct ast_iterator<function_call<F, Args...>, void> {
|
||||||
|
using node_type = function_call<F, Args...>;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void operator()(const node_type& f, L& lambda) const {
|
||||||
|
iterate_ast(f.args, lambda);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class R, class S, class... Args>
|
||||||
|
struct ast_iterator<built_in_function_t<R, S, Args...>, void> {
|
||||||
|
using node_type = built_in_function_t<R, S, Args...>;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void operator()(const node_type& node, L& lambda) const {
|
||||||
|
iterate_ast(node.args, lambda);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class R, class S, class... Args>
|
||||||
|
struct ast_iterator<built_in_aggregate_function_t<R, S, Args...>, void> {
|
||||||
|
using node_type = built_in_aggregate_function_t<R, S, Args...>;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void operator()(const node_type& node, L& lambda) const {
|
||||||
|
iterate_ast(node.args, lambda);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class F, class W>
|
||||||
|
struct ast_iterator<filtered_aggregate_function<F, W>, void> {
|
||||||
|
using node_type = filtered_aggregate_function<F, W>;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void operator()(const node_type& node, L& lambda) const {
|
||||||
|
iterate_ast(node.function, lambda);
|
||||||
|
iterate_ast(node.where, lambda);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class Join>
|
||||||
|
struct ast_iterator<Join, match_if<is_constrained_join, Join>> {
|
||||||
|
using node_type = Join;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void operator()(const node_type& join, L& lambda) const {
|
||||||
|
iterate_ast(join.constraint, lambda);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct ast_iterator<on_t<T>, void> {
|
||||||
|
using node_type = on_t<T>;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void operator()(const node_type& on, L& lambda) const {
|
||||||
|
iterate_ast(on.arg, lambda);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// note: not strictly necessary as there's no binding support for USING;
|
||||||
|
// we provide it nevertheless, in line with on_t.
|
||||||
|
template<class T>
|
||||||
|
struct ast_iterator<T, std::enable_if_t<polyfill::is_specialization_of_v<T, using_t>>> {
|
||||||
|
using node_type = T;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void operator()(const node_type& o, L& lambda) const {
|
||||||
|
iterate_ast(o.column, lambda);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class R, class T, class E, class... Args>
|
||||||
|
struct ast_iterator<simple_case_t<R, T, E, Args...>, void> {
|
||||||
|
using node_type = simple_case_t<R, T, E, Args...>;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void operator()(const node_type& c, L& lambda) const {
|
||||||
|
c.case_expression.apply([&lambda](auto& c_) {
|
||||||
|
iterate_ast(c_, lambda);
|
||||||
|
});
|
||||||
|
iterate_tuple(c.args, [&lambda](auto& pair) {
|
||||||
|
iterate_ast(pair.first, lambda);
|
||||||
|
iterate_ast(pair.second, lambda);
|
||||||
|
});
|
||||||
|
c.else_expression.apply([&lambda](auto& el) {
|
||||||
|
iterate_ast(el, lambda);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T, class E>
|
||||||
|
struct ast_iterator<as_t<T, E>, void> {
|
||||||
|
using node_type = as_t<T, E>;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void operator()(const node_type& a, L& lambda) const {
|
||||||
|
iterate_ast(a.expression, lambda);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T, bool OI>
|
||||||
|
struct ast_iterator<limit_t<T, false, OI, void>, void> {
|
||||||
|
using node_type = limit_t<T, false, OI, void>;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void operator()(const node_type& a, L& lambda) const {
|
||||||
|
iterate_ast(a.lim, lambda);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T, class O>
|
||||||
|
struct ast_iterator<limit_t<T, true, false, O>, void> {
|
||||||
|
using node_type = limit_t<T, true, false, O>;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void operator()(const node_type& a, L& lambda) const {
|
||||||
|
iterate_ast(a.lim, lambda);
|
||||||
|
a.off.apply([&lambda](auto& value) {
|
||||||
|
iterate_ast(value, lambda);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T, class O>
|
||||||
|
struct ast_iterator<limit_t<T, true, true, O>, void> {
|
||||||
|
using node_type = limit_t<T, true, true, O>;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void operator()(const node_type& a, L& lambda) const {
|
||||||
|
a.off.apply([&lambda](auto& value) {
|
||||||
|
iterate_ast(value, lambda);
|
||||||
|
});
|
||||||
|
iterate_ast(a.lim, lambda);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct ast_iterator<distinct_t<T>, void> {
|
||||||
|
using node_type = distinct_t<T>;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void operator()(const node_type& a, L& lambda) const {
|
||||||
|
iterate_ast(a.value, lambda);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct ast_iterator<all_t<T>, void> {
|
||||||
|
using node_type = all_t<T>;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void operator()(const node_type& a, L& lambda) const {
|
||||||
|
iterate_ast(a.value, lambda);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct ast_iterator<bitwise_not_t<T>, void> {
|
||||||
|
using node_type = bitwise_not_t<T>;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void operator()(const node_type& a, L& lambda) const {
|
||||||
|
iterate_ast(a.argument, lambda);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class... Args>
|
||||||
|
struct ast_iterator<values_t<Args...>, void> {
|
||||||
|
using node_type = values_t<Args...>;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void operator()(const node_type& node, L& lambda) const {
|
||||||
|
iterate_ast(node.tuple, lambda);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct ast_iterator<dynamic_values_t<T>, void> {
|
||||||
|
using node_type = dynamic_values_t<T>;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void operator()(const node_type& node, L& lambda) const {
|
||||||
|
iterate_ast(node.vector, lambda);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Column alias or literal: skipped
|
||||||
|
*/
|
||||||
|
template<class T>
|
||||||
|
struct ast_iterator<T,
|
||||||
|
std::enable_if_t<polyfill::disjunction_v<polyfill::is_specialization_of<T, alias_holder>,
|
||||||
|
polyfill::is_specialization_of<T, literal_holder>,
|
||||||
|
is_column_alias<T>>>> {
|
||||||
|
using node_type = T;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void operator()(const node_type& /*node*/, L& /*lambda*/) const {}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class E>
|
||||||
|
struct ast_iterator<order_by_t<E>, void> {
|
||||||
|
using node_type = order_by_t<E>;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void operator()(const node_type& node, L& lambda) const {
|
||||||
|
iterate_ast(node.expression, lambda);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct ast_iterator<collate_t<T>, void> {
|
||||||
|
using node_type = collate_t<T>;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void operator()(const node_type& node, L& lambda) const {
|
||||||
|
iterate_ast(node.expr, lambda);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <sqlite3.h>
|
||||||
|
#include <system_error> // std::system_error
|
||||||
|
#include <string> // std::string
|
||||||
|
#include <memory>
|
||||||
|
#include <utility> // std::move, std::exchange
|
||||||
|
|
||||||
|
#include "error_code.h"
|
||||||
|
#include "connection_holder.h"
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A backup class. Don't construct it as is, call storage.make_backup_from or storage.make_backup_to instead.
|
||||||
|
* An instance of this class represents a wrapper around sqlite3_backup pointer. Use this class
|
||||||
|
* to have maximum control on a backup operation. In case you need a single backup in one line you
|
||||||
|
* can skip creating a backup_t instance and just call storage.backup_from or storage.backup_to function.
|
||||||
|
*/
|
||||||
|
struct backup_t {
|
||||||
|
backup_t(connection_ref to_,
|
||||||
|
const std::string& zDestName,
|
||||||
|
connection_ref from_,
|
||||||
|
const std::string& zSourceName,
|
||||||
|
std::unique_ptr<connection_holder> holder_) :
|
||||||
|
handle(sqlite3_backup_init(to_.get(), zDestName.c_str(), from_.get(), zSourceName.c_str())),
|
||||||
|
holder(std::move(holder_)), to(to_), from(from_) {
|
||||||
|
if(!this->handle) {
|
||||||
|
throw std::system_error{orm_error_code::failed_to_init_a_backup};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
backup_t(backup_t&& other) :
|
||||||
|
handle(std::exchange(other.handle, nullptr)), holder(std::move(other.holder)), to(other.to),
|
||||||
|
from(other.from) {}
|
||||||
|
|
||||||
|
~backup_t() {
|
||||||
|
if(this->handle) {
|
||||||
|
(void)sqlite3_backup_finish(this->handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls sqlite3_backup_step with pages argument
|
||||||
|
*/
|
||||||
|
int step(int pages) {
|
||||||
|
return sqlite3_backup_step(this->handle, pages);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns sqlite3_backup_remaining result
|
||||||
|
*/
|
||||||
|
int remaining() const {
|
||||||
|
return sqlite3_backup_remaining(this->handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns sqlite3_backup_pagecount result
|
||||||
|
*/
|
||||||
|
int pagecount() const {
|
||||||
|
return sqlite3_backup_pagecount(this->handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
sqlite3_backup* handle = nullptr;
|
||||||
|
std::unique_ptr<connection_holder> holder;
|
||||||
|
connection_ref to;
|
||||||
|
connection_ref from;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,79 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Note: This feature needs constexpr variables with external linkage.
|
||||||
|
* which can be achieved before C++17's inline variables, but differs from compiler to compiler.
|
||||||
|
* Hence we make it only available for compilers supporting inline variables.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef SQLITE_ORM_INLINE_VARIABLES_SUPPORTED
|
||||||
|
#include <type_traits> // std::integral_constant
|
||||||
|
#include <utility> // std::move
|
||||||
|
|
||||||
|
#include "functional/cxx_universal.h"
|
||||||
|
#include "pointer_value.h"
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
|
||||||
|
inline constexpr const char carray_pvt_name[] = "carray";
|
||||||
|
using carray_pvt = std::integral_constant<const char*, carray_pvt_name>;
|
||||||
|
|
||||||
|
template<typename P>
|
||||||
|
using carray_pointer_arg = pointer_arg<P, carray_pvt>;
|
||||||
|
template<typename P, typename D>
|
||||||
|
using carray_pointer_binding = pointer_binding<P, carray_pvt, D>;
|
||||||
|
template<typename P>
|
||||||
|
using static_carray_pointer_binding = static_pointer_binding<P, carray_pvt>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap a pointer of type 'carray' and its deleter function for binding it to a statement.
|
||||||
|
*
|
||||||
|
* Unless the deleter yields a nullptr 'xDestroy' function the ownership of the pointed-to-object
|
||||||
|
* is transferred to the pointer binding, which will delete it through
|
||||||
|
* the deleter when the statement finishes.
|
||||||
|
*/
|
||||||
|
template<class P, class D>
|
||||||
|
auto bindable_carray_pointer(P* p, D d) noexcept -> pointer_binding<P, carray_pvt, D> {
|
||||||
|
return bindable_pointer<carray_pvt>(p, std::move(d));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap a pointer of type 'carray' for binding it to a statement.
|
||||||
|
*
|
||||||
|
* Note: 'Static' means that ownership of the pointed-to-object won't be transferred
|
||||||
|
* and sqlite assumes the object pointed to is valid throughout the lifetime of a statement.
|
||||||
|
*/
|
||||||
|
template<class P>
|
||||||
|
auto statically_bindable_carray_pointer(P* p) noexcept -> static_pointer_binding<P, carray_pvt> {
|
||||||
|
return statically_bindable_pointer<carray_pvt>(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generalized form of the 'remember' SQL function that is a pass-through for values
|
||||||
|
* (it returns its argument unchanged using move semantics) but also saves the
|
||||||
|
* value that is passed through into a bound variable.
|
||||||
|
*/
|
||||||
|
template<typename P>
|
||||||
|
struct note_value_fn {
|
||||||
|
P operator()(P&& value, carray_pointer_arg<P> pv) const {
|
||||||
|
if(P* observer = pv) {
|
||||||
|
*observer = value;
|
||||||
|
}
|
||||||
|
return std::move(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr const char* name() {
|
||||||
|
return "note_value";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* remember(V, $PTR) extension function https://sqlite.org/src/file/ext/misc/remember.c
|
||||||
|
*/
|
||||||
|
struct remember_fn : note_value_fn<int64> {
|
||||||
|
static constexpr const char* name() {
|
||||||
|
return "remember";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
#endif
|
|
@ -0,0 +1,14 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
enum class collate_argument {
|
||||||
|
binary,
|
||||||
|
nocase,
|
||||||
|
rtrim,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,184 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <tuple> // std::tuple
|
||||||
|
#include <string> // std::string
|
||||||
|
#include <memory> // std::unique_ptr
|
||||||
|
#include <type_traits> // std::is_same, std::is_member_object_pointer
|
||||||
|
|
||||||
|
#include "functional/cxx_universal.h"
|
||||||
|
#include "functional/cxx_type_traits_polyfill.h"
|
||||||
|
#include "tuple_helper/tuple_traits.h"
|
||||||
|
#include "tuple_helper/tuple_filter.h"
|
||||||
|
#include "type_traits.h"
|
||||||
|
#include "member_traits/member_traits.h"
|
||||||
|
#include "type_is_nullable.h"
|
||||||
|
#include "constraints.h"
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
struct column_identifier {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Column name.
|
||||||
|
*/
|
||||||
|
std::string name;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct empty_setter {};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Encapsulates object member pointers that are used as column fields,
|
||||||
|
* and whose object is mapped to storage.
|
||||||
|
*
|
||||||
|
* G is a member object pointer or member function pointer
|
||||||
|
* S is a member function pointer or `empty_setter`
|
||||||
|
*/
|
||||||
|
template<class G, class S>
|
||||||
|
struct column_field {
|
||||||
|
using member_pointer_t = G;
|
||||||
|
using setter_type = S;
|
||||||
|
using object_type = member_object_type_t<G>;
|
||||||
|
using field_type = member_field_type_t<G>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Member pointer used to read a field value.
|
||||||
|
* If it is a object member pointer it is also used to write a field value.
|
||||||
|
*/
|
||||||
|
const member_pointer_t member_pointer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setter member function to write a field value
|
||||||
|
*/
|
||||||
|
SQLITE_ORM_NOUNIQUEADDRESS
|
||||||
|
const setter_type setter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simplified interface for `NOT NULL` constraint
|
||||||
|
*/
|
||||||
|
constexpr bool is_not_null() const {
|
||||||
|
return !type_is_nullable<field_type>::value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Encapsulates a tuple of column constraints.
|
||||||
|
*
|
||||||
|
* Op... is a constraints pack, e.g. primary_key_t, unique_t etc
|
||||||
|
*/
|
||||||
|
template<class... Op>
|
||||||
|
struct column_constraints {
|
||||||
|
using constraints_type = std::tuple<Op...>;
|
||||||
|
|
||||||
|
SQLITE_ORM_NOUNIQUEADDRESS
|
||||||
|
constraints_type constraints;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether contraints are of trait `Trait`
|
||||||
|
*/
|
||||||
|
template<template<class...> class Trait>
|
||||||
|
constexpr bool is() const {
|
||||||
|
return tuple_has<Trait, constraints_type>::value;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool is_generated() const {
|
||||||
|
#if SQLITE_VERSION_NUMBER >= 3031000
|
||||||
|
return is<is_generated_always>();
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simplified interface for `DEFAULT` constraint
|
||||||
|
* @return string representation of default value if it exists otherwise nullptr
|
||||||
|
*/
|
||||||
|
std::unique_ptr<std::string> default_value() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Column definition.
|
||||||
|
*
|
||||||
|
* It is a composition of orthogonal information stored in different base classes.
|
||||||
|
*/
|
||||||
|
template<class G, class S, class... Op>
|
||||||
|
struct column_t : column_identifier, column_field<G, S>, column_constraints<Op...> {
|
||||||
|
#ifndef SQLITE_ORM_AGGREGATE_BASES_SUPPORTED
|
||||||
|
column_t(std::string name, G memberPointer, S setter, std::tuple<Op...> op) :
|
||||||
|
column_identifier{std::move(name)}, column_field<G, S>{memberPointer, setter},
|
||||||
|
column_constraints<Op...>{std::move(op)} {}
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
SQLITE_ORM_INLINE_VAR constexpr bool is_column_v = polyfill::is_specialization_of_v<T, column_t>;
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
using is_column = polyfill::bool_constant<is_column_v<T>>;
|
||||||
|
|
||||||
|
template<class Elements, class F>
|
||||||
|
using col_index_sequence_with_field_type =
|
||||||
|
filter_tuple_sequence_t<Elements,
|
||||||
|
check_if_is_type<F>::template fn,
|
||||||
|
field_type_t,
|
||||||
|
filter_tuple_sequence_t<Elements, is_column>>;
|
||||||
|
|
||||||
|
template<class Elements, template<class...> class TraitFn>
|
||||||
|
using col_index_sequence_with = filter_tuple_sequence_t<Elements,
|
||||||
|
check_if_tuple_has<TraitFn>::template fn,
|
||||||
|
constraints_type_t,
|
||||||
|
filter_tuple_sequence_t<Elements, is_column>>;
|
||||||
|
|
||||||
|
template<class Elements, template<class...> class TraitFn>
|
||||||
|
using col_index_sequence_excluding = filter_tuple_sequence_t<Elements,
|
||||||
|
check_if_tuple_has_not<TraitFn>::template fn,
|
||||||
|
constraints_type_t,
|
||||||
|
filter_tuple_sequence_t<Elements, is_column>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Column builder function. You should use it to create columns instead of constructor
|
||||||
|
*/
|
||||||
|
template<class M, class... Op, internal::satisfies<std::is_member_object_pointer, M> = true>
|
||||||
|
internal::column_t<M, internal::empty_setter, Op...> make_column(std::string name, M m, Op... constraints) {
|
||||||
|
static_assert(polyfill::conjunction_v<internal::is_constraint<Op>...>, "Incorrect constraints pack");
|
||||||
|
|
||||||
|
SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(return {std::move(name), m, {}, std::make_tuple(constraints...)});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Column builder function with setter and getter. You should use it to create columns instead of constructor
|
||||||
|
*/
|
||||||
|
template<class G,
|
||||||
|
class S,
|
||||||
|
class... Op,
|
||||||
|
internal::satisfies<internal::is_getter, G> = true,
|
||||||
|
internal::satisfies<internal::is_setter, S> = true>
|
||||||
|
internal::column_t<G, S, Op...> make_column(std::string name, S setter, G getter, Op... constraints) {
|
||||||
|
static_assert(std::is_same<internal::setter_field_type_t<S>, internal::getter_field_type_t<G>>::value,
|
||||||
|
"Getter and setter must get and set same data type");
|
||||||
|
static_assert(polyfill::conjunction_v<internal::is_constraint<Op>...>, "Incorrect constraints pack");
|
||||||
|
|
||||||
|
SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(
|
||||||
|
return {std::move(name), getter, setter, std::make_tuple(constraints...)});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Column builder function with getter and setter (reverse order). You should use it to create columns instead of
|
||||||
|
* constructor
|
||||||
|
*/
|
||||||
|
template<class G,
|
||||||
|
class S,
|
||||||
|
class... Op,
|
||||||
|
internal::satisfies<internal::is_getter, G> = true,
|
||||||
|
internal::satisfies<internal::is_setter, S> = true>
|
||||||
|
internal::column_t<G, S, Op...> make_column(std::string name, G getter, S setter, Op... constraints) {
|
||||||
|
static_assert(std::is_same<internal::setter_field_type_t<S>, internal::getter_field_type_t<G>>::value,
|
||||||
|
"Getter and setter must get and set same data type");
|
||||||
|
static_assert(polyfill::conjunction_v<internal::is_constraint<Op>...>, "Incorrect constraints pack");
|
||||||
|
|
||||||
|
SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(
|
||||||
|
return {std::move(name), getter, setter, std::make_tuple(constraints...)});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,118 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <type_traits> // std::is_base_of
|
||||||
|
#include <string> // std::string
|
||||||
|
#include <vector> // std::vector
|
||||||
|
#include <functional> // std::reference_wrapper
|
||||||
|
#include <system_error>
|
||||||
|
#include <utility> // std::move
|
||||||
|
|
||||||
|
#include "tuple_helper/tuple_traits.h"
|
||||||
|
#include "tuple_helper/tuple_iteration.h"
|
||||||
|
#include "error_code.h"
|
||||||
|
#include "mapped_type_proxy.h"
|
||||||
|
#include "alias_traits.h"
|
||||||
|
#include "select_constraints.h"
|
||||||
|
#include "storage_lookup.h" // pick_table
|
||||||
|
#include "serializer_context.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
template<class T, class DBOs>
|
||||||
|
std::string serialize(const T&, const serializer_context<DBOs>&);
|
||||||
|
|
||||||
|
template<class T, class Ctx>
|
||||||
|
std::vector<std::string>& collect_table_column_names(std::vector<std::string>& collectedExpressions,
|
||||||
|
bool definedOrder,
|
||||||
|
const Ctx& context) {
|
||||||
|
if(definedOrder) {
|
||||||
|
auto& table = pick_table<mapped_type_proxy_t<T>>(context.db_objects);
|
||||||
|
collectedExpressions.reserve(collectedExpressions.size() + table.count_columns_amount());
|
||||||
|
table.for_each_column([qualified = !context.skip_table_name,
|
||||||
|
&tableName = table.name,
|
||||||
|
&collectedExpressions](const column_identifier& column) {
|
||||||
|
if(is_alias_v<T>) {
|
||||||
|
collectedExpressions.push_back(quote_identifier(alias_extractor<T>::extract()) + "." +
|
||||||
|
quote_identifier(column.name));
|
||||||
|
} else if(qualified) {
|
||||||
|
collectedExpressions.push_back(quote_identifier(tableName) + "." +
|
||||||
|
quote_identifier(column.name));
|
||||||
|
} else {
|
||||||
|
collectedExpressions.push_back(quote_identifier(column.name));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
collectedExpressions.reserve(collectedExpressions.size() + 1);
|
||||||
|
if(is_alias_v<T>) {
|
||||||
|
collectedExpressions.push_back(quote_identifier(alias_extractor<T>::extract()) + ".*");
|
||||||
|
} else if(!context.skip_table_name) {
|
||||||
|
const basic_table& table = pick_table<mapped_type_proxy_t<T>>(context.db_objects);
|
||||||
|
collectedExpressions.push_back(quote_identifier(table.name) + ".*");
|
||||||
|
} else {
|
||||||
|
collectedExpressions.emplace_back("*");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return collectedExpressions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @short Column expression collector.
|
||||||
|
*/
|
||||||
|
struct column_names_getter {
|
||||||
|
/**
|
||||||
|
* The default implementation simply serializes the passed argument.
|
||||||
|
*/
|
||||||
|
template<class E, class Ctx>
|
||||||
|
std::vector<std::string>& operator()(const E& t, const Ctx& context) {
|
||||||
|
auto columnExpression = serialize(t, context);
|
||||||
|
if(columnExpression.empty()) {
|
||||||
|
throw std::system_error{orm_error_code::column_not_found};
|
||||||
|
}
|
||||||
|
collectedExpressions.reserve(collectedExpressions.size() + 1);
|
||||||
|
collectedExpressions.push_back(std::move(columnExpression));
|
||||||
|
return collectedExpressions;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T, class Ctx>
|
||||||
|
std::vector<std::string>& operator()(const std::reference_wrapper<T>& expression, const Ctx& context) {
|
||||||
|
return (*this)(expression.get(), context);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T, class Ctx>
|
||||||
|
std::vector<std::string>& operator()(const asterisk_t<T>& expression, const Ctx& context) {
|
||||||
|
return collect_table_column_names<T>(collectedExpressions, expression.defined_order, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T, class Ctx>
|
||||||
|
std::vector<std::string>& operator()(const object_t<T>& expression, const Ctx& context) {
|
||||||
|
return collect_table_column_names<T>(collectedExpressions, expression.defined_order, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class... Args, class Ctx>
|
||||||
|
std::vector<std::string>& operator()(const columns_t<Args...>& cols, const Ctx& context) {
|
||||||
|
collectedExpressions.reserve(collectedExpressions.size() + cols.count);
|
||||||
|
iterate_tuple(cols.columns, [this, &context](auto& colExpr) {
|
||||||
|
(*this)(colExpr, context);
|
||||||
|
});
|
||||||
|
// note: `capacity() > size()` can occur in case `asterisk_t<>` does spell out the columns in defined order
|
||||||
|
if(mpl::instantiate<check_if_tuple_has_template<asterisk_t>,
|
||||||
|
typename columns_t<Args...>::columns_type>::value &&
|
||||||
|
collectedExpressions.capacity() > collectedExpressions.size()) {
|
||||||
|
collectedExpressions.shrink_to_fit();
|
||||||
|
}
|
||||||
|
return collectedExpressions;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> collectedExpressions;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T, class Ctx>
|
||||||
|
std::vector<std::string> get_column_names(const T& t, const Ctx& context) {
|
||||||
|
column_names_getter serializer;
|
||||||
|
return serializer(t, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string> // std::string
|
||||||
|
#include <utility> // std::move
|
||||||
|
|
||||||
|
#include "functional/cxx_core_features.h"
|
||||||
|
#include "conditions.h"
|
||||||
|
#include "alias_traits.h"
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
namespace internal {
|
||||||
|
/**
|
||||||
|
* This class is used to store explicit mapped type T and its column descriptor (member pointer/getter/setter).
|
||||||
|
* Is useful when mapped type is derived from other type and base class has members mapped to a storage.
|
||||||
|
*/
|
||||||
|
template<class T, class F>
|
||||||
|
struct column_pointer {
|
||||||
|
using self = column_pointer<T, F>;
|
||||||
|
using type = T;
|
||||||
|
using field_type = F;
|
||||||
|
|
||||||
|
field_type field;
|
||||||
|
|
||||||
|
template<class R>
|
||||||
|
is_equal_t<self, R> operator==(R rhs) const {
|
||||||
|
return {*this, std::move(rhs)};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class R>
|
||||||
|
is_not_equal_t<self, R> operator!=(R rhs) const {
|
||||||
|
return {*this, std::move(rhs)};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class R>
|
||||||
|
lesser_than_t<self, R> operator<(R rhs) const {
|
||||||
|
return {*this, std::move(rhs)};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class R>
|
||||||
|
lesser_or_equal_t<self, R> operator<=(R rhs) const {
|
||||||
|
return {*this, std::move(rhs)};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class R>
|
||||||
|
greater_than_t<self, R> operator>(R rhs) const {
|
||||||
|
return {*this, std::move(rhs)};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class R>
|
||||||
|
greater_or_equal_t<self, R> operator>=(R rhs) const {
|
||||||
|
return {*this, std::move(rhs)};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
SQLITE_ORM_INLINE_VAR constexpr bool is_column_pointer_v = polyfill::is_specialization_of_v<T, column_pointer>;
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
using is_column_pointer = polyfill::bool_constant<is_column_pointer_v<T>>;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,289 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <type_traits> // std::enable_if, std::is_same, std::decay, std::is_arithmetic, std::is_base_of
|
||||||
|
#include <tuple> // std::tuple
|
||||||
|
#include <functional> // std::reference_wrapper
|
||||||
|
|
||||||
|
#include "functional/cxx_universal.h"
|
||||||
|
#include "tuple_helper/tuple_traits.h"
|
||||||
|
#include "tuple_helper/tuple_fy.h"
|
||||||
|
#include "tuple_helper/tuple_filter.h"
|
||||||
|
#include "type_traits.h"
|
||||||
|
#include "member_traits/member_traits.h"
|
||||||
|
#include "mapped_type_proxy.h"
|
||||||
|
#include "core_functions.h"
|
||||||
|
#include "select_constraints.h"
|
||||||
|
#include "operators.h"
|
||||||
|
#include "rowid.h"
|
||||||
|
#include "alias.h"
|
||||||
|
#include "storage_traits.h"
|
||||||
|
#include "function.h"
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains the result type of expressions that form the columns of a select statement.
|
||||||
|
*
|
||||||
|
* This is a proxy class used to define what type must have result type depending on select
|
||||||
|
* arguments (member pointer, aggregate functions, etc). Below you can see specializations
|
||||||
|
* for different types. E.g. specialization for internal::length_t has `type` int cause
|
||||||
|
* LENGTH returns INTEGER in sqlite. Every column_result_t must have `type` type that equals
|
||||||
|
* c++ SELECT return type for T
|
||||||
|
* DBOs - db_objects_tuple type
|
||||||
|
* T - C++ type
|
||||||
|
* SFINAE - sfinae argument
|
||||||
|
*/
|
||||||
|
template<class DBOs, class T, class SFINAE = void>
|
||||||
|
struct column_result_t;
|
||||||
|
|
||||||
|
template<class DBOs, class T>
|
||||||
|
using column_result_of_t = typename column_result_t<DBOs, T>::type;
|
||||||
|
|
||||||
|
#ifdef SQLITE_ORM_OPTIONAL_SUPPORTED
|
||||||
|
template<class DBOs, class T>
|
||||||
|
struct column_result_t<DBOs, as_optional_t<T>, void> {
|
||||||
|
using type = std::optional<column_result_of_t<DBOs, T>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class DBOs, class T>
|
||||||
|
struct column_result_t<DBOs, std::optional<T>, void> {
|
||||||
|
using type = std::optional<T>;
|
||||||
|
};
|
||||||
|
#endif // SQLITE_ORM_OPTIONAL_SUPPORTED
|
||||||
|
|
||||||
|
template<class DBOs, class L, class A>
|
||||||
|
struct column_result_t<DBOs, dynamic_in_t<L, A>, void> {
|
||||||
|
using type = bool;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class DBOs, class L, class... Args>
|
||||||
|
struct column_result_t<DBOs, in_t<L, Args...>, void> {
|
||||||
|
using type = bool;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class DBOs, class T>
|
||||||
|
struct column_result_t<DBOs, T, match_if<std::is_member_pointer, T>> : member_field_type<T> {};
|
||||||
|
|
||||||
|
template<class DBOs, class R, class S, class... Args>
|
||||||
|
struct column_result_t<DBOs, built_in_function_t<R, S, Args...>, void> {
|
||||||
|
using type = R;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class DBOs, class R, class S, class... Args>
|
||||||
|
struct column_result_t<DBOs, built_in_aggregate_function_t<R, S, Args...>, void> {
|
||||||
|
using type = R;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class DBOs, class F, class... Args>
|
||||||
|
struct column_result_t<DBOs, function_call<F, Args...>, void> {
|
||||||
|
using type = typename callable_arguments<F>::return_type;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class DBOs, class X, class... Rest, class S>
|
||||||
|
struct column_result_t<DBOs, built_in_function_t<unique_ptr_result_of<X>, S, X, Rest...>, void> {
|
||||||
|
using type = std::unique_ptr<column_result_of_t<DBOs, X>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class DBOs, class X, class S>
|
||||||
|
struct column_result_t<DBOs, built_in_aggregate_function_t<unique_ptr_result_of<X>, S, X>, void> {
|
||||||
|
using type = std::unique_ptr<column_result_of_t<DBOs, X>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class DBOs, class T>
|
||||||
|
struct column_result_t<DBOs, count_asterisk_t<T>, void> {
|
||||||
|
using type = int;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class DBOs>
|
||||||
|
struct column_result_t<DBOs, nullptr_t, void> {
|
||||||
|
using type = nullptr_t;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class DBOs>
|
||||||
|
struct column_result_t<DBOs, count_asterisk_without_type, void> {
|
||||||
|
using type = int;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class DBOs, class T>
|
||||||
|
struct column_result_t<DBOs, distinct_t<T>, void> : column_result_t<DBOs, T> {};
|
||||||
|
|
||||||
|
template<class DBOs, class T>
|
||||||
|
struct column_result_t<DBOs, all_t<T>, void> : column_result_t<DBOs, T> {};
|
||||||
|
|
||||||
|
template<class DBOs, class L, class R>
|
||||||
|
struct column_result_t<DBOs, conc_t<L, R>, void> {
|
||||||
|
using type = std::string;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class DBOs, class L, class R>
|
||||||
|
struct column_result_t<DBOs, add_t<L, R>, void> {
|
||||||
|
using type = double;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class DBOs, class L, class R>
|
||||||
|
struct column_result_t<DBOs, sub_t<L, R>, void> {
|
||||||
|
using type = double;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class DBOs, class L, class R>
|
||||||
|
struct column_result_t<DBOs, mul_t<L, R>, void> {
|
||||||
|
using type = double;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class DBOs, class L, class R>
|
||||||
|
struct column_result_t<DBOs, div_t<L, R>, void> {
|
||||||
|
using type = double;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class DBOs, class L, class R>
|
||||||
|
struct column_result_t<DBOs, mod_t<L, R>, void> {
|
||||||
|
using type = double;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class DBOs, class L, class R>
|
||||||
|
struct column_result_t<DBOs, bitwise_shift_left_t<L, R>, void> {
|
||||||
|
using type = int;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class DBOs, class L, class R>
|
||||||
|
struct column_result_t<DBOs, bitwise_shift_right_t<L, R>, void> {
|
||||||
|
using type = int;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class DBOs, class L, class R>
|
||||||
|
struct column_result_t<DBOs, bitwise_and_t<L, R>, void> {
|
||||||
|
using type = int;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class DBOs, class L, class R>
|
||||||
|
struct column_result_t<DBOs, bitwise_or_t<L, R>, void> {
|
||||||
|
using type = int;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class DBOs, class T>
|
||||||
|
struct column_result_t<DBOs, bitwise_not_t<T>, void> {
|
||||||
|
using type = int;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class DBOs>
|
||||||
|
struct column_result_t<DBOs, rowid_t, void> {
|
||||||
|
using type = int64;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class DBOs>
|
||||||
|
struct column_result_t<DBOs, oid_t, void> {
|
||||||
|
using type = int64;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class DBOs>
|
||||||
|
struct column_result_t<DBOs, _rowid_t, void> {
|
||||||
|
using type = int64;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class DBOs, class T>
|
||||||
|
struct column_result_t<DBOs, table_rowid_t<T>, void> {
|
||||||
|
using type = int64;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class DBOs, class T>
|
||||||
|
struct column_result_t<DBOs, table_oid_t<T>, void> {
|
||||||
|
using type = int64;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class DBOs, class T>
|
||||||
|
struct column_result_t<DBOs, table__rowid_t<T>, void> {
|
||||||
|
using type = int64;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class DBOs, class T, class C>
|
||||||
|
struct column_result_t<DBOs, alias_column_t<T, C>, void> : column_result_t<DBOs, C> {};
|
||||||
|
|
||||||
|
template<class DBOs, class T, class F>
|
||||||
|
struct column_result_t<DBOs, column_pointer<T, F>, void> : column_result_t<DBOs, F> {};
|
||||||
|
|
||||||
|
template<class DBOs, class... Args>
|
||||||
|
struct column_result_t<DBOs, columns_t<Args...>, void> {
|
||||||
|
using type = tuple_cat_t<tuplify_t<column_result_of_t<DBOs, std::decay_t<Args>>>...>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class DBOs, class T, class... Args>
|
||||||
|
struct column_result_t<DBOs, select_t<T, Args...>> : column_result_t<DBOs, T> {};
|
||||||
|
|
||||||
|
template<class DBOs, class T>
|
||||||
|
struct column_result_t<DBOs, T, match_if<is_compound_operator, T>> {
|
||||||
|
using left_result = column_result_of_t<DBOs, typename T::left_type>;
|
||||||
|
using right_result = column_result_of_t<DBOs, typename T::right_type>;
|
||||||
|
static_assert(std::is_same<left_result, right_result>::value,
|
||||||
|
"Compound subselect queries must return same types");
|
||||||
|
using type = left_result;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class DBOs, class T>
|
||||||
|
struct column_result_t<DBOs, T, match_if<is_binary_condition, T>> {
|
||||||
|
using type = typename T::result_type;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Result for the most simple queries like `SELECT 1`
|
||||||
|
*/
|
||||||
|
template<class DBOs, class T>
|
||||||
|
struct column_result_t<DBOs, T, match_if<std::is_arithmetic, T>> {
|
||||||
|
using type = T;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Result for the most simple queries like `SELECT 'ototo'`
|
||||||
|
*/
|
||||||
|
template<class DBOs>
|
||||||
|
struct column_result_t<DBOs, const char*, void> {
|
||||||
|
using type = std::string;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class DBOs>
|
||||||
|
struct column_result_t<DBOs, std::string, void> {
|
||||||
|
using type = std::string;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class DBOs, class T, class E>
|
||||||
|
struct column_result_t<DBOs, as_t<T, E>, void> : column_result_t<DBOs, std::decay_t<E>> {};
|
||||||
|
|
||||||
|
template<class DBOs, class T>
|
||||||
|
struct column_result_t<DBOs, asterisk_t<T>, void>
|
||||||
|
: storage_traits::storage_mapped_columns<DBOs, mapped_type_proxy_t<T>> {};
|
||||||
|
|
||||||
|
template<class DBOs, class T>
|
||||||
|
struct column_result_t<DBOs, object_t<T>, void> {
|
||||||
|
using type = T;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class DBOs, class T, class E>
|
||||||
|
struct column_result_t<DBOs, cast_t<T, E>, void> {
|
||||||
|
using type = T;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class DBOs, class R, class T, class E, class... Args>
|
||||||
|
struct column_result_t<DBOs, simple_case_t<R, T, E, Args...>, void> {
|
||||||
|
using type = R;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class DBOs, class A, class T, class E>
|
||||||
|
struct column_result_t<DBOs, like_t<A, T, E>, void> {
|
||||||
|
using type = bool;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class DBOs, class A, class T>
|
||||||
|
struct column_result_t<DBOs, glob_t<A, T>, void> {
|
||||||
|
using type = bool;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class DBOs, class C>
|
||||||
|
struct column_result_t<DBOs, negated_condition_t<C>, void> {
|
||||||
|
using type = bool;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class DBOs, class T>
|
||||||
|
struct column_result_t<DBOs, std::reference_wrapper<T>, void> : column_result_t<DBOs, T> {};
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,75 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <sqlite3.h>
|
||||||
|
#include <atomic>
|
||||||
|
#include <string> // std::string
|
||||||
|
|
||||||
|
#include "error_code.h"
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
struct connection_holder {
|
||||||
|
|
||||||
|
connection_holder(std::string filename_) : filename(std::move(filename_)) {}
|
||||||
|
|
||||||
|
void retain() {
|
||||||
|
if(1 == ++this->_retain_count) {
|
||||||
|
auto rc = sqlite3_open(this->filename.c_str(), &this->db);
|
||||||
|
if(rc != SQLITE_OK) {
|
||||||
|
throw_translated_sqlite_error(db);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void release() {
|
||||||
|
if(0 == --this->_retain_count) {
|
||||||
|
auto rc = sqlite3_close(this->db);
|
||||||
|
if(rc != SQLITE_OK) {
|
||||||
|
throw_translated_sqlite_error(db);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlite3* get() const {
|
||||||
|
return this->db;
|
||||||
|
}
|
||||||
|
|
||||||
|
int retain_count() const {
|
||||||
|
return this->_retain_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string filename;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
sqlite3* db = nullptr;
|
||||||
|
std::atomic_int _retain_count{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct connection_ref {
|
||||||
|
connection_ref(connection_holder& holder_) : holder(holder_) {
|
||||||
|
this->holder.retain();
|
||||||
|
}
|
||||||
|
|
||||||
|
connection_ref(const connection_ref& other) : holder(other.holder) {
|
||||||
|
this->holder.retain();
|
||||||
|
}
|
||||||
|
|
||||||
|
connection_ref(connection_ref&& other) : holder(other.holder) {
|
||||||
|
this->holder.retain();
|
||||||
|
}
|
||||||
|
|
||||||
|
~connection_ref() {
|
||||||
|
this->holder.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlite3* get() const {
|
||||||
|
return this->holder.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
connection_holder& holder;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,560 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <system_error> // std::system_error
|
||||||
|
#include <ostream> // std::ostream
|
||||||
|
#include <string> // std::string
|
||||||
|
#include <tuple> // std::tuple, std::make_tuple
|
||||||
|
#include <type_traits> // std::is_base_of, std::false_type, std::true_type
|
||||||
|
|
||||||
|
#include "functional/cxx_universal.h"
|
||||||
|
#include "functional/cxx_type_traits_polyfill.h"
|
||||||
|
#include "functional/mpl.h"
|
||||||
|
#include "tuple_helper/same_or_void.h"
|
||||||
|
#include "tuple_helper/tuple_traits.h"
|
||||||
|
#include "tuple_helper/tuple_filter.h"
|
||||||
|
#include "type_traits.h"
|
||||||
|
#include "collate_argument.h"
|
||||||
|
#include "error_code.h"
|
||||||
|
#include "table_type_of.h"
|
||||||
|
#include "type_printer.h"
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AUTOINCREMENT constraint class.
|
||||||
|
*/
|
||||||
|
struct autoincrement_t {};
|
||||||
|
|
||||||
|
enum class conflict_clause_t {
|
||||||
|
rollback,
|
||||||
|
abort,
|
||||||
|
fail,
|
||||||
|
ignore,
|
||||||
|
replace,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct primary_key_base {
|
||||||
|
enum class order_by {
|
||||||
|
unspecified,
|
||||||
|
ascending,
|
||||||
|
descending,
|
||||||
|
};
|
||||||
|
struct {
|
||||||
|
order_by asc_option = order_by::unspecified;
|
||||||
|
conflict_clause_t conflict_clause = conflict_clause_t::rollback;
|
||||||
|
bool conflict_clause_is_on = false;
|
||||||
|
} options;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct primary_key_with_autoincrement {
|
||||||
|
using primary_key_type = T;
|
||||||
|
|
||||||
|
primary_key_type primary_key;
|
||||||
|
|
||||||
|
primary_key_with_autoincrement(primary_key_type primary_key_) : primary_key(primary_key_) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PRIMARY KEY constraint class.
|
||||||
|
* Cs is parameter pack which contains columns (member pointers and/or function pointers). Can be empty when
|
||||||
|
* used within `make_column` function.
|
||||||
|
*/
|
||||||
|
template<class... Cs>
|
||||||
|
struct primary_key_t : primary_key_base {
|
||||||
|
using self = primary_key_t<Cs...>;
|
||||||
|
using order_by = primary_key_base::order_by;
|
||||||
|
using columns_tuple = std::tuple<Cs...>;
|
||||||
|
|
||||||
|
columns_tuple columns;
|
||||||
|
|
||||||
|
primary_key_t(decltype(columns) columns) : columns(std::move(columns)) {}
|
||||||
|
|
||||||
|
self asc() const {
|
||||||
|
auto res = *this;
|
||||||
|
res.options.asc_option = order_by::ascending;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
self desc() const {
|
||||||
|
auto res = *this;
|
||||||
|
res.options.asc_option = order_by::descending;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
primary_key_with_autoincrement<self> autoincrement() const {
|
||||||
|
return {*this};
|
||||||
|
}
|
||||||
|
|
||||||
|
self on_conflict_rollback() const {
|
||||||
|
auto res = *this;
|
||||||
|
res.options.conflict_clause_is_on = true;
|
||||||
|
res.options.conflict_clause = conflict_clause_t::rollback;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
self on_conflict_abort() const {
|
||||||
|
auto res = *this;
|
||||||
|
res.options.conflict_clause_is_on = true;
|
||||||
|
res.options.conflict_clause = conflict_clause_t::abort;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
self on_conflict_fail() const {
|
||||||
|
auto res = *this;
|
||||||
|
res.options.conflict_clause_is_on = true;
|
||||||
|
res.options.conflict_clause = conflict_clause_t::fail;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
self on_conflict_ignore() const {
|
||||||
|
auto res = *this;
|
||||||
|
res.options.conflict_clause_is_on = true;
|
||||||
|
res.options.conflict_clause = conflict_clause_t::ignore;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
self on_conflict_replace() const {
|
||||||
|
auto res = *this;
|
||||||
|
res.options.conflict_clause_is_on = true;
|
||||||
|
res.options.conflict_clause = conflict_clause_t::replace;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct unique_base {
|
||||||
|
operator std::string() const {
|
||||||
|
return "UNIQUE";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UNIQUE constraint class.
|
||||||
|
*/
|
||||||
|
template<class... Args>
|
||||||
|
struct unique_t : unique_base {
|
||||||
|
using columns_tuple = std::tuple<Args...>;
|
||||||
|
|
||||||
|
columns_tuple columns;
|
||||||
|
|
||||||
|
unique_t(columns_tuple columns_) : columns(std::move(columns_)) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DEFAULT constraint class.
|
||||||
|
* T is a value type.
|
||||||
|
*/
|
||||||
|
template<class T>
|
||||||
|
struct default_t {
|
||||||
|
using value_type = T;
|
||||||
|
|
||||||
|
value_type value;
|
||||||
|
|
||||||
|
operator std::string() const {
|
||||||
|
return "DEFAULT";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#if SQLITE_VERSION_NUMBER >= 3006019
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FOREIGN KEY constraint class.
|
||||||
|
* Cs are columns which has foreign key
|
||||||
|
* Rs are column which C references to
|
||||||
|
* Available in SQLite 3.6.19 or higher
|
||||||
|
*/
|
||||||
|
|
||||||
|
template<class A, class B>
|
||||||
|
struct foreign_key_t;
|
||||||
|
|
||||||
|
enum class foreign_key_action {
|
||||||
|
none, // not specified
|
||||||
|
no_action,
|
||||||
|
restrict_,
|
||||||
|
set_null,
|
||||||
|
set_default,
|
||||||
|
cascade,
|
||||||
|
};
|
||||||
|
|
||||||
|
inline std::ostream& operator<<(std::ostream& os, foreign_key_action action) {
|
||||||
|
switch(action) {
|
||||||
|
case foreign_key_action::no_action:
|
||||||
|
os << "NO ACTION";
|
||||||
|
break;
|
||||||
|
case foreign_key_action::restrict_:
|
||||||
|
os << "RESTRICT";
|
||||||
|
break;
|
||||||
|
case foreign_key_action::set_null:
|
||||||
|
os << "SET NULL";
|
||||||
|
break;
|
||||||
|
case foreign_key_action::set_default:
|
||||||
|
os << "SET DEFAULT";
|
||||||
|
break;
|
||||||
|
case foreign_key_action::cascade:
|
||||||
|
os << "CASCADE";
|
||||||
|
break;
|
||||||
|
case foreign_key_action::none:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct on_update_delete_base {
|
||||||
|
const bool update; // true if update and false if delete
|
||||||
|
|
||||||
|
operator std::string() const {
|
||||||
|
if(this->update) {
|
||||||
|
return "ON UPDATE";
|
||||||
|
} else {
|
||||||
|
return "ON DELETE";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* F - foreign key class
|
||||||
|
*/
|
||||||
|
template<class F>
|
||||||
|
struct on_update_delete_t : on_update_delete_base {
|
||||||
|
using foreign_key_type = F;
|
||||||
|
|
||||||
|
const foreign_key_type& fk;
|
||||||
|
|
||||||
|
on_update_delete_t(decltype(fk) fk_, decltype(update) update_, foreign_key_action action_) :
|
||||||
|
on_update_delete_base{update_}, fk(fk_), _action(action_) {}
|
||||||
|
|
||||||
|
foreign_key_action _action = foreign_key_action::none;
|
||||||
|
|
||||||
|
foreign_key_type no_action() const {
|
||||||
|
auto res = this->fk;
|
||||||
|
if(update) {
|
||||||
|
res.on_update._action = foreign_key_action::no_action;
|
||||||
|
} else {
|
||||||
|
res.on_delete._action = foreign_key_action::no_action;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreign_key_type restrict_() const {
|
||||||
|
auto res = this->fk;
|
||||||
|
if(update) {
|
||||||
|
res.on_update._action = foreign_key_action::restrict_;
|
||||||
|
} else {
|
||||||
|
res.on_delete._action = foreign_key_action::restrict_;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreign_key_type set_null() const {
|
||||||
|
auto res = this->fk;
|
||||||
|
if(update) {
|
||||||
|
res.on_update._action = foreign_key_action::set_null;
|
||||||
|
} else {
|
||||||
|
res.on_delete._action = foreign_key_action::set_null;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreign_key_type set_default() const {
|
||||||
|
auto res = this->fk;
|
||||||
|
if(update) {
|
||||||
|
res.on_update._action = foreign_key_action::set_default;
|
||||||
|
} else {
|
||||||
|
res.on_delete._action = foreign_key_action::set_default;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreign_key_type cascade() const {
|
||||||
|
auto res = this->fk;
|
||||||
|
if(update) {
|
||||||
|
res.on_update._action = foreign_key_action::cascade;
|
||||||
|
} else {
|
||||||
|
res.on_delete._action = foreign_key_action::cascade;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
operator bool() const {
|
||||||
|
return this->_action != foreign_key_action::none;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class F>
|
||||||
|
bool operator==(const on_update_delete_t<F>& lhs, const on_update_delete_t<F>& rhs) {
|
||||||
|
return lhs._action == rhs._action;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class... Cs, class... Rs>
|
||||||
|
struct foreign_key_t<std::tuple<Cs...>, std::tuple<Rs...>> {
|
||||||
|
using columns_type = std::tuple<Cs...>;
|
||||||
|
using references_type = std::tuple<Rs...>;
|
||||||
|
using self = foreign_key_t<columns_type, references_type>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds obect type of all referenced columns.
|
||||||
|
*/
|
||||||
|
using target_type = typename same_or_void<table_type_of_t<Rs>...>::type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds obect type of all source columns.
|
||||||
|
*/
|
||||||
|
using source_type = typename same_or_void<table_type_of_t<Cs>...>::type;
|
||||||
|
|
||||||
|
columns_type columns;
|
||||||
|
references_type references;
|
||||||
|
|
||||||
|
on_update_delete_t<self> on_update;
|
||||||
|
on_update_delete_t<self> on_delete;
|
||||||
|
|
||||||
|
static_assert(std::tuple_size<columns_type>::value == std::tuple_size<references_type>::value,
|
||||||
|
"Columns size must be equal to references tuple");
|
||||||
|
static_assert(!std::is_same<target_type, void>::value, "All references must have the same type");
|
||||||
|
|
||||||
|
foreign_key_t(columns_type columns_, references_type references_) :
|
||||||
|
columns(std::move(columns_)), references(std::move(references_)),
|
||||||
|
on_update(*this, true, foreign_key_action::none), on_delete(*this, false, foreign_key_action::none) {}
|
||||||
|
|
||||||
|
foreign_key_t(const self& other) :
|
||||||
|
columns(other.columns), references(other.references), on_update(*this, true, other.on_update._action),
|
||||||
|
on_delete(*this, false, other.on_delete._action) {}
|
||||||
|
|
||||||
|
self& operator=(const self& other) {
|
||||||
|
this->columns = other.columns;
|
||||||
|
this->references = other.references;
|
||||||
|
this->on_update = {*this, true, other.on_update._action};
|
||||||
|
this->on_delete = {*this, false, other.on_delete._action};
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class A, class B>
|
||||||
|
bool operator==(const foreign_key_t<A, B>& lhs, const foreign_key_t<A, B>& rhs) {
|
||||||
|
return lhs.columns == rhs.columns && lhs.references == rhs.references && lhs.on_update == rhs.on_update &&
|
||||||
|
lhs.on_delete == rhs.on_delete;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cs can be a class member pointer, a getter function member pointer or setter
|
||||||
|
* func member pointer
|
||||||
|
* Available in SQLite 3.6.19 or higher
|
||||||
|
*/
|
||||||
|
template<class... Cs>
|
||||||
|
struct foreign_key_intermediate_t {
|
||||||
|
using tuple_type = std::tuple<Cs...>;
|
||||||
|
|
||||||
|
tuple_type columns;
|
||||||
|
|
||||||
|
template<class... Rs>
|
||||||
|
foreign_key_t<std::tuple<Cs...>, std::tuple<Rs...>> references(Rs... refs) {
|
||||||
|
return {std::move(this->columns), std::make_tuple(std::forward<Rs>(refs)...)};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct collate_constraint_t {
|
||||||
|
collate_argument argument = collate_argument::binary;
|
||||||
|
#ifndef SQLITE_ORM_AGGREGATE_NSDMI_SUPPORTED
|
||||||
|
collate_constraint_t(collate_argument argument) : argument{argument} {}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
operator std::string() const {
|
||||||
|
return "COLLATE " + this->string_from_collate_argument(this->argument);
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string string_from_collate_argument(collate_argument argument) {
|
||||||
|
switch(argument) {
|
||||||
|
case collate_argument::binary:
|
||||||
|
return "BINARY";
|
||||||
|
case collate_argument::nocase:
|
||||||
|
return "NOCASE";
|
||||||
|
case collate_argument::rtrim:
|
||||||
|
return "RTRIM";
|
||||||
|
}
|
||||||
|
throw std::system_error{orm_error_code::invalid_collate_argument_enum};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct check_t {
|
||||||
|
using expression_type = T;
|
||||||
|
|
||||||
|
expression_type expression;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct basic_generated_always {
|
||||||
|
enum class storage_type {
|
||||||
|
not_specified,
|
||||||
|
virtual_,
|
||||||
|
stored,
|
||||||
|
};
|
||||||
|
|
||||||
|
bool full = true;
|
||||||
|
storage_type storage = storage_type::not_specified;
|
||||||
|
|
||||||
|
#ifndef SQLITE_ORM_AGGREGATE_NSDMI_SUPPORTED
|
||||||
|
basic_generated_always(bool full, storage_type storage) : full{full}, storage{storage} {}
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct generated_always_t : basic_generated_always {
|
||||||
|
using expression_type = T;
|
||||||
|
|
||||||
|
expression_type expression;
|
||||||
|
|
||||||
|
generated_always_t(expression_type expression_, bool full, storage_type storage) :
|
||||||
|
basic_generated_always{full, storage}, expression(std::move(expression_)) {}
|
||||||
|
|
||||||
|
generated_always_t<T> virtual_() {
|
||||||
|
return {std::move(this->expression), this->full, storage_type::virtual_};
|
||||||
|
}
|
||||||
|
|
||||||
|
generated_always_t<T> stored() {
|
||||||
|
return {std::move(this->expression), this->full, storage_type::stored};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
SQLITE_ORM_INLINE_VAR constexpr bool is_foreign_key_v = polyfill::is_specialization_of_v<T, foreign_key_t>;
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
using is_foreign_key = polyfill::bool_constant<is_foreign_key_v<T>>;
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct is_primary_key : std::false_type {};
|
||||||
|
|
||||||
|
template<class... Cs>
|
||||||
|
struct is_primary_key<primary_key_t<Cs...>> : std::true_type {};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct is_primary_key<primary_key_with_autoincrement<T>> : std::true_type {};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
SQLITE_ORM_INLINE_VAR constexpr bool is_primary_key_v = is_primary_key<T>::value;
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
using is_generated_always = polyfill::is_specialization_of<T, generated_always_t>;
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
SQLITE_ORM_INLINE_VAR constexpr bool is_generated_always_v = is_generated_always<T>::value;
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
using is_autoincrement = std::is_same<T, autoincrement_t>;
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
SQLITE_ORM_INLINE_VAR constexpr bool is_autoincrement_v = is_autoincrement<T>::value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PRIMARY KEY INSERTABLE traits.
|
||||||
|
*/
|
||||||
|
template<typename T>
|
||||||
|
struct is_primary_key_insertable
|
||||||
|
: polyfill::disjunction<
|
||||||
|
mpl::instantiate<mpl::disjunction<check_if_tuple_has<is_autoincrement>,
|
||||||
|
check_if_tuple_has_template<default_t>,
|
||||||
|
check_if_tuple_has_template<primary_key_with_autoincrement>>,
|
||||||
|
constraints_type_t<T>>,
|
||||||
|
std::is_base_of<integer_printer, type_printer<field_type_t<T>>>> {
|
||||||
|
|
||||||
|
static_assert(tuple_has<is_primary_key, constraints_type_t<T>>::value, "an unexpected type was passed");
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
using is_constraint =
|
||||||
|
mpl::instantiate<mpl::disjunction<check_if<is_autoincrement>,
|
||||||
|
check_if<is_primary_key>,
|
||||||
|
check_if<is_foreign_key>,
|
||||||
|
check_if_is_template<unique_t>,
|
||||||
|
check_if_is_template<default_t>,
|
||||||
|
check_if_is_template<check_t>,
|
||||||
|
check_if_is_template<primary_key_with_autoincrement>,
|
||||||
|
check_if_is_type<collate_constraint_t>,
|
||||||
|
#if SQLITE_VERSION_NUMBER >= 3031000
|
||||||
|
check_if<is_generated_always>,
|
||||||
|
#endif
|
||||||
|
// dummy tail because of SQLITE_VERSION_NUMBER checks above
|
||||||
|
mpl::always<std::false_type>>,
|
||||||
|
T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if SQLITE_VERSION_NUMBER >= 3031000
|
||||||
|
template<class T>
|
||||||
|
internal::generated_always_t<T> generated_always_as(T expression) {
|
||||||
|
return {std::move(expression), true, internal::basic_generated_always::storage_type::not_specified};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
internal::generated_always_t<T> as(T expression) {
|
||||||
|
return {std::move(expression), false, internal::basic_generated_always::storage_type::not_specified};
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#if SQLITE_VERSION_NUMBER >= 3006019
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FOREIGN KEY constraint construction function that takes member pointer as argument
|
||||||
|
* Available in SQLite 3.6.19 or higher
|
||||||
|
*/
|
||||||
|
template<class... Cs>
|
||||||
|
internal::foreign_key_intermediate_t<Cs...> foreign_key(Cs... columns) {
|
||||||
|
return {std::make_tuple(std::forward<Cs>(columns)...)};
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UNIQUE constraint builder function.
|
||||||
|
*/
|
||||||
|
template<class... Args>
|
||||||
|
internal::unique_t<Args...> unique(Args... args) {
|
||||||
|
return {std::make_tuple(std::forward<Args>(args)...)};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline internal::unique_t<> unique() {
|
||||||
|
return {{}};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AUTOINCREMENT keyword. [Deprecation notice] Use `primary_key().autoincrement()` instead of using this function.
|
||||||
|
* This function will be removed in 1.9
|
||||||
|
*/
|
||||||
|
[[deprecated("Use primary_key().autoincrement()` instead")]] inline internal::autoincrement_t autoincrement() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class... Cs>
|
||||||
|
internal::primary_key_t<Cs...> primary_key(Cs... cs) {
|
||||||
|
return {std::make_tuple(std::forward<Cs>(cs)...)};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline internal::primary_key_t<> primary_key() {
|
||||||
|
return {{}};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
internal::default_t<T> default_value(T t) {
|
||||||
|
return {std::move(t)};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline internal::collate_constraint_t collate_nocase() {
|
||||||
|
return {internal::collate_argument::nocase};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline internal::collate_constraint_t collate_binary() {
|
||||||
|
return {internal::collate_argument::binary};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline internal::collate_constraint_t collate_rtrim() {
|
||||||
|
return {internal::collate_argument::rtrim};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
internal::check_t<T> check(T t) {
|
||||||
|
return {std::move(t)};
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,37 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string> // std::string
|
||||||
|
|
||||||
|
#include "column.h"
|
||||||
|
#include "table.h"
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
#ifdef SQLITE_ENABLE_DBSTAT_VTAB
|
||||||
|
struct dbstat {
|
||||||
|
std::string name;
|
||||||
|
std::string path;
|
||||||
|
int pageno = 0;
|
||||||
|
std::string pagetype;
|
||||||
|
int ncell = 0;
|
||||||
|
int payload = 0;
|
||||||
|
int unused = 0;
|
||||||
|
int mx_payload = 0;
|
||||||
|
int pgoffset = 0;
|
||||||
|
int pgsize = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline auto make_dbstat_table() {
|
||||||
|
return make_table("dbstat",
|
||||||
|
make_column("name", &dbstat::name),
|
||||||
|
make_column("path", &dbstat::path),
|
||||||
|
make_column("pageno", &dbstat::pageno),
|
||||||
|
make_column("pagetype", &dbstat::pagetype),
|
||||||
|
make_column("ncell", &dbstat::ncell),
|
||||||
|
make_column("payload", &dbstat::payload),
|
||||||
|
make_column("unused", &dbstat::unused),
|
||||||
|
make_column("mx_payload", &dbstat::mx_payload),
|
||||||
|
make_column("pgoffset", &dbstat::pgoffset),
|
||||||
|
make_column("pgsize", &dbstat::pgsize));
|
||||||
|
}
|
||||||
|
#endif // SQLITE_ENABLE_DBSTAT_VTAB
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string> // std::string
|
||||||
|
|
||||||
|
#include "constraints.h"
|
||||||
|
#include "serializer_context.h"
|
||||||
|
#include "storage_lookup.h"
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
template<class T, class DBOs>
|
||||||
|
std::string serialize(const T&, const serializer_context<DBOs>&);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serialize default value of a column's default valu
|
||||||
|
*/
|
||||||
|
template<class T>
|
||||||
|
std::string serialize_default_value(const default_t<T>& dft) {
|
||||||
|
db_objects_tuple<> dbObjects;
|
||||||
|
serializer_context<db_objects_tuple<>> context{dbObjects};
|
||||||
|
return serialize(dft.value, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,169 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <sqlite3.h>
|
||||||
|
#include <system_error> // std::error_code, std::system_error
|
||||||
|
#include <string> // std::string
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <sstream> // std::ostringstream
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
|
||||||
|
/** @short Enables classifying sqlite error codes.
|
||||||
|
|
||||||
|
@note We don't bother listing all possible values;
|
||||||
|
this also allows for compatibility with
|
||||||
|
'Construction rules for enum class values (P0138R2)'
|
||||||
|
*/
|
||||||
|
enum class sqlite_errc {};
|
||||||
|
|
||||||
|
enum class orm_error_code {
|
||||||
|
not_found = 1,
|
||||||
|
type_is_not_mapped_to_storage,
|
||||||
|
trying_to_dereference_null_iterator,
|
||||||
|
too_many_tables_specified,
|
||||||
|
incorrect_set_fields_specified,
|
||||||
|
column_not_found,
|
||||||
|
table_has_no_primary_key_column,
|
||||||
|
cannot_start_a_transaction_within_a_transaction,
|
||||||
|
no_active_transaction,
|
||||||
|
incorrect_journal_mode_string,
|
||||||
|
invalid_collate_argument_enum,
|
||||||
|
failed_to_init_a_backup,
|
||||||
|
unknown_member_value,
|
||||||
|
incorrect_order,
|
||||||
|
cannot_use_default_value,
|
||||||
|
arguments_count_does_not_match,
|
||||||
|
function_not_found,
|
||||||
|
index_is_out_of_bounds,
|
||||||
|
value_is_null,
|
||||||
|
no_tables_specified,
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace std {
|
||||||
|
template<>
|
||||||
|
struct is_error_code_enum<::sqlite_orm::sqlite_errc> : true_type {};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct is_error_code_enum<::sqlite_orm::orm_error_code> : true_type {};
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
|
||||||
|
class orm_error_category : public std::error_category {
|
||||||
|
public:
|
||||||
|
const char* name() const noexcept override final {
|
||||||
|
return "ORM error";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string message(int c) const override final {
|
||||||
|
switch(static_cast<orm_error_code>(c)) {
|
||||||
|
case orm_error_code::not_found:
|
||||||
|
return "Not found";
|
||||||
|
case orm_error_code::type_is_not_mapped_to_storage:
|
||||||
|
return "Type is not mapped to storage";
|
||||||
|
case orm_error_code::trying_to_dereference_null_iterator:
|
||||||
|
return "Trying to dereference null iterator";
|
||||||
|
case orm_error_code::too_many_tables_specified:
|
||||||
|
return "Too many tables specified";
|
||||||
|
case orm_error_code::incorrect_set_fields_specified:
|
||||||
|
return "Incorrect set fields specified";
|
||||||
|
case orm_error_code::column_not_found:
|
||||||
|
return "Column not found";
|
||||||
|
case orm_error_code::table_has_no_primary_key_column:
|
||||||
|
return "Table has no primary key column";
|
||||||
|
case orm_error_code::cannot_start_a_transaction_within_a_transaction:
|
||||||
|
return "Cannot start a transaction within a transaction";
|
||||||
|
case orm_error_code::no_active_transaction:
|
||||||
|
return "No active transaction";
|
||||||
|
case orm_error_code::invalid_collate_argument_enum:
|
||||||
|
return "Invalid collate_argument enum";
|
||||||
|
case orm_error_code::failed_to_init_a_backup:
|
||||||
|
return "Failed to init a backup";
|
||||||
|
case orm_error_code::unknown_member_value:
|
||||||
|
return "Unknown member value";
|
||||||
|
case orm_error_code::incorrect_order:
|
||||||
|
return "Incorrect order";
|
||||||
|
case orm_error_code::cannot_use_default_value:
|
||||||
|
return "The statement 'INSERT INTO * DEFAULT VALUES' can be used with only one row";
|
||||||
|
case orm_error_code::arguments_count_does_not_match:
|
||||||
|
return "Arguments count does not match";
|
||||||
|
case orm_error_code::function_not_found:
|
||||||
|
return "Function not found";
|
||||||
|
case orm_error_code::index_is_out_of_bounds:
|
||||||
|
return "Index is out of bounds";
|
||||||
|
case orm_error_code::value_is_null:
|
||||||
|
return "Value is null";
|
||||||
|
case orm_error_code::no_tables_specified:
|
||||||
|
return "No tables specified";
|
||||||
|
default:
|
||||||
|
return "unknown error";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class sqlite_error_category : public std::error_category {
|
||||||
|
public:
|
||||||
|
const char* name() const noexcept override final {
|
||||||
|
return "SQLite error";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string message(int c) const override final {
|
||||||
|
return sqlite3_errstr(c);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
inline const orm_error_category& get_orm_error_category() {
|
||||||
|
static orm_error_category res;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const sqlite_error_category& get_sqlite_error_category() {
|
||||||
|
static sqlite_error_category res;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::error_code make_error_code(sqlite_errc ev) noexcept {
|
||||||
|
return {static_cast<int>(ev), get_sqlite_error_category()};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::error_code make_error_code(orm_error_code ev) noexcept {
|
||||||
|
return {static_cast<int>(ev), get_orm_error_category()};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename... T>
|
||||||
|
std::string get_error_message(sqlite3* db, T&&... args) {
|
||||||
|
std::ostringstream stream;
|
||||||
|
using unpack = int[];
|
||||||
|
static_cast<void>(unpack{0, (static_cast<void>(static_cast<void>(stream << args)), 0)...});
|
||||||
|
stream << sqlite3_errmsg(db);
|
||||||
|
return stream.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename... T>
|
||||||
|
[[noreturn]] void throw_error(sqlite3* db, T&&... args) {
|
||||||
|
throw std::system_error{sqlite_errc(sqlite3_errcode(db)), get_error_message(db, std::forward<T>(args)...)};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::system_error sqlite_to_system_error(int ev) {
|
||||||
|
return {sqlite_errc(ev)};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::system_error sqlite_to_system_error(sqlite3* db) {
|
||||||
|
return {sqlite_errc(sqlite3_errcode(db)), sqlite3_errmsg(db)};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[noreturn]] inline void throw_translated_sqlite_error(int ev) {
|
||||||
|
throw sqlite_to_system_error(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[noreturn]] inline void throw_translated_sqlite_error(sqlite3* db) {
|
||||||
|
throw sqlite_to_system_error(db);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[noreturn]] inline void throw_translated_sqlite_error(sqlite3_stmt* stmt) {
|
||||||
|
throw sqlite_to_system_error(sqlite3_db_handle(stmt));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <tuple>
|
||||||
|
#include <utility> // std::move, std::forward
|
||||||
|
#include "functional/cxx_optional.h"
|
||||||
|
|
||||||
|
#include "functional/cxx_universal.h"
|
||||||
|
#include "operators.h"
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
template<class L, class R>
|
||||||
|
struct and_condition_t;
|
||||||
|
|
||||||
|
template<class L, class R>
|
||||||
|
struct or_condition_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is not an operator but a result of c(...) function. Has operator= overloaded which returns assign_t
|
||||||
|
*/
|
||||||
|
template<class T>
|
||||||
|
struct expression_t : condition_t {
|
||||||
|
T value;
|
||||||
|
|
||||||
|
expression_t(T value_) : value(std::move(value_)) {}
|
||||||
|
|
||||||
|
template<class R>
|
||||||
|
assign_t<T, R> operator=(R r) const {
|
||||||
|
return {this->value, std::move(r)};
|
||||||
|
}
|
||||||
|
|
||||||
|
assign_t<T, nullptr_t> operator=(nullptr_t) const {
|
||||||
|
return {this->value, nullptr};
|
||||||
|
}
|
||||||
|
#ifdef SQLITE_ORM_OPTIONAL_SUPPORTED
|
||||||
|
assign_t<T, std::nullopt_t> operator=(std::nullopt_t) const {
|
||||||
|
return {this->value, std::nullopt};
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
template<class... Args>
|
||||||
|
in_t<T, Args...> in(Args... args) const {
|
||||||
|
return {this->value, std::make_tuple(std::forward<Args>(args)...), false};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class... Args>
|
||||||
|
in_t<T, Args...> not_in(Args... args) const {
|
||||||
|
return {this->value, std::make_tuple(std::forward<Args>(args)...), true};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class R>
|
||||||
|
and_condition_t<T, R> and_(R right) const {
|
||||||
|
return {this->value, std::move(right)};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class R>
|
||||||
|
or_condition_t<T, R> or_(R right) const {
|
||||||
|
return {this->value, std::move(right)};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
T get_from_expression(T value) {
|
||||||
|
return std::move(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
T get_from_expression(expression_t<T> expression) {
|
||||||
|
return std::move(expression.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public interface for syntax sugar for columns. Example: `where(c(&User::id) == 5)` or
|
||||||
|
* `storage.update(set(c(&User::name) = "Dua Lipa"));
|
||||||
|
*/
|
||||||
|
template<class T>
|
||||||
|
internal::expression_t<T> c(T value) {
|
||||||
|
return {std::move(value)};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,137 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <type_traits> // std::decay
|
||||||
|
#include <functional> // std::reference_wrapper
|
||||||
|
|
||||||
|
#include "prepared_statement.h"
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
template<class T, class SFINAE = void>
|
||||||
|
struct expression_object_type;
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct expression_object_type<update_t<T>> : std::decay<T> {};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct expression_object_type<update_t<std::reference_wrapper<T>>> : std::decay<T> {};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct expression_object_type<replace_t<T>> : std::decay<T> {};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct expression_object_type<replace_t<std::reference_wrapper<T>>> : std::decay<T> {};
|
||||||
|
|
||||||
|
template<class It, class L, class O>
|
||||||
|
struct expression_object_type<replace_range_t<It, L, O>> {
|
||||||
|
using type = typename replace_range_t<It, L, O>::object_type;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class It, class L, class O>
|
||||||
|
struct expression_object_type<replace_range_t<std::reference_wrapper<It>, L, O>> {
|
||||||
|
using type = typename replace_range_t<std::reference_wrapper<It>, L, O>::object_type;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T, class... Ids>
|
||||||
|
struct expression_object_type<remove_t<T, Ids...>> {
|
||||||
|
using type = T;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T, class... Ids>
|
||||||
|
struct expression_object_type<remove_t<std::reference_wrapper<T>, Ids...>> {
|
||||||
|
using type = T;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct expression_object_type<insert_t<T>> : std::decay<T> {};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct expression_object_type<insert_t<std::reference_wrapper<T>>> : std::decay<T> {};
|
||||||
|
|
||||||
|
template<class It, class L, class O>
|
||||||
|
struct expression_object_type<insert_range_t<It, L, O>> {
|
||||||
|
using type = typename insert_range_t<It, L, O>::object_type;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class It, class L, class O>
|
||||||
|
struct expression_object_type<insert_range_t<std::reference_wrapper<It>, L, O>> {
|
||||||
|
using type = typename insert_range_t<std::reference_wrapper<It>, L, O>::object_type;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T, class... Cols>
|
||||||
|
struct expression_object_type<insert_explicit<T, Cols...>> : std::decay<T> {};
|
||||||
|
|
||||||
|
template<class T, class... Cols>
|
||||||
|
struct expression_object_type<insert_explicit<std::reference_wrapper<T>, Cols...>> : std::decay<T> {};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct get_ref_t {
|
||||||
|
|
||||||
|
template<class O>
|
||||||
|
auto& operator()(O& t) const {
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct get_ref_t<std::reference_wrapper<T>> {
|
||||||
|
|
||||||
|
template<class O>
|
||||||
|
auto& operator()(O& t) const {
|
||||||
|
return t.get();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
auto& get_ref(T& t) {
|
||||||
|
using arg_type = std::decay_t<T>;
|
||||||
|
get_ref_t<arg_type> g;
|
||||||
|
return g(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct get_object_t;
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct get_object_t<const T> : get_object_t<T> {};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
auto& get_object(T& t) {
|
||||||
|
using expression_type = std::decay_t<T>;
|
||||||
|
get_object_t<expression_type> obj;
|
||||||
|
return obj(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct get_object_t<replace_t<T>> {
|
||||||
|
using expression_type = replace_t<T>;
|
||||||
|
|
||||||
|
template<class O>
|
||||||
|
auto& operator()(O& e) const {
|
||||||
|
return get_ref(e.object);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct get_object_t<insert_t<T>> {
|
||||||
|
using expression_type = insert_t<T>;
|
||||||
|
|
||||||
|
template<class O>
|
||||||
|
auto& operator()(O& e) const {
|
||||||
|
return get_ref(e.object);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct get_object_t<update_t<T>> {
|
||||||
|
using expression_type = update_t<T>;
|
||||||
|
|
||||||
|
template<class O>
|
||||||
|
auto& operator()(O& e) const {
|
||||||
|
return get_ref(e.object);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,159 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <locale> // std::wstring_convert
|
||||||
|
#include <string> // std::string
|
||||||
|
#include <sstream> // std::stringstream
|
||||||
|
#include <vector> // std::vector
|
||||||
|
#include <memory> // std::shared_ptr, std::unique_ptr
|
||||||
|
#ifndef SQLITE_ORM_OMITS_CODECVT
|
||||||
|
#include <codecvt> // std::codecvt_utf8_utf16
|
||||||
|
#endif // SQLITE_ORM_OMITS_CODECVT
|
||||||
|
#include "functional/cxx_optional.h"
|
||||||
|
|
||||||
|
#include "functional/cxx_universal.h"
|
||||||
|
#include "functional/cxx_type_traits_polyfill.h"
|
||||||
|
#include "is_std_ptr.h"
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is used to print members mapped to objects in storage_t::dump member function.
|
||||||
|
* Other developers can create own specialization to map custom types
|
||||||
|
*/
|
||||||
|
template<class T, typename SFINAE = void>
|
||||||
|
struct field_printer;
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
template<class T, class SFINAE = void>
|
||||||
|
SQLITE_ORM_INLINE_VAR constexpr bool is_printable_v = false;
|
||||||
|
template<class T>
|
||||||
|
SQLITE_ORM_INLINE_VAR constexpr bool is_printable_v<T, polyfill::void_t<decltype(field_printer<T>{})>> = true
|
||||||
|
// Also see implementation note for `is_bindable_v`
|
||||||
|
;
|
||||||
|
template<class T>
|
||||||
|
using is_printable = polyfill::bool_constant<is_printable_v<T>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct field_printer<T, std::enable_if_t<std::is_arithmetic<T>::value>> {
|
||||||
|
std::string operator()(const T& t) const {
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << t;
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upgrade to integer is required when using unsigned char(uint8_t)
|
||||||
|
*/
|
||||||
|
template<>
|
||||||
|
struct field_printer<unsigned char, void> {
|
||||||
|
std::string operator()(const unsigned char& t) const {
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << +t;
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upgrade to integer is required when using signed char(int8_t)
|
||||||
|
*/
|
||||||
|
template<>
|
||||||
|
struct field_printer<signed char, void> {
|
||||||
|
std::string operator()(const signed char& t) const {
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << +t;
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* char is neither signed char nor unsigned char so it has its own specialization
|
||||||
|
*/
|
||||||
|
template<>
|
||||||
|
struct field_printer<char, void> {
|
||||||
|
std::string operator()(const char& t) const {
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << +t;
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct field_printer<T, std::enable_if_t<std::is_base_of<std::string, T>::value>> {
|
||||||
|
std::string operator()(std::string string) const {
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct field_printer<std::vector<char>, void> {
|
||||||
|
std::string operator()(const std::vector<char>& t) const {
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << std::hex;
|
||||||
|
for(auto c: t) {
|
||||||
|
ss << c;
|
||||||
|
}
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#ifndef SQLITE_ORM_OMITS_CODECVT
|
||||||
|
/**
|
||||||
|
* Specialization for std::wstring (UTF-16 assumed).
|
||||||
|
*/
|
||||||
|
template<class T>
|
||||||
|
struct field_printer<T, std::enable_if_t<std::is_base_of<std::wstring, T>::value>> {
|
||||||
|
std::string operator()(const std::wstring& wideString) const {
|
||||||
|
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
|
||||||
|
return converter.to_bytes(wideString);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#endif // SQLITE_ORM_OMITS_CODECVT
|
||||||
|
template<>
|
||||||
|
struct field_printer<nullptr_t, void> {
|
||||||
|
std::string operator()(const nullptr_t&) const {
|
||||||
|
return "null";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#ifdef SQLITE_ORM_OPTIONAL_SUPPORTED
|
||||||
|
template<>
|
||||||
|
struct field_printer<std::nullopt_t, void> {
|
||||||
|
std::string operator()(const std::nullopt_t&) const {
|
||||||
|
return "null";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#endif // SQLITE_ORM_OPTIONAL_SUPPORTED
|
||||||
|
template<class T>
|
||||||
|
struct field_printer<
|
||||||
|
T,
|
||||||
|
std::enable_if_t<polyfill::conjunction_v<is_std_ptr<T>,
|
||||||
|
internal::is_printable<std::remove_cv_t<typename T::element_type>>>>> {
|
||||||
|
using unqualified_type = std::remove_cv_t<typename T::element_type>;
|
||||||
|
|
||||||
|
std::string operator()(const T& t) const {
|
||||||
|
if(t) {
|
||||||
|
return field_printer<unqualified_type>()(*t);
|
||||||
|
} else {
|
||||||
|
return field_printer<nullptr_t>{}(nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef SQLITE_ORM_OPTIONAL_SUPPORTED
|
||||||
|
template<class T>
|
||||||
|
struct field_printer<
|
||||||
|
T,
|
||||||
|
std::enable_if_t<polyfill::conjunction_v<polyfill::is_specialization_of<T, std::optional>,
|
||||||
|
internal::is_printable<std::remove_cv_t<typename T::value_type>>>>> {
|
||||||
|
using unqualified_type = std::remove_cv_t<typename T::value_type>;
|
||||||
|
|
||||||
|
std::string operator()(const T& t) const {
|
||||||
|
if(t.has_value()) {
|
||||||
|
return field_printer<unqualified_type>()(*t);
|
||||||
|
} else {
|
||||||
|
return field_printer<std::nullopt_t>{}(std::nullopt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#endif // SQLITE_ORM_OPTIONAL_SUPPORTED
|
||||||
|
}
|
|
@ -0,0 +1,246 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <sqlite3.h>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <string> // std::string
|
||||||
|
#include <tuple> // std::tuple
|
||||||
|
#include <functional> // std::function
|
||||||
|
#include <algorithm> // std::min
|
||||||
|
#include <utility> // std::move, std::forward
|
||||||
|
|
||||||
|
#include "functional/cxx_universal.h"
|
||||||
|
#include "functional/cxx_type_traits_polyfill.h"
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
|
||||||
|
struct arg_values;
|
||||||
|
|
||||||
|
template<class T, class P>
|
||||||
|
struct pointer_arg;
|
||||||
|
template<class T, class P, class D>
|
||||||
|
class pointer_binding;
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
struct user_defined_function_base {
|
||||||
|
using func_call = std::function<
|
||||||
|
void(sqlite3_context* context, void* functionPointer, int argsCount, sqlite3_value** values)>;
|
||||||
|
using final_call = std::function<void(sqlite3_context* context, void* functionPointer)>;
|
||||||
|
|
||||||
|
std::string name;
|
||||||
|
int argumentsCount = 0;
|
||||||
|
std::function<int*()> create;
|
||||||
|
void (*destroy)(int*) = nullptr;
|
||||||
|
|
||||||
|
#ifndef SQLITE_ORM_AGGREGATE_NSDMI_SUPPORTED
|
||||||
|
user_defined_function_base(decltype(name) name_,
|
||||||
|
decltype(argumentsCount) argumentsCount_,
|
||||||
|
decltype(create) create_,
|
||||||
|
decltype(destroy) destroy_) :
|
||||||
|
name(std::move(name_)),
|
||||||
|
argumentsCount(argumentsCount_), create(std::move(create_)), destroy(destroy_) {}
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
struct user_defined_scalar_function_t : user_defined_function_base {
|
||||||
|
func_call run;
|
||||||
|
|
||||||
|
user_defined_scalar_function_t(decltype(name) name_,
|
||||||
|
int argumentsCount_,
|
||||||
|
decltype(create) create_,
|
||||||
|
decltype(run) run_,
|
||||||
|
decltype(destroy) destroy_) :
|
||||||
|
user_defined_function_base{std::move(name_), argumentsCount_, std::move(create_), destroy_},
|
||||||
|
run(std::move(run_)) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct user_defined_aggregate_function_t : user_defined_function_base {
|
||||||
|
func_call step;
|
||||||
|
final_call finalCall;
|
||||||
|
|
||||||
|
user_defined_aggregate_function_t(decltype(name) name_,
|
||||||
|
int argumentsCount_,
|
||||||
|
decltype(create) create_,
|
||||||
|
decltype(step) step_,
|
||||||
|
decltype(finalCall) finalCall_,
|
||||||
|
decltype(destroy) destroy_) :
|
||||||
|
user_defined_function_base{std::move(name_), argumentsCount_, std::move(create_), destroy_},
|
||||||
|
step(std::move(step_)), finalCall(std::move(finalCall_)) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class F>
|
||||||
|
using scalar_call_function_t = decltype(&F::operator());
|
||||||
|
|
||||||
|
template<class F>
|
||||||
|
using aggregate_step_function_t = decltype(&F::step);
|
||||||
|
|
||||||
|
template<class F>
|
||||||
|
using aggregate_fin_function_t = decltype(&F::fin);
|
||||||
|
|
||||||
|
template<class F, class = void>
|
||||||
|
SQLITE_ORM_INLINE_VAR constexpr bool is_scalar_function_v = false;
|
||||||
|
template<class F>
|
||||||
|
SQLITE_ORM_INLINE_VAR constexpr bool is_scalar_function_v<F, polyfill::void_t<scalar_call_function_t<F>>> =
|
||||||
|
true;
|
||||||
|
|
||||||
|
template<class F, class = void>
|
||||||
|
SQLITE_ORM_INLINE_VAR constexpr bool is_aggregate_function_v = false;
|
||||||
|
template<class F>
|
||||||
|
SQLITE_ORM_INLINE_VAR constexpr bool is_aggregate_function_v<
|
||||||
|
F,
|
||||||
|
polyfill::void_t<aggregate_step_function_t<F>,
|
||||||
|
aggregate_fin_function_t<F>,
|
||||||
|
std::enable_if_t<std::is_member_function_pointer<aggregate_step_function_t<F>>::value>,
|
||||||
|
std::enable_if_t<std::is_member_function_pointer<aggregate_fin_function_t<F>>::value>>> =
|
||||||
|
true;
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct member_function_arguments;
|
||||||
|
|
||||||
|
template<class O, class R, class... Args>
|
||||||
|
struct member_function_arguments<R (O::*)(Args...) const> {
|
||||||
|
using member_function_type = R (O::*)(Args...) const;
|
||||||
|
using tuple_type = std::tuple<std::decay_t<Args>...>;
|
||||||
|
using return_type = R;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class O, class R, class... Args>
|
||||||
|
struct member_function_arguments<R (O::*)(Args...)> {
|
||||||
|
using member_function_type = R (O::*)(Args...);
|
||||||
|
using tuple_type = std::tuple<std::decay_t<Args>...>;
|
||||||
|
using return_type = R;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class F, class SFINAE = void>
|
||||||
|
struct callable_arguments_impl;
|
||||||
|
|
||||||
|
template<class F>
|
||||||
|
struct callable_arguments_impl<F, std::enable_if_t<is_scalar_function_v<F>>> {
|
||||||
|
using args_tuple = typename member_function_arguments<scalar_call_function_t<F>>::tuple_type;
|
||||||
|
using return_type = typename member_function_arguments<scalar_call_function_t<F>>::return_type;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class F>
|
||||||
|
struct callable_arguments_impl<F, std::enable_if_t<is_aggregate_function_v<F>>> {
|
||||||
|
using args_tuple = typename member_function_arguments<aggregate_step_function_t<F>>::tuple_type;
|
||||||
|
using return_type = typename member_function_arguments<aggregate_fin_function_t<F>>::return_type;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class F>
|
||||||
|
struct callable_arguments : callable_arguments_impl<F> {};
|
||||||
|
|
||||||
|
template<class F, class... Args>
|
||||||
|
struct function_call {
|
||||||
|
using function_type = F;
|
||||||
|
using args_tuple = std::tuple<Args...>;
|
||||||
|
|
||||||
|
args_tuple args;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct unpacked_arg {
|
||||||
|
using type = T;
|
||||||
|
};
|
||||||
|
template<class F, class... Args>
|
||||||
|
struct unpacked_arg<function_call<F, Args...>> {
|
||||||
|
using type = typename callable_arguments<F>::return_type;
|
||||||
|
};
|
||||||
|
template<class T>
|
||||||
|
using unpacked_arg_t = typename unpacked_arg<T>::type;
|
||||||
|
|
||||||
|
template<size_t I, class FnArg, class CallArg>
|
||||||
|
SQLITE_ORM_CONSTEVAL bool expected_pointer_value() {
|
||||||
|
static_assert(polyfill::always_false_v<FnArg, CallArg>, "Expected a pointer value for I-th argument");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<size_t I, class FnArg, class CallArg, class EnableIfTag = void>
|
||||||
|
constexpr bool is_same_pvt_v = expected_pointer_value<I, FnArg, CallArg>();
|
||||||
|
|
||||||
|
// Always allow binding nullptr to a pointer argument
|
||||||
|
template<size_t I, class PointerArg>
|
||||||
|
constexpr bool is_same_pvt_v<I, PointerArg, nullptr_t, polyfill::void_t<typename PointerArg::tag>> = true;
|
||||||
|
|
||||||
|
#if __cplusplus >= 201703L // C++17 or later
|
||||||
|
template<size_t I, const char* PointerArg, const char* Binding>
|
||||||
|
SQLITE_ORM_CONSTEVAL bool assert_same_pointer_type() {
|
||||||
|
constexpr bool valid = Binding == PointerArg;
|
||||||
|
static_assert(valid, "Pointer value types of I-th argument do not match");
|
||||||
|
return valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<size_t I, class PointerArg, class Binding>
|
||||||
|
constexpr bool
|
||||||
|
is_same_pvt_v<I, PointerArg, Binding, polyfill::void_t<typename PointerArg::tag, typename Binding::tag>> =
|
||||||
|
assert_same_pointer_type<I, PointerArg::tag::value, Binding::tag::value>();
|
||||||
|
#else
|
||||||
|
template<size_t I, class PointerArg, class Binding>
|
||||||
|
SQLITE_ORM_CONSTEVAL bool assert_same_pointer_type() {
|
||||||
|
constexpr bool valid = Binding::value == PointerArg::value;
|
||||||
|
static_assert(valid, "Pointer value types of I-th argument do not match");
|
||||||
|
return valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<size_t I, class PointerArg, class Binding>
|
||||||
|
constexpr bool
|
||||||
|
is_same_pvt_v<I, PointerArg, Binding, polyfill::void_t<typename PointerArg::tag, typename Binding::tag>> =
|
||||||
|
assert_same_pointer_type<I, typename PointerArg::tag, typename Binding::tag>();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
template<size_t I, class FnArg, class CallArg>
|
||||||
|
SQLITE_ORM_CONSTEVAL bool validate_pointer_value_type(std::false_type) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<size_t I, class FnArg, class CallArg>
|
||||||
|
SQLITE_ORM_CONSTEVAL bool validate_pointer_value_type(std::true_type) {
|
||||||
|
return is_same_pvt_v<I, FnArg, CallArg>;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class FnArgs, class CallArgs>
|
||||||
|
SQLITE_ORM_CONSTEVAL bool validate_pointer_value_types(polyfill::index_constant<size_t(-1)>) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
template<class FnArgs, class CallArgs, size_t I>
|
||||||
|
SQLITE_ORM_CONSTEVAL bool validate_pointer_value_types(polyfill::index_constant<I>) {
|
||||||
|
using func_arg_t = std::tuple_element_t<I, FnArgs>;
|
||||||
|
using passed_arg_t = unpacked_arg_t<std::tuple_element_t<I, CallArgs>>;
|
||||||
|
|
||||||
|
#ifdef SQLITE_ORM_RELAXED_CONSTEXPR_SUPPORTED
|
||||||
|
constexpr bool valid = validate_pointer_value_type<I,
|
||||||
|
std::tuple_element_t<I, FnArgs>,
|
||||||
|
unpacked_arg_t<std::tuple_element_t<I, CallArgs>>>(
|
||||||
|
polyfill::bool_constant < (polyfill::is_specialization_of_v<func_arg_t, pointer_arg>) ||
|
||||||
|
(polyfill::is_specialization_of_v<passed_arg_t, pointer_binding>) > {});
|
||||||
|
|
||||||
|
return validate_pointer_value_types<FnArgs, CallArgs>(polyfill::index_constant<I - 1>{}) && valid;
|
||||||
|
#else
|
||||||
|
return validate_pointer_value_types<FnArgs, CallArgs>(polyfill::index_constant<I - 1>{}) &&
|
||||||
|
validate_pointer_value_type<I,
|
||||||
|
std::tuple_element_t<I, FnArgs>,
|
||||||
|
unpacked_arg_t<std::tuple_element_t<I, CallArgs>>>(
|
||||||
|
polyfill::bool_constant < (polyfill::is_specialization_of_v<func_arg_t, pointer_arg>) ||
|
||||||
|
(polyfill::is_specialization_of_v<passed_arg_t, pointer_binding>) > {});
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to call user defined function: `func<MyFunc>(...);`
|
||||||
|
*/
|
||||||
|
template<class F, class... Args>
|
||||||
|
internal::function_call<F, Args...> func(Args... args) {
|
||||||
|
using args_tuple = std::tuple<Args...>;
|
||||||
|
using function_args_tuple = typename internal::callable_arguments<F>::args_tuple;
|
||||||
|
constexpr auto argsCount = std::tuple_size<args_tuple>::value;
|
||||||
|
constexpr auto functionArgsCount = std::tuple_size<function_args_tuple>::value;
|
||||||
|
static_assert((argsCount == functionArgsCount &&
|
||||||
|
!std::is_same<function_args_tuple, std::tuple<arg_values>>::value &&
|
||||||
|
internal::validate_pointer_value_types<function_args_tuple, args_tuple>(
|
||||||
|
polyfill::index_constant<std::min<>(functionArgsCount, argsCount) - 1>{})) ||
|
||||||
|
std::is_same<function_args_tuple, std::tuple<arg_values>>::value,
|
||||||
|
"Number of arguments does not match");
|
||||||
|
return {std::make_tuple(std::forward<Args>(args)...)};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "cxx_universal.h"
|
||||||
|
|
||||||
|
#ifdef SQLITE_ORM_INLINE_VARIABLES_SUPPORTED
|
||||||
|
#define SQLITE_ORM_INLINE_VAR inline
|
||||||
|
#else
|
||||||
|
#define SQLITE_ORM_INLINE_VAR
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if SQLITE_ORM_HAS_CPP_ATTRIBUTE(no_unique_address) >= 201803L
|
||||||
|
#define SQLITE_ORM_NOUNIQUEADDRESS [[no_unique_address]]
|
||||||
|
#else
|
||||||
|
#define SQLITE_ORM_NOUNIQUEADDRESS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef SQLITE_ORM_CONSTEVAL_SUPPORTED
|
||||||
|
#define SQLITE_ORM_CONSTEVAL consteval
|
||||||
|
#else
|
||||||
|
#define SQLITE_ORM_CONSTEVAL constexpr
|
||||||
|
#endif
|
|
@ -0,0 +1,42 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This header defines macros for circumventing compiler quirks on which sqlite_orm depends.
|
||||||
|
* May amend cxx_core_features.h
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef __clang__
|
||||||
|
#define SQLITE_ORM_DO_PRAGMA(...) _Pragma(#__VA_ARGS__)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __clang__
|
||||||
|
#define SQLITE_ORM_CLANG_SUPPRESS(warnoption, ...) \
|
||||||
|
SQLITE_ORM_DO_PRAGMA(clang diagnostic push) \
|
||||||
|
SQLITE_ORM_DO_PRAGMA(clang diagnostic ignored warnoption) \
|
||||||
|
__VA_ARGS__ \
|
||||||
|
SQLITE_ORM_DO_PRAGMA(clang diagnostic pop)
|
||||||
|
|
||||||
|
#else
|
||||||
|
#define SQLITE_ORM_CLANG_SUPPRESS(warnoption, ...) __VA_ARGS__
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// clang has the bad habit of diagnosing missing brace-init-lists when constructing aggregates with base classes.
|
||||||
|
// This is a false positive, since the C++ standard is quite clear that braces for nested or base objects may be omitted,
|
||||||
|
// see https://en.cppreference.com/w/cpp/language/aggregate_initialization:
|
||||||
|
// "The braces around the nested initializer lists may be elided (omitted),
|
||||||
|
// in which case as many initializer clauses as necessary are used to initialize every member or element of the corresponding subaggregate,
|
||||||
|
// and the subsequent initializer clauses are used to initialize the following members of the object."
|
||||||
|
// In this sense clang should only warn about missing field initializers.
|
||||||
|
// Because we know what we are doing, we suppress the diagnostic message
|
||||||
|
#define SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(...) SQLITE_ORM_CLANG_SUPPRESS("-Wmissing-braces", __VA_ARGS__)
|
||||||
|
|
||||||
|
#if defined(_MSC_VER) && (_MSC_VER < 1920)
|
||||||
|
#define SQLITE_ORM_BROKEN_VARIADIC_PACK_EXPANSION
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// clang 10 chokes on concepts that don't depend on template parameters;
|
||||||
|
// when it tries to instantiate an expression in a requires expression, which results in an error,
|
||||||
|
// the compiler reports an error instead of dismissing the templated function.
|
||||||
|
#if defined(SQLITE_ORM_CONCEPTS_SUPPORTED) && (defined(__clang__) && (__clang_major__ == 10))
|
||||||
|
#define SQLITE_ORM_BROKEN_NONTEMPLATE_CONCEPTS
|
||||||
|
#endif
|
|
@ -0,0 +1,67 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This header detects core C++ language features on which sqlite_orm depends.
|
||||||
|
* May be updated/overwritten by cxx_compiler_quirks.h
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef __has_cpp_attribute
|
||||||
|
#define SQLITE_ORM_HAS_CPP_ATTRIBUTE(attr) __has_cpp_attribute(attr)
|
||||||
|
#else
|
||||||
|
#define SQLITE_ORM_HAS_CPP_ATTRIBUTE(attr) 0L
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __has_include
|
||||||
|
#define SQLITE_ORM_HAS_INCLUDE(file) __has_include(file)
|
||||||
|
#else
|
||||||
|
#define SQLITE_ORM_HAS_INCLUDE(file) 0L
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if __cpp_aggregate_nsdmi >= 201304L
|
||||||
|
#define SQLITE_ORM_AGGREGATE_NSDMI_SUPPORTED
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if __cpp_constexpr >= 201304L
|
||||||
|
#define SQLITE_ORM_RELAXED_CONSTEXPR_SUPPORTED
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if __cpp_noexcept_function_type >= 201510L
|
||||||
|
#define SQLITE_ORM_NOTHROW_ALIASES_SUPPORTED
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if __cpp_aggregate_bases >= 201603L
|
||||||
|
#define SQLITE_ORM_AGGREGATE_BASES_SUPPORTED
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if __cpp_fold_expressions >= 201603L
|
||||||
|
#define SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if __cpp_inline_variables >= 201606L
|
||||||
|
#define SQLITE_ORM_INLINE_VARIABLES_SUPPORTED
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if __cpp_if_constexpr >= 201606L
|
||||||
|
#define SQLITE_ORM_IF_CONSTEXPR_SUPPORTED
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if __cpp_inline_variables >= 201606L
|
||||||
|
#define SQLITE_ORM_INLINE_VARIABLES_SUPPORTED
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if __cpp_generic_lambdas >= 201707L
|
||||||
|
#define SQLITE_ORM_EXPLICIT_GENERIC_LAMBDA_SUPPORTED
|
||||||
|
#else
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if __cpp_consteval >= 201811L
|
||||||
|
#define SQLITE_ORM_CONSTEVAL_SUPPORTED
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if __cpp_aggregate_paren_init >= 201902L
|
||||||
|
#define SQLITE_ORM_AGGREGATE_PAREN_INIT_SUPPORTED
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if __cpp_concepts >= 201907L
|
||||||
|
#define SQLITE_ORM_CONCEPTS_SUPPORTED
|
||||||
|
#endif
|
|
@ -0,0 +1,82 @@
|
||||||
|
#pragma once
|
||||||
|
#include <functional>
|
||||||
|
#if __cpp_lib_invoke < 201411L
|
||||||
|
#include <type_traits> // std::enable_if, std::is_member_object_pointer, std::is_member_function_pointer
|
||||||
|
#endif
|
||||||
|
#include <utility> // std::forward
|
||||||
|
|
||||||
|
#if __cpp_lib_invoke < 201411L
|
||||||
|
#include "cxx_type_traits_polyfill.h"
|
||||||
|
#endif
|
||||||
|
#include "../member_traits/member_traits.h"
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
namespace internal {
|
||||||
|
namespace polyfill {
|
||||||
|
// C++20 or later (unfortunately there's no feature test macro).
|
||||||
|
// Stupidly, clang says C++20, but `std::identity` was only implemented in libc++ 13 and libstd++-v3 10
|
||||||
|
// (the latter is used on Linux).
|
||||||
|
// gcc got it right and reports C++20 only starting with v10.
|
||||||
|
// The check here doesn't care and checks the library versions in use.
|
||||||
|
//
|
||||||
|
// Another way of detection would be the constrained algorithms feature macro __cpp_lib_ranges
|
||||||
|
#if(__cplusplus >= 202002L) && \
|
||||||
|
((!_LIBCPP_VERSION || _LIBCPP_VERSION >= 13) && (!_GLIBCXX_RELEASE || _GLIBCXX_RELEASE >= 10))
|
||||||
|
using std::identity;
|
||||||
|
#else
|
||||||
|
struct identity {
|
||||||
|
template<class T>
|
||||||
|
constexpr T&& operator()(T&& v) const noexcept {
|
||||||
|
return std::forward<T>(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
using is_transparent = int;
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if __cpp_lib_invoke >= 201411L
|
||||||
|
using std::invoke;
|
||||||
|
#else
|
||||||
|
// pointer-to-data-member+object
|
||||||
|
template<class Callable,
|
||||||
|
class Object,
|
||||||
|
class... Args,
|
||||||
|
class Unqualified = remove_cvref_t<Callable>,
|
||||||
|
std::enable_if_t<std::is_member_object_pointer<Unqualified>::value, bool> = true>
|
||||||
|
decltype(auto) invoke(Callable&& callable, Object&& object, Args&&... args) {
|
||||||
|
return std::forward<Object>(object).*callable;
|
||||||
|
}
|
||||||
|
|
||||||
|
// pointer-to-member-function+object
|
||||||
|
template<class Callable,
|
||||||
|
class Object,
|
||||||
|
class... Args,
|
||||||
|
class Unqualified = remove_cvref_t<Callable>,
|
||||||
|
std::enable_if_t<std::is_member_function_pointer<Unqualified>::value, bool> = true>
|
||||||
|
decltype(auto) invoke(Callable&& callable, Object&& object, Args&&... args) {
|
||||||
|
return (std::forward<Object>(object).*callable)(std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
// pointer-to-member+reference-wrapped object (expect `reference_wrapper::*`)
|
||||||
|
template<class Callable,
|
||||||
|
class Object,
|
||||||
|
class... Args,
|
||||||
|
std::enable_if_t<polyfill::negation_v<polyfill::is_specialization_of<
|
||||||
|
member_object_type_t<std::remove_reference_t<Callable>>,
|
||||||
|
std::reference_wrapper>>,
|
||||||
|
bool> = true>
|
||||||
|
decltype(auto) invoke(Callable&& callable, std::reference_wrapper<Object> wrapper, Args&&... args) {
|
||||||
|
return invoke(std::forward<Callable>(callable), wrapper.get(), std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
// functor
|
||||||
|
template<class Callable, class... Args>
|
||||||
|
decltype(auto) invoke(Callable&& callable, Args&&... args) {
|
||||||
|
return std::forward<Callable>(callable)(std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace polyfill = internal::polyfill;
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "cxx_core_features.h"
|
||||||
|
|
||||||
|
#if SQLITE_ORM_HAS_INCLUDE(<optional>)
|
||||||
|
#include <optional>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if __cpp_lib_optional >= 201606L
|
||||||
|
#define SQLITE_ORM_OPTIONAL_SUPPORTED
|
||||||
|
#endif
|
|
@ -0,0 +1,11 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "cxx_core_features.h"
|
||||||
|
|
||||||
|
#if SQLITE_ORM_HAS_INCLUDE(<string_view>)
|
||||||
|
#include <string_view>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if __cpp_lib_string_view >= 201606L
|
||||||
|
#define SQLITE_ORM_STRING_VIEW_SUPPORTED
|
||||||
|
#endif
|
|
@ -0,0 +1,145 @@
|
||||||
|
#pragma once
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
#include "cxx_universal.h"
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
namespace internal {
|
||||||
|
namespace polyfill {
|
||||||
|
#if __cpp_lib_void_t >= 201411L
|
||||||
|
using std::void_t;
|
||||||
|
#else
|
||||||
|
template<class...>
|
||||||
|
using void_t = void;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if __cpp_lib_bool_constant >= 201505L
|
||||||
|
using std::bool_constant;
|
||||||
|
#else
|
||||||
|
template<bool v>
|
||||||
|
using bool_constant = std::integral_constant<bool, v>;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if __cpp_lib_logical_traits >= 201510L && __cpp_lib_type_trait_variable_templates >= 201510L
|
||||||
|
using std::conjunction;
|
||||||
|
using std::conjunction_v;
|
||||||
|
using std::disjunction;
|
||||||
|
using std::disjunction_v;
|
||||||
|
using std::negation;
|
||||||
|
using std::negation_v;
|
||||||
|
#else
|
||||||
|
template<typename...>
|
||||||
|
struct conjunction : std::true_type {};
|
||||||
|
template<typename B1>
|
||||||
|
struct conjunction<B1> : B1 {};
|
||||||
|
template<typename B1, typename... Bn>
|
||||||
|
struct conjunction<B1, Bn...> : std::conditional_t<bool(B1::value), conjunction<Bn...>, B1> {};
|
||||||
|
template<typename... Bs>
|
||||||
|
SQLITE_ORM_INLINE_VAR constexpr bool conjunction_v = conjunction<Bs...>::value;
|
||||||
|
|
||||||
|
template<typename...>
|
||||||
|
struct disjunction : std::false_type {};
|
||||||
|
template<typename B1>
|
||||||
|
struct disjunction<B1> : B1 {};
|
||||||
|
template<typename B1, typename... Bn>
|
||||||
|
struct disjunction<B1, Bn...> : std::conditional_t<bool(B1::value), B1, disjunction<Bn...>> {};
|
||||||
|
template<typename... Bs>
|
||||||
|
SQLITE_ORM_INLINE_VAR constexpr bool disjunction_v = disjunction<Bs...>::value;
|
||||||
|
|
||||||
|
template<typename B>
|
||||||
|
struct negation : bool_constant<!bool(B::value)> {};
|
||||||
|
template<typename B>
|
||||||
|
SQLITE_ORM_INLINE_VAR constexpr bool negation_v = negation<B>::value;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if __cpp_lib_remove_cvref >= 201711L
|
||||||
|
using std::remove_cvref, std::remove_cvref_t;
|
||||||
|
#else
|
||||||
|
template<class T>
|
||||||
|
struct remove_cvref : std::remove_cv<std::remove_reference_t<T>> {};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
using remove_cvref_t = typename remove_cvref<T>::type;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if __cpp_lib_type_identity >= 201806L
|
||||||
|
using std::type_identity, std::type_identity_t;
|
||||||
|
#else
|
||||||
|
template<class T>
|
||||||
|
struct type_identity {
|
||||||
|
using type = T;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
using type_identity_t = typename type_identity<T>::type;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if 0 // __cpp_lib_detect >= 0L // library fundamentals TS v2, [meta.detect]
|
||||||
|
using std::nonesuch;
|
||||||
|
using std::detector;
|
||||||
|
using std::is_detected, std::is_detected_v;
|
||||||
|
using std::detected, std::detected_t;
|
||||||
|
using std::detected_or, std::detected_or_t;
|
||||||
|
#else
|
||||||
|
struct nonesuch {
|
||||||
|
~nonesuch() = delete;
|
||||||
|
nonesuch(const nonesuch&) = delete;
|
||||||
|
void operator=(const nonesuch&) = delete;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class Default, class AlwaysVoid, template<class...> class Op, class... Args>
|
||||||
|
struct detector {
|
||||||
|
using value_t = std::false_type;
|
||||||
|
using type = Default;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class Default, template<class...> class Op, class... Args>
|
||||||
|
struct detector<Default, polyfill::void_t<Op<Args...>>, Op, Args...> {
|
||||||
|
using value_t = std::true_type;
|
||||||
|
using type = Op<Args...>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<template<class...> class Op, class... Args>
|
||||||
|
using is_detected = typename detector<nonesuch, void, Op, Args...>::value_t;
|
||||||
|
|
||||||
|
template<template<class...> class Op, class... Args>
|
||||||
|
using detected = detector<nonesuch, void, Op, Args...>;
|
||||||
|
|
||||||
|
template<template<class...> class Op, class... Args>
|
||||||
|
using detected_t = typename detector<nonesuch, void, Op, Args...>::type;
|
||||||
|
|
||||||
|
template<class Default, template<class...> class Op, class... Args>
|
||||||
|
using detected_or = detector<Default, void, Op, Args...>;
|
||||||
|
|
||||||
|
template<class Default, template<class...> class Op, class... Args>
|
||||||
|
using detected_or_t = typename detected_or<Default, Op, Args...>::type;
|
||||||
|
|
||||||
|
template<template<class...> class Op, class... Args>
|
||||||
|
SQLITE_ORM_INLINE_VAR constexpr bool is_detected_v = is_detected<Op, Args...>::value;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if 0 // proposed but not pursued
|
||||||
|
using std::is_specialization_of, std::is_specialization_of_t, std::is_specialization_of_v;
|
||||||
|
#else
|
||||||
|
// is_specialization_of: https://github.com/cplusplus/papers/issues/812
|
||||||
|
|
||||||
|
template<typename Type, template<typename...> class Primary>
|
||||||
|
SQLITE_ORM_INLINE_VAR constexpr bool is_specialization_of_v = false;
|
||||||
|
|
||||||
|
template<template<typename...> class Primary, class... Types>
|
||||||
|
SQLITE_ORM_INLINE_VAR constexpr bool is_specialization_of_v<Primary<Types...>, Primary> = true;
|
||||||
|
|
||||||
|
template<typename Type, template<typename...> class Primary>
|
||||||
|
struct is_specialization_of : bool_constant<is_specialization_of_v<Type, Primary>> {};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
template<typename...>
|
||||||
|
SQLITE_ORM_INLINE_VAR constexpr bool always_false_v = false;
|
||||||
|
|
||||||
|
template<size_t I>
|
||||||
|
using index_constant = std::integral_constant<size_t, I>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace polyfill = internal::polyfill;
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This header makes central C++ functionality on which sqlite_orm depends universally available:
|
||||||
|
* - alternative operator representations
|
||||||
|
* - ::size_t, ::ptrdiff_t, ::nullptr_t
|
||||||
|
* - C++ core language feature macros
|
||||||
|
* - macros for dealing with compiler quirks
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <iso646.h> // alternative operator representations
|
||||||
|
#include <cstddef> // sqlite_orm is using size_t, ptrdiff_t, nullptr_t everywhere, pull it in early
|
||||||
|
|
||||||
|
// earlier clang versions didn't make nullptr_t available in the global namespace via stddef.h,
|
||||||
|
// though it should have according to C++ documentation (see https://en.cppreference.com/w/cpp/types/nullptr_t#Notes).
|
||||||
|
// actually it should be available when including stddef.h
|
||||||
|
using std::nullptr_t;
|
||||||
|
|
||||||
|
#include "cxx_core_features.h"
|
||||||
|
#include "cxx_compiler_quirks.h"
|
|
@ -0,0 +1,6 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if defined(_MSC_VER)
|
||||||
|
__pragma(pop_macro("max"))
|
||||||
|
__pragma(pop_macro("min"))
|
||||||
|
#endif // defined(_MSC_VER)
|
|
@ -0,0 +1,34 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <utility> // std::index_sequence, std::make_index_sequence
|
||||||
|
|
||||||
|
#include "../functional/cxx_universal.h"
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
namespace internal {
|
||||||
|
/**
|
||||||
|
* Get the first value of an index_sequence.
|
||||||
|
*/
|
||||||
|
template<size_t I, size_t... Idx>
|
||||||
|
SQLITE_ORM_CONSTEVAL size_t first_index_sequence_value(std::index_sequence<I, Idx...>) {
|
||||||
|
return I;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class... Seq>
|
||||||
|
struct flatten_idxseq {
|
||||||
|
using type = std::index_sequence<>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<size_t... Ix>
|
||||||
|
struct flatten_idxseq<std::index_sequence<Ix...>> {
|
||||||
|
using type = std::index_sequence<Ix...>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<size_t... As, size_t... Bs, class... Seq>
|
||||||
|
struct flatten_idxseq<std::index_sequence<As...>, std::index_sequence<Bs...>, Seq...>
|
||||||
|
: flatten_idxseq<std::index_sequence<As..., Bs...>, Seq...> {};
|
||||||
|
|
||||||
|
template<class... Seq>
|
||||||
|
using flatten_idxseq_t = typename flatten_idxseq<Seq...>::type;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,306 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Symbols for 'template metaprogramming' (compile-time template programming),
|
||||||
|
* inspired by the MPL of Aleksey Gurtovoy and David Abrahams.
|
||||||
|
*
|
||||||
|
* Currently, the focus is on facilitating advanced type filtering,
|
||||||
|
* such as filtering columns by constraints having various traits.
|
||||||
|
* Hence it contains only a very small subset of a full MPL.
|
||||||
|
*
|
||||||
|
* Two key concepts are critical to understanding:
|
||||||
|
* 1. A 'metafunction' is a class template that represents a function invocable at compile-time.
|
||||||
|
* 2. A 'metafunction class' is a certain form of metafunction representation that enables higher-order metaprogramming.
|
||||||
|
* More precisely, it's a class with a nested metafunction called "fn"
|
||||||
|
* Correspondingly, a metafunction class invocation is defined as invocation of its nested "fn" metafunction.
|
||||||
|
* 3. A 'metafunction operation' is an alias template that represents a function whose instantiation already yields a type.
|
||||||
|
*
|
||||||
|
* Conventions:
|
||||||
|
* - "Fn" is the name for a metafunction template template parameter.
|
||||||
|
* - "FnCls" is the name for a metafunction class template parameter.
|
||||||
|
* - "_fn" is a suffix for a type that accepts metafunctions and turns them into metafunction classes.
|
||||||
|
* - "higher order" denotes a metafunction that operates on another metafunction (i.e. takes it as an argument).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <type_traits> // std::false_type, std::true_type
|
||||||
|
|
||||||
|
#include "cxx_universal.h"
|
||||||
|
#include "cxx_type_traits_polyfill.h"
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
namespace internal {
|
||||||
|
namespace mpl {
|
||||||
|
template<template<class...> class Fn>
|
||||||
|
struct indirectly_test_metafunction;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Determines whether a class template has a nested metafunction `fn`.
|
||||||
|
*
|
||||||
|
* Implementation note: the technique of specialiazing on the inline variable must come first because
|
||||||
|
* of older compilers having problems with the detection of dependent templates [SQLITE_ORM_BROKEN_VARIADIC_PACK_EXPANSION].
|
||||||
|
*/
|
||||||
|
template<class T, class SFINAE = void>
|
||||||
|
SQLITE_ORM_INLINE_VAR constexpr bool is_metafunction_class_v = false;
|
||||||
|
template<class FnCls>
|
||||||
|
SQLITE_ORM_INLINE_VAR constexpr bool
|
||||||
|
is_metafunction_class_v<FnCls, polyfill::void_t<indirectly_test_metafunction<FnCls::template fn>>> =
|
||||||
|
true;
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct is_metafunction_class : polyfill::bool_constant<is_metafunction_class_v<T>> {};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Invoke metafunction.
|
||||||
|
*/
|
||||||
|
template<template<class...> class Fn, class... Args>
|
||||||
|
using invoke_fn_t = typename Fn<Args...>::type;
|
||||||
|
|
||||||
|
#ifdef SQLITE_ORM_BROKEN_VARIADIC_PACK_EXPANSION
|
||||||
|
template<template<class...> class Op, class... Args>
|
||||||
|
struct wrap_op {
|
||||||
|
using type = Op<Args...>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Invoke metafunction operation.
|
||||||
|
*
|
||||||
|
* Note: legacy compilers need an extra layer of indirection, otherwise type replacement may fail
|
||||||
|
* if alias template `Op` has a dependent expression in it.
|
||||||
|
*/
|
||||||
|
template<template<class...> class Op, class... Args>
|
||||||
|
using invoke_op_t = typename wrap_op<Op, Args...>::type;
|
||||||
|
#else
|
||||||
|
/*
|
||||||
|
* Invoke metafunction operation.
|
||||||
|
*/
|
||||||
|
template<template<class...> class Op, class... Args>
|
||||||
|
using invoke_op_t = Op<Args...>;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Invoke metafunction class by invoking its nested metafunction.
|
||||||
|
*/
|
||||||
|
template<class FnCls, class... Args>
|
||||||
|
using invoke_t = typename FnCls::template fn<Args...>::type;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Instantiate metafunction class' nested metafunction.
|
||||||
|
*/
|
||||||
|
template<class FnCls, class... Args>
|
||||||
|
using instantiate = typename FnCls::template fn<Args...>;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Wrap given type such that `typename T::type` is valid.
|
||||||
|
*/
|
||||||
|
template<class T, class SFINAE = void>
|
||||||
|
struct type_wrap : polyfill::type_identity<T> {};
|
||||||
|
template<class T>
|
||||||
|
struct type_wrap<T, polyfill::void_t<typename T::type>> : T {};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Turn metafunction into a metafunction class.
|
||||||
|
*
|
||||||
|
* Invocation of the nested metafunction `fn` is SFINAE-friendly (detection idiom).
|
||||||
|
* This is necessary because `fn` is a proxy to the originally quoted metafunction,
|
||||||
|
* and the instantiation of the metafunction might be an invalid expression.
|
||||||
|
*/
|
||||||
|
template<template<class...> class Fn>
|
||||||
|
struct quote_fn {
|
||||||
|
template<class InvocableTest, template<class...> class, class...>
|
||||||
|
struct invoke_fn;
|
||||||
|
|
||||||
|
template<template<class...> class F, class... Args>
|
||||||
|
struct invoke_fn<polyfill::void_t<F<Args...>>, F, Args...> {
|
||||||
|
using type = type_wrap<F<Args...>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class... Args>
|
||||||
|
using fn = typename invoke_fn<void, Fn, Args...>::type;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Indirection wrapper for higher-order metafunctions,
|
||||||
|
* specialized on the argument indexes where metafunctions appear.
|
||||||
|
*/
|
||||||
|
template<size_t...>
|
||||||
|
struct higherorder;
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct higherorder<0u> {
|
||||||
|
/*
|
||||||
|
* Turn higher-order metafunction into a metafunction class.
|
||||||
|
*/
|
||||||
|
template<template<template<class...> class Fn, class... Args2> class HigherFn>
|
||||||
|
struct quote_fn {
|
||||||
|
template<class QuotedFn, class... Args2>
|
||||||
|
struct fn : HigherFn<QuotedFn::template fn, Args2...> {};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Metafunction class that extracts the nested metafunction of its metafunction class argument,
|
||||||
|
* quotes the extracted metafunction and passes it on to the next metafunction class
|
||||||
|
* (kind of the inverse of quoting).
|
||||||
|
*/
|
||||||
|
template<class FnCls>
|
||||||
|
struct pass_extracted_fn_to {
|
||||||
|
template<class... Args>
|
||||||
|
struct fn : FnCls::template fn<Args...> {};
|
||||||
|
|
||||||
|
// extract, quote, pass on
|
||||||
|
template<template<class...> class Fn, class... Args>
|
||||||
|
struct fn<Fn<Args...>> : FnCls::template fn<quote_fn<Fn>> {};
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Metafunction class that invokes the specified metafunction operation,
|
||||||
|
* and passes its result on to the next metafunction class.
|
||||||
|
*/
|
||||||
|
template<template<class...> class Op, class FnCls>
|
||||||
|
struct pass_result_to {
|
||||||
|
// call Op, pass on its result
|
||||||
|
template<class... Args>
|
||||||
|
struct fn : FnCls::template fn<Op<Args...>> {};
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Bind arguments at the front of a metafunction class.
|
||||||
|
* Metafunction class equivalent to std::bind_front().
|
||||||
|
*/
|
||||||
|
template<class FnCls, class... Bound>
|
||||||
|
struct bind_front {
|
||||||
|
template<class... Args>
|
||||||
|
struct fn : FnCls::template fn<Bound..., Args...> {};
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Bind arguments at the back of a metafunction class.
|
||||||
|
* Metafunction class equivalent to std::bind_back()
|
||||||
|
*/
|
||||||
|
template<class FnCls, class... Bound>
|
||||||
|
struct bind_back {
|
||||||
|
template<class... Args>
|
||||||
|
struct fn : FnCls::template fn<Args..., Bound...> {};
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Metafunction class equivalent to polyfill::always_false.
|
||||||
|
* It ignores arguments passed to the metafunction,
|
||||||
|
* and always returns the given type.
|
||||||
|
*/
|
||||||
|
template<class T>
|
||||||
|
struct always {
|
||||||
|
template<class...>
|
||||||
|
struct fn : type_wrap<T> {};
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Unary metafunction class equivalent to std::type_identity.
|
||||||
|
*/
|
||||||
|
struct identity {
|
||||||
|
template<class T>
|
||||||
|
struct fn : type_wrap<T> {};
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Metafunction class equivalent to std::negation.
|
||||||
|
*/
|
||||||
|
template<class FnCls>
|
||||||
|
struct not_ {
|
||||||
|
template<class... Args>
|
||||||
|
struct fn : polyfill::negation<invoke_t<FnCls, Args...>> {};
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Metafunction class equivalent to std::conjunction
|
||||||
|
*/
|
||||||
|
template<class... TraitFnCls>
|
||||||
|
struct conjunction {
|
||||||
|
template<class... Args>
|
||||||
|
struct fn : polyfill::conjunction<typename TraitFnCls::template fn<Args...>...> {};
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Metafunction class equivalent to std::disjunction.
|
||||||
|
*/
|
||||||
|
template<class... TraitFnCls>
|
||||||
|
struct disjunction {
|
||||||
|
template<class... Args>
|
||||||
|
struct fn : polyfill::disjunction<typename TraitFnCls::template fn<Args...>...> {};
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifndef SQLITE_ORM_BROKEN_VARIADIC_PACK_EXPANSION
|
||||||
|
/*
|
||||||
|
* Metafunction equivalent to std::conjunction.
|
||||||
|
*/
|
||||||
|
template<template<class...> class... TraitFn>
|
||||||
|
using conjunction_fn = conjunction<quote_fn<TraitFn>...>;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Metafunction equivalent to std::disjunction.
|
||||||
|
*/
|
||||||
|
template<template<class...> class... TraitFn>
|
||||||
|
using disjunction_fn = disjunction<quote_fn<TraitFn>...>;
|
||||||
|
#else
|
||||||
|
template<template<class...> class... TraitFn>
|
||||||
|
struct conjunction_fn : conjunction<quote_fn<TraitFn>...> {};
|
||||||
|
|
||||||
|
template<template<class...> class... TraitFn>
|
||||||
|
struct disjunction_fn : disjunction<quote_fn<TraitFn>...> {};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convenience template alias for binding arguments at the front of a metafunction.
|
||||||
|
*/
|
||||||
|
template<template<class...> class Fn, class... Bound>
|
||||||
|
using bind_front_fn = bind_front<quote_fn<Fn>, Bound...>;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convenience template alias for binding arguments at the back of a metafunction.
|
||||||
|
*/
|
||||||
|
template<template<class...> class Fn, class... Bound>
|
||||||
|
using bind_back_fn = bind_back<quote_fn<Fn>, Bound...>;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convenience template alias for binding a metafunction at the front of a higher-order metafunction.
|
||||||
|
*/
|
||||||
|
template<template<template<class...> class Fn, class... Args2> class HigherFn,
|
||||||
|
template<class...>
|
||||||
|
class BoundFn,
|
||||||
|
class... Bound>
|
||||||
|
using bind_front_higherorder_fn =
|
||||||
|
bind_front<higherorder<0>::quote_fn<HigherFn>, quote_fn<BoundFn>, Bound...>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace mpl = internal::mpl;
|
||||||
|
|
||||||
|
// convenience metafunction classes
|
||||||
|
namespace internal {
|
||||||
|
/*
|
||||||
|
* Trait metafunction class that checks if a type has the specified trait.
|
||||||
|
*/
|
||||||
|
template<template<class...> class TraitFn>
|
||||||
|
using check_if = mpl::quote_fn<TraitFn>;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Trait metafunction class that checks if a type doesn't have the specified trait.
|
||||||
|
*/
|
||||||
|
template<template<class...> class TraitFn>
|
||||||
|
using check_if_not = mpl::not_<mpl::quote_fn<TraitFn>>;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Trait metafunction class that checks if a type is the same as the specified type.
|
||||||
|
*/
|
||||||
|
template<class Type>
|
||||||
|
using check_if_is_type = mpl::bind_front_fn<std::is_same, Type>;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Trait metafunction class that checks if a type's template matches the specified template
|
||||||
|
* (similar to `is_specialization_of`).
|
||||||
|
*/
|
||||||
|
template<template<class...> class Template>
|
||||||
|
using check_if_is_template =
|
||||||
|
mpl::pass_extracted_fn_to<mpl::bind_front_fn<std::is_same, mpl::quote_fn<Template>>>;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if defined(_MSC_VER)
|
||||||
|
__pragma(push_macro("min"))
|
||||||
|
#undef min
|
||||||
|
__pragma(push_macro("max"))
|
||||||
|
#undef max
|
||||||
|
#endif // defined(_MSC_VER)
|
|
@ -0,0 +1,75 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef SQLITE_ORM_IF_CONSTEXPR_SUPPORTED
|
||||||
|
#include <type_traits> // std::false_type, std::true_type, std::integral_constant
|
||||||
|
#endif
|
||||||
|
#include <utility> // std::forward
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
|
||||||
|
// got from here
|
||||||
|
// https://stackoverflow.com/questions/37617677/implementing-a-compile-time-static-if-logic-for-different-string-types-in-a-co
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
template<class R = void>
|
||||||
|
decltype(auto) empty_callable() {
|
||||||
|
static auto res = [](auto&&...) -> R {
|
||||||
|
return R();
|
||||||
|
};
|
||||||
|
return (res);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef SQLITE_ORM_IF_CONSTEXPR_SUPPORTED
|
||||||
|
template<bool B, typename T, typename F>
|
||||||
|
decltype(auto) static_if([[maybe_unused]] T&& trueFn, [[maybe_unused]] F&& falseFn) {
|
||||||
|
if constexpr(B) {
|
||||||
|
return std::forward<T>(trueFn);
|
||||||
|
} else {
|
||||||
|
return std::forward<F>(falseFn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<bool B, typename T>
|
||||||
|
decltype(auto) static_if([[maybe_unused]] T&& trueFn) {
|
||||||
|
if constexpr(B) {
|
||||||
|
return std::forward<T>(trueFn);
|
||||||
|
} else {
|
||||||
|
return empty_callable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<bool B, typename L, typename... Args>
|
||||||
|
void call_if_constexpr([[maybe_unused]] L&& lambda, [[maybe_unused]] Args&&... args) {
|
||||||
|
if constexpr(B) {
|
||||||
|
lambda(std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
template<typename T, typename F>
|
||||||
|
decltype(auto) static_if(std::true_type, T&& trueFn, const F&) {
|
||||||
|
return std::forward<T>(trueFn);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T, typename F>
|
||||||
|
decltype(auto) static_if(std::false_type, const T&, F&& falseFn) {
|
||||||
|
return std::forward<F>(falseFn);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<bool B, typename T, typename F>
|
||||||
|
decltype(auto) static_if(T&& trueFn, F&& falseFn) {
|
||||||
|
return static_if(std::integral_constant<bool, B>{}, std::forward<T>(trueFn), std::forward<F>(falseFn));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<bool B, typename T>
|
||||||
|
decltype(auto) static_if(T&& trueFn) {
|
||||||
|
return static_if(std::integral_constant<bool, B>{}, std::forward<T>(trueFn), empty_callable());
|
||||||
|
}
|
||||||
|
|
||||||
|
template<bool B, typename L, typename... Args>
|
||||||
|
void call_if_constexpr(L&& lambda, Args&&... args) {
|
||||||
|
static_if<B>(std::forward<L>(lambda))(std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,175 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <type_traits> // std::is_same, std::decay, std::remove_reference
|
||||||
|
#include <tuple> // std::get
|
||||||
|
|
||||||
|
#include "functional/cxx_universal.h" // ::size_t
|
||||||
|
#include "functional/static_magic.h"
|
||||||
|
#include "prepared_statement.h"
|
||||||
|
#include "ast_iterator.h"
|
||||||
|
#include "node_tuple.h"
|
||||||
|
#include "expression_object_type.h"
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
|
||||||
|
template<int N, class It, class L, class O>
|
||||||
|
auto& get(internal::prepared_statement_t<internal::insert_range_t<It, L, O>>& statement) {
|
||||||
|
return std::get<N>(statement.expression.range);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<int N, class It, class L, class O>
|
||||||
|
const auto& get(const internal::prepared_statement_t<internal::insert_range_t<It, L, O>>& statement) {
|
||||||
|
return std::get<N>(statement.expression.range);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<int N, class It, class L, class O>
|
||||||
|
auto& get(internal::prepared_statement_t<internal::replace_range_t<It, L, O>>& statement) {
|
||||||
|
return std::get<N>(statement.expression.range);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<int N, class It, class L, class O>
|
||||||
|
const auto& get(const internal::prepared_statement_t<internal::replace_range_t<It, L, O>>& statement) {
|
||||||
|
return std::get<N>(statement.expression.range);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<int N, class T, class... Ids>
|
||||||
|
auto& get(internal::prepared_statement_t<internal::get_t<T, Ids...>>& statement) {
|
||||||
|
return internal::get_ref(std::get<N>(statement.expression.ids));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<int N, class T, class... Ids>
|
||||||
|
const auto& get(const internal::prepared_statement_t<internal::get_t<T, Ids...>>& statement) {
|
||||||
|
return internal::get_ref(std::get<N>(statement.expression.ids));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<int N, class T, class... Ids>
|
||||||
|
auto& get(internal::prepared_statement_t<internal::get_pointer_t<T, Ids...>>& statement) {
|
||||||
|
return internal::get_ref(std::get<N>(statement.expression.ids));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<int N, class T, class... Ids>
|
||||||
|
const auto& get(const internal::prepared_statement_t<internal::get_pointer_t<T, Ids...>>& statement) {
|
||||||
|
return internal::get_ref(std::get<N>(statement.expression.ids));
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef SQLITE_ORM_OPTIONAL_SUPPORTED
|
||||||
|
template<int N, class T, class... Ids>
|
||||||
|
auto& get(internal::prepared_statement_t<internal::get_optional_t<T, Ids...>>& statement) {
|
||||||
|
return internal::get_ref(std::get<N>(statement.expression.ids));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<int N, class T, class... Ids>
|
||||||
|
const auto& get(const internal::prepared_statement_t<internal::get_optional_t<T, Ids...>>& statement) {
|
||||||
|
return internal::get_ref(std::get<N>(statement.expression.ids));
|
||||||
|
}
|
||||||
|
#endif // SQLITE_ORM_OPTIONAL_SUPPORTED
|
||||||
|
|
||||||
|
template<int N, class T, class... Ids>
|
||||||
|
auto& get(internal::prepared_statement_t<internal::remove_t<T, Ids...>>& statement) {
|
||||||
|
return internal::get_ref(std::get<N>(statement.expression.ids));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<int N, class T, class... Ids>
|
||||||
|
const auto& get(const internal::prepared_statement_t<internal::remove_t<T, Ids...>>& statement) {
|
||||||
|
return internal::get_ref(std::get<N>(statement.expression.ids));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<int N, class T>
|
||||||
|
auto& get(internal::prepared_statement_t<internal::update_t<T>>& statement) {
|
||||||
|
static_assert(N == 0, "get<> works only with 0 argument for update statement");
|
||||||
|
return internal::get_ref(statement.expression.object);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<int N, class T>
|
||||||
|
const auto& get(const internal::prepared_statement_t<internal::update_t<T>>& statement) {
|
||||||
|
static_assert(N == 0, "get<> works only with 0 argument for update statement");
|
||||||
|
return internal::get_ref(statement.expression.object);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<int N, class T, class... Cols>
|
||||||
|
auto& get(internal::prepared_statement_t<internal::insert_explicit<T, Cols...>>& statement) {
|
||||||
|
static_assert(N == 0, "get<> works only with 0 argument for insert statement");
|
||||||
|
return internal::get_ref(statement.expression.obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<int N, class T, class... Cols>
|
||||||
|
const auto& get(const internal::prepared_statement_t<internal::insert_explicit<T, Cols...>>& statement) {
|
||||||
|
static_assert(N == 0, "get<> works only with 0 argument for insert statement");
|
||||||
|
return internal::get_ref(statement.expression.obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<int N, class T>
|
||||||
|
auto& get(internal::prepared_statement_t<internal::replace_t<T>>& statement) {
|
||||||
|
static_assert(N == 0, "get<> works only with 0 argument for replace statement");
|
||||||
|
return internal::get_ref(statement.expression.object);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<int N, class T>
|
||||||
|
const auto& get(const internal::prepared_statement_t<internal::replace_t<T>>& statement) {
|
||||||
|
static_assert(N == 0, "get<> works only with 0 argument for replace statement");
|
||||||
|
return internal::get_ref(statement.expression.object);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<int N, class T>
|
||||||
|
auto& get(internal::prepared_statement_t<internal::insert_t<T>>& statement) {
|
||||||
|
static_assert(N == 0, "get<> works only with 0 argument for insert statement");
|
||||||
|
return internal::get_ref(statement.expression.object);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<int N, class T>
|
||||||
|
const auto& get(const internal::prepared_statement_t<internal::insert_t<T>>& statement) {
|
||||||
|
static_assert(N == 0, "get<> works only with 0 argument for insert statement");
|
||||||
|
return internal::get_ref(statement.expression.object);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<int N, class T>
|
||||||
|
const auto& get(const internal::prepared_statement_t<T>& statement) {
|
||||||
|
using statement_type = std::decay_t<decltype(statement)>;
|
||||||
|
using expression_type = typename statement_type::expression_type;
|
||||||
|
using node_tuple = internal::node_tuple_t<expression_type>;
|
||||||
|
using bind_tuple = internal::bindable_filter_t<node_tuple>;
|
||||||
|
using result_type = std::tuple_element_t<static_cast<size_t>(N), bind_tuple>;
|
||||||
|
const result_type* result = nullptr;
|
||||||
|
internal::iterate_ast(statement.expression, [&result, index = -1](auto& node) mutable {
|
||||||
|
using node_type = std::decay_t<decltype(node)>;
|
||||||
|
if(internal::is_bindable_v<node_type>) {
|
||||||
|
++index;
|
||||||
|
}
|
||||||
|
if(index == N) {
|
||||||
|
internal::call_if_constexpr<std::is_same<result_type, node_type>::value>(
|
||||||
|
[](auto& r, auto& n) {
|
||||||
|
r = &n;
|
||||||
|
},
|
||||||
|
result,
|
||||||
|
node);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return internal::get_ref(*result);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<int N, class T>
|
||||||
|
auto& get(internal::prepared_statement_t<T>& statement) {
|
||||||
|
using statement_type = std::decay_t<decltype(statement)>;
|
||||||
|
using expression_type = typename statement_type::expression_type;
|
||||||
|
using node_tuple = internal::node_tuple_t<expression_type>;
|
||||||
|
using bind_tuple = internal::bindable_filter_t<node_tuple>;
|
||||||
|
using result_type = std::tuple_element_t<static_cast<size_t>(N), bind_tuple>;
|
||||||
|
result_type* result = nullptr;
|
||||||
|
|
||||||
|
internal::iterate_ast(statement.expression, [&result, index = -1](auto& node) mutable {
|
||||||
|
using node_type = std::decay_t<decltype(node)>;
|
||||||
|
if(internal::is_bindable_v<node_type>) {
|
||||||
|
++index;
|
||||||
|
}
|
||||||
|
if(index == N) {
|
||||||
|
internal::call_if_constexpr<std::is_same<result_type, node_type>::value>(
|
||||||
|
[](auto& r, auto& n) {
|
||||||
|
r = const_cast<std::remove_reference_t<decltype(r)>>(&n);
|
||||||
|
},
|
||||||
|
result,
|
||||||
|
node);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return internal::get_ref(*result);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
/** @file Mainly existing to disentangle implementation details from circular and cross dependencies
|
||||||
|
* (e.g. column_t -> default_value_extractor -> serializer_context -> db_objects_tuple -> table_t -> column_t)
|
||||||
|
* this file is also used to provide definitions of interface methods 'hitting the database'.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory> // std::make_unique
|
||||||
|
|
||||||
|
#include "../functional/cxx_core_features.h"
|
||||||
|
#include "../functional/static_magic.h"
|
||||||
|
#include "../functional/index_sequence_util.h"
|
||||||
|
#include "../tuple_helper/tuple_filter.h"
|
||||||
|
#include "../tuple_helper/tuple_traits.h"
|
||||||
|
#include "../default_value_extractor.h"
|
||||||
|
#include "../column.h"
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
template<class... Op>
|
||||||
|
std::unique_ptr<std::string> column_constraints<Op...>::default_value() const {
|
||||||
|
using default_op_index_sequence =
|
||||||
|
filter_tuple_sequence_t<constraints_type, check_if_is_template<default_t>::template fn>;
|
||||||
|
|
||||||
|
std::unique_ptr<std::string> value;
|
||||||
|
call_if_constexpr<default_op_index_sequence::size()>(
|
||||||
|
[&value](auto& constraints, auto op_index_sequence) {
|
||||||
|
using default_op_index_sequence = decltype(op_index_sequence);
|
||||||
|
constexpr size_t opIndex = first_index_sequence_value(default_op_index_sequence{});
|
||||||
|
value = std::make_unique<std::string>(serialize_default_value(get<opIndex>(constraints)));
|
||||||
|
},
|
||||||
|
this->constraints,
|
||||||
|
default_op_index_sequence{});
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,144 @@
|
||||||
|
/** @file Mainly existing to disentangle implementation details from circular and cross dependencies
|
||||||
|
* this file is also used to separate implementation details from the main header file,
|
||||||
|
* e.g. usage of the dbstat table.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
#include <type_traits> // std::is_same
|
||||||
|
#include <sstream>
|
||||||
|
#include <functional> // std::reference_wrapper, std::cref
|
||||||
|
#include <algorithm> // std::find_if, std::ranges::find
|
||||||
|
|
||||||
|
#include "../dbstat.h"
|
||||||
|
#include "../type_traits.h"
|
||||||
|
#include "../util.h"
|
||||||
|
#include "../serializing_util.h"
|
||||||
|
#include "../storage.h"
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
template<class... DBO>
|
||||||
|
template<class Table, satisfies<is_table, Table>>
|
||||||
|
sync_schema_result storage_t<DBO...>::sync_table(const Table& table, sqlite3* db, bool preserve) {
|
||||||
|
#ifdef SQLITE_ENABLE_DBSTAT_VTAB
|
||||||
|
if(std::is_same<object_type_t<Table>, dbstat>::value) {
|
||||||
|
return sync_schema_result::already_in_sync;
|
||||||
|
}
|
||||||
|
#endif // SQLITE_ENABLE_DBSTAT_VTAB
|
||||||
|
auto res = sync_schema_result::already_in_sync;
|
||||||
|
bool attempt_to_preserve = true;
|
||||||
|
|
||||||
|
auto schema_stat = this->schema_status(table, db, preserve, &attempt_to_preserve);
|
||||||
|
if(schema_stat != sync_schema_result::already_in_sync) {
|
||||||
|
if(schema_stat == sync_schema_result::new_table_created) {
|
||||||
|
this->create_table(db, table.name, table);
|
||||||
|
res = sync_schema_result::new_table_created;
|
||||||
|
} else {
|
||||||
|
if(schema_stat == sync_schema_result::old_columns_removed ||
|
||||||
|
schema_stat == sync_schema_result::new_columns_added ||
|
||||||
|
schema_stat == sync_schema_result::new_columns_added_and_old_columns_removed) {
|
||||||
|
|
||||||
|
// get table info provided in `make_table` call..
|
||||||
|
auto storageTableInfo = table.get_table_info();
|
||||||
|
|
||||||
|
// now get current table info from db using `PRAGMA table_xinfo` query..
|
||||||
|
auto dbTableInfo = this->pragma.table_xinfo(table.name); // should include generated columns
|
||||||
|
|
||||||
|
// this vector will contain pointers to columns that gotta be added..
|
||||||
|
std::vector<const table_xinfo*> columnsToAdd;
|
||||||
|
|
||||||
|
this->calculate_remove_add_columns(columnsToAdd, storageTableInfo, dbTableInfo);
|
||||||
|
|
||||||
|
if(schema_stat == sync_schema_result::old_columns_removed) {
|
||||||
|
#if SQLITE_VERSION_NUMBER >= 3035000 // DROP COLUMN feature exists (v3.35.0)
|
||||||
|
for(auto& tableInfo: dbTableInfo) {
|
||||||
|
this->drop_column(db, table.name, tableInfo.name);
|
||||||
|
}
|
||||||
|
res = sync_schema_result::old_columns_removed;
|
||||||
|
#else
|
||||||
|
// extra table columns than storage columns
|
||||||
|
this->backup_table(db, table, {});
|
||||||
|
res = sync_schema_result::old_columns_removed;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
if(schema_stat == sync_schema_result::new_columns_added) {
|
||||||
|
for(const table_xinfo* colInfo: columnsToAdd) {
|
||||||
|
table.for_each_column([this, colInfo, &tableName = table.name, db](auto& column) {
|
||||||
|
if(column.name != colInfo->name) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->add_column(db, tableName, column);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
res = sync_schema_result::new_columns_added;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(schema_stat == sync_schema_result::new_columns_added_and_old_columns_removed) {
|
||||||
|
|
||||||
|
auto storageTableInfo = table.get_table_info();
|
||||||
|
this->add_generated_cols(columnsToAdd, storageTableInfo);
|
||||||
|
|
||||||
|
// remove extra columns and generated columns
|
||||||
|
this->backup_table(db, table, columnsToAdd);
|
||||||
|
res = sync_schema_result::new_columns_added_and_old_columns_removed;
|
||||||
|
}
|
||||||
|
} else if(schema_stat == sync_schema_result::dropped_and_recreated) {
|
||||||
|
// now get current table info from db using `PRAGMA table_xinfo` query..
|
||||||
|
auto dbTableInfo = this->pragma.table_xinfo(table.name); // should include generated columns
|
||||||
|
auto storageTableInfo = table.get_table_info();
|
||||||
|
|
||||||
|
// this vector will contain pointers to columns that gotta be added..
|
||||||
|
std::vector<const table_xinfo*> columnsToAdd;
|
||||||
|
|
||||||
|
this->calculate_remove_add_columns(columnsToAdd, storageTableInfo, dbTableInfo);
|
||||||
|
|
||||||
|
this->add_generated_cols(columnsToAdd, storageTableInfo);
|
||||||
|
|
||||||
|
if(preserve && attempt_to_preserve) {
|
||||||
|
this->backup_table(db, table, columnsToAdd);
|
||||||
|
} else {
|
||||||
|
this->drop_create_with_loss(db, table);
|
||||||
|
}
|
||||||
|
res = schema_stat;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class... DBO>
|
||||||
|
template<class Table>
|
||||||
|
void storage_t<DBO...>::copy_table(
|
||||||
|
sqlite3* db,
|
||||||
|
const std::string& sourceTableName,
|
||||||
|
const std::string& destinationTableName,
|
||||||
|
const Table& table,
|
||||||
|
const std::vector<const table_xinfo*>& columnsToIgnore) const { // must ignore generated columns
|
||||||
|
std::vector<std::reference_wrapper<const std::string>> columnNames;
|
||||||
|
columnNames.reserve(table.count_columns_amount());
|
||||||
|
table.for_each_column([&columnNames, &columnsToIgnore](const column_identifier& column) {
|
||||||
|
auto& columnName = column.name;
|
||||||
|
#if __cpp_lib_ranges >= 201911L
|
||||||
|
auto columnToIgnoreIt = std::ranges::find(columnsToIgnore, columnName, &table_xinfo::name);
|
||||||
|
#else
|
||||||
|
auto columnToIgnoreIt = std::find_if(columnsToIgnore.begin(),
|
||||||
|
columnsToIgnore.end(),
|
||||||
|
[&columnName](const table_xinfo* tableInfo) {
|
||||||
|
return columnName == tableInfo->name;
|
||||||
|
});
|
||||||
|
#endif
|
||||||
|
if(columnToIgnoreIt == columnsToIgnore.end()) {
|
||||||
|
columnNames.push_back(cref(columnName));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "INSERT INTO " << streaming_identifier(destinationTableName) << " ("
|
||||||
|
<< streaming_identifiers(columnNames) << ") "
|
||||||
|
<< "SELECT " << streaming_identifiers(columnNames) << " FROM " << streaming_identifier(sourceTableName)
|
||||||
|
<< std::flush;
|
||||||
|
perform_void_exec(db, ss.str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
/** @file Mainly existing to disentangle implementation details from circular and cross dependencies
|
||||||
|
* (e.g. column_t -> default_value_extractor -> serializer_context -> db_objects_tuple -> table_t -> column_t)
|
||||||
|
* this file is also used to provide definitions of interface methods 'hitting the database'.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
#include <type_traits> // std::decay_t
|
||||||
|
#include <utility> // std::move
|
||||||
|
#include <algorithm> // std::find_if, std::ranges::find
|
||||||
|
|
||||||
|
#include "../type_printer.h"
|
||||||
|
#include "../column.h"
|
||||||
|
#include "../table.h"
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
template<class T, bool WithoutRowId, class... Cs>
|
||||||
|
std::vector<table_xinfo> table_t<T, WithoutRowId, Cs...>::get_table_info() const {
|
||||||
|
std::vector<table_xinfo> res;
|
||||||
|
res.reserve(size_t(filter_tuple_sequence_t<elements_type, is_column>::size()));
|
||||||
|
this->for_each_column([&res](auto& column) {
|
||||||
|
using field_type = field_type_t<std::decay_t<decltype(column)>>;
|
||||||
|
std::string dft;
|
||||||
|
if(auto d = column.default_value()) {
|
||||||
|
dft = std::move(*d);
|
||||||
|
}
|
||||||
|
res.emplace_back(-1,
|
||||||
|
column.name,
|
||||||
|
type_printer<field_type>().print(),
|
||||||
|
column.is_not_null(),
|
||||||
|
dft,
|
||||||
|
column.template is<is_primary_key>(),
|
||||||
|
column.is_generated());
|
||||||
|
});
|
||||||
|
auto compositeKeyColumnNames = this->composite_key_columns_names();
|
||||||
|
for(size_t i = 0; i < compositeKeyColumnNames.size(); ++i) {
|
||||||
|
auto& columnName = compositeKeyColumnNames[i];
|
||||||
|
#if __cpp_lib_ranges >= 201911L
|
||||||
|
auto it = std::ranges::find(res, columnName, &table_xinfo::name);
|
||||||
|
#else
|
||||||
|
auto it = std::find_if(res.begin(), res.end(), [&columnName](const table_xinfo& ti) {
|
||||||
|
return ti.name == columnName;
|
||||||
|
});
|
||||||
|
#endif
|
||||||
|
if(it != res.end()) {
|
||||||
|
it->pk = static_cast<int>(i + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <tuple> // std::tuple, std::make_tuple, std::declval, std::tuple_element_t
|
||||||
|
#include <string> // std::string
|
||||||
|
#include <utility> // std::forward
|
||||||
|
|
||||||
|
#include "functional/cxx_universal.h"
|
||||||
|
#include "tuple_helper/tuple_filter.h"
|
||||||
|
#include "indexed_column.h"
|
||||||
|
#include "table_type_of.h"
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
struct index_base {
|
||||||
|
std::string name;
|
||||||
|
bool unique = false;
|
||||||
|
|
||||||
|
#ifndef SQLITE_ORM_AGGREGATE_NSDMI_SUPPORTED
|
||||||
|
index_base(std::string name, bool unique) : name{std::move(name)}, unique{unique} {}
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T, class... Els>
|
||||||
|
struct index_t : index_base {
|
||||||
|
using elements_type = std::tuple<Els...>;
|
||||||
|
using object_type = void;
|
||||||
|
using table_mapped_type = T;
|
||||||
|
|
||||||
|
#ifndef SQLITE_ORM_AGGREGATE_BASES_SUPPORTED
|
||||||
|
index_t(std::string name_, bool unique_, elements_type elements_) :
|
||||||
|
index_base{std::move(name_), unique_}, elements(std::move(elements_)) {}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
elements_type elements;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T, class... Cols>
|
||||||
|
internal::index_t<T, decltype(internal::make_indexed_column(std::declval<Cols>()))...> make_index(std::string name,
|
||||||
|
Cols... cols) {
|
||||||
|
using cols_tuple = std::tuple<Cols...>;
|
||||||
|
static_assert(internal::count_tuple<cols_tuple, internal::is_where>::value <= 1,
|
||||||
|
"amount of where arguments can be 0 or 1");
|
||||||
|
SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(
|
||||||
|
return {std::move(name), false, std::make_tuple(internal::make_indexed_column(std::move(cols))...)});
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class... Cols>
|
||||||
|
internal::index_t<internal::table_type_of_t<typename std::tuple_element_t<0, std::tuple<Cols...>>>,
|
||||||
|
decltype(internal::make_indexed_column(std::declval<Cols>()))...>
|
||||||
|
make_index(std::string name, Cols... cols) {
|
||||||
|
using cols_tuple = std::tuple<Cols...>;
|
||||||
|
static_assert(internal::count_tuple<cols_tuple, internal::is_where>::value <= 1,
|
||||||
|
"amount of where arguments can be 0 or 1");
|
||||||
|
SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(
|
||||||
|
return {std::move(name), false, std::make_tuple(internal::make_indexed_column(std::move(cols))...)});
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class... Cols>
|
||||||
|
internal::index_t<internal::table_type_of_t<typename std::tuple_element_t<0, std::tuple<Cols...>>>,
|
||||||
|
decltype(internal::make_indexed_column(std::declval<Cols>()))...>
|
||||||
|
make_unique_index(std::string name, Cols... cols) {
|
||||||
|
using cols_tuple = std::tuple<Cols...>;
|
||||||
|
static_assert(internal::count_tuple<cols_tuple, internal::is_where>::value <= 1,
|
||||||
|
"amount of where arguments can be 0 or 1");
|
||||||
|
SQLITE_ORM_CLANG_SUPPRESS_MISSING_BRACES(
|
||||||
|
return {std::move(name), true, std::make_tuple(internal::make_indexed_column(std::move(cols))...)});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string> // std::string
|
||||||
|
#include <utility> // std::move
|
||||||
|
|
||||||
|
#include "functional/cxx_universal.h"
|
||||||
|
#include "ast/where.h"
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
template<class C>
|
||||||
|
struct indexed_column_t {
|
||||||
|
using column_type = C;
|
||||||
|
|
||||||
|
#ifndef SQLITE_ORM_AGGREGATE_NSDMI_SUPPORTED
|
||||||
|
indexed_column_t(column_type _column_or_expression) :
|
||||||
|
column_or_expression(std::move(_column_or_expression)) {}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
column_type column_or_expression;
|
||||||
|
std::string _collation_name;
|
||||||
|
int _order = 0; // -1 = desc, 1 = asc, 0 = not specified
|
||||||
|
|
||||||
|
indexed_column_t<column_type> collate(std::string name) {
|
||||||
|
auto res = std::move(*this);
|
||||||
|
res._collation_name = std::move(name);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
indexed_column_t<column_type> asc() {
|
||||||
|
auto res = std::move(*this);
|
||||||
|
res._order = 1;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
indexed_column_t<column_type> desc() {
|
||||||
|
auto res = std::move(*this);
|
||||||
|
res._order = -1;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class C>
|
||||||
|
indexed_column_t<C> make_indexed_column(C col) {
|
||||||
|
return {std::move(col)};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class C>
|
||||||
|
where_t<C> make_indexed_column(where_t<C> wher) {
|
||||||
|
return std::move(wher);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class C>
|
||||||
|
indexed_column_t<C> make_indexed_column(indexed_column_t<C> col) {
|
||||||
|
return std::move(col);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this function to specify indexed column inside `make_index` function call.
|
||||||
|
* Example: make_index("index_name", indexed_column(&User::id).asc())
|
||||||
|
*/
|
||||||
|
template<class C>
|
||||||
|
internal::indexed_column_t<C> indexed_column(C column_or_expression) {
|
||||||
|
return {std::move(column_or_expression)};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
/** @file Mainly existing to disentangle implementation details from circular and cross dependencies
|
||||||
|
* (e.g. column_t -> default_value_extractor -> serializer_context -> db_objects_tuple -> table_t -> column_t)
|
||||||
|
* this file is also used to provide definitions of interface methods 'hitting the database'.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "implementations/column_definitions.h"
|
||||||
|
#include "implementations/table_definitions.h"
|
||||||
|
#include "implementations/storage_definitions.h"
|
|
@ -0,0 +1,38 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <type_traits> // std::true_type, std::false_type, std::declval
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is because of bug in MSVC, for more information, please visit
|
||||||
|
* https://stackoverflow.com/questions/34672441/stdis-base-of-for-template-classes/34672753#34672753
|
||||||
|
*/
|
||||||
|
#ifdef SQLITE_ORM_BROKEN_VARIADIC_PACK_EXPANSION
|
||||||
|
template<template<typename...> class Base>
|
||||||
|
struct is_base_of_template_impl {
|
||||||
|
template<typename... Ts>
|
||||||
|
static constexpr std::true_type test(const Base<Ts...>&);
|
||||||
|
|
||||||
|
static constexpr std::false_type test(...);
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T, template<typename...> class C>
|
||||||
|
using is_base_of_template = decltype(is_base_of_template_impl<C>::test(std::declval<T>()));
|
||||||
|
#else
|
||||||
|
template<template<typename...> class C, typename... Ts>
|
||||||
|
std::true_type is_base_of_template_impl(const C<Ts...>&);
|
||||||
|
|
||||||
|
template<template<typename...> class C>
|
||||||
|
std::false_type is_base_of_template_impl(...);
|
||||||
|
|
||||||
|
template<typename T, template<typename...> class C>
|
||||||
|
using is_base_of_template = decltype(is_base_of_template_impl<C>(std::declval<T>()));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
template<typename T, template<typename...> class C>
|
||||||
|
SQLITE_ORM_INLINE_VAR constexpr bool is_base_of_template_v = is_base_of_template<T, C>::value;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
#pragma once
|
||||||
|
#include <type_traits>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specialization for optional type (std::shared_ptr / std::unique_ptr).
|
||||||
|
*/
|
||||||
|
template<typename T>
|
||||||
|
struct is_std_ptr : std::false_type {};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
struct is_std_ptr<std::shared_ptr<T>> : std::true_type {
|
||||||
|
using element_type = typename std::shared_ptr<T>::element_type;
|
||||||
|
|
||||||
|
static std::shared_ptr<T> make(std::remove_cv_t<T>&& v) {
|
||||||
|
return std::make_shared<T>(std::move(v));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
struct is_std_ptr<std::unique_ptr<T>> : std::true_type {
|
||||||
|
using element_type = typename std::unique_ptr<T>::element_type;
|
||||||
|
|
||||||
|
static auto make(std::remove_cv_t<T>&& v) {
|
||||||
|
return std::make_unique<T>(std::move(v));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <sqlite3.h>
|
||||||
|
#include <memory> // std::shared_ptr, std::unique_ptr, std::make_shared
|
||||||
|
#include <type_traits> // std::decay
|
||||||
|
#include <utility> // std::move
|
||||||
|
#include <iterator> // std::input_iterator_tag
|
||||||
|
#include <system_error> // std::system_error
|
||||||
|
#include <functional> // std::bind
|
||||||
|
|
||||||
|
#include "functional/cxx_universal.h"
|
||||||
|
#include "statement_finalizer.h"
|
||||||
|
#include "error_code.h"
|
||||||
|
#include "object_from_column_builder.h"
|
||||||
|
#include "storage_lookup.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
template<class V>
|
||||||
|
struct iterator_t {
|
||||||
|
using view_type = V;
|
||||||
|
using value_type = typename view_type::mapped_type;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/**
|
||||||
|
* shared_ptr is used over unique_ptr here
|
||||||
|
* so that the iterator can be copyable.
|
||||||
|
*/
|
||||||
|
std::shared_ptr<sqlite3_stmt> stmt;
|
||||||
|
|
||||||
|
// only null for the default constructed iterator
|
||||||
|
view_type* view = nullptr;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* shared_ptr is used over unique_ptr here
|
||||||
|
* so that the iterator can be copyable.
|
||||||
|
*/
|
||||||
|
std::shared_ptr<value_type> current;
|
||||||
|
|
||||||
|
void extract_value() {
|
||||||
|
auto& dbObjects = obtain_db_objects(this->view->storage);
|
||||||
|
this->current = std::make_shared<value_type>();
|
||||||
|
object_from_column_builder<value_type> builder{*this->current, this->stmt.get()};
|
||||||
|
pick_table<value_type>(dbObjects).for_each_column(builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
void next() {
|
||||||
|
this->current.reset();
|
||||||
|
if(sqlite3_stmt* stmt = this->stmt.get()) {
|
||||||
|
perform_step(stmt, std::bind(&iterator_t::extract_value, this));
|
||||||
|
if(!this->current) {
|
||||||
|
this->stmt.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
using difference_type = ptrdiff_t;
|
||||||
|
using pointer = value_type*;
|
||||||
|
using reference = value_type&;
|
||||||
|
using iterator_category = std::input_iterator_tag;
|
||||||
|
|
||||||
|
iterator_t(){};
|
||||||
|
|
||||||
|
iterator_t(statement_finalizer stmt_, view_type& view_) : stmt{std::move(stmt_)}, view{&view_} {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
const value_type& operator*() const {
|
||||||
|
if(!this->stmt || !this->current) {
|
||||||
|
throw std::system_error{orm_error_code::trying_to_dereference_null_iterator};
|
||||||
|
}
|
||||||
|
return *this->current;
|
||||||
|
}
|
||||||
|
|
||||||
|
const value_type* operator->() const {
|
||||||
|
return &(this->operator*());
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator_t<V>& operator++() {
|
||||||
|
next();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void operator++(int) {
|
||||||
|
this->operator++();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const iterator_t& other) const {
|
||||||
|
return this->current == other.current;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator!=(const iterator_t& other) const {
|
||||||
|
return !(*this == other);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <iterator> // std::back_inserter
|
||||||
|
#include <string> // std::string
|
||||||
|
#include <memory> // std::unique_ptr
|
||||||
|
#include <array> // std::array
|
||||||
|
#include <algorithm> // std::transform
|
||||||
|
#include <cctype> // std::toupper
|
||||||
|
|
||||||
|
#if defined(_WINNT_)
|
||||||
|
// DELETE is a macro defined in the Windows SDK (winnt.h)
|
||||||
|
#pragma push_macro("DELETE")
|
||||||
|
#undef DELETE
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Caps case because of:
|
||||||
|
* 1) delete keyword;
|
||||||
|
* 2) https://www.sqlite.org/pragma.html#pragma_journal_mode original spelling
|
||||||
|
*/
|
||||||
|
enum class journal_mode : signed char {
|
||||||
|
DELETE = 0,
|
||||||
|
// An alternate enumeration value when using the Windows SDK that defines DELETE as a macro.
|
||||||
|
DELETE_ = DELETE,
|
||||||
|
TRUNCATE = 1,
|
||||||
|
PERSIST = 2,
|
||||||
|
MEMORY = 3,
|
||||||
|
WAL = 4,
|
||||||
|
OFF = 5,
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
inline const std::string& to_string(journal_mode j) {
|
||||||
|
static std::string res[] = {
|
||||||
|
"DELETE",
|
||||||
|
"TRUNCATE",
|
||||||
|
"PERSIST",
|
||||||
|
"MEMORY",
|
||||||
|
"WAL",
|
||||||
|
"OFF",
|
||||||
|
};
|
||||||
|
return res[static_cast<int>(j)];
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::unique_ptr<journal_mode> journal_mode_from_string(const std::string& str) {
|
||||||
|
std::string upper_str;
|
||||||
|
std::transform(str.begin(), str.end(), std::back_inserter(upper_str), [](char c) {
|
||||||
|
return static_cast<char>(std::toupper(static_cast<int>(c)));
|
||||||
|
});
|
||||||
|
static std::array<journal_mode, 6> all = {{
|
||||||
|
journal_mode::DELETE,
|
||||||
|
journal_mode::TRUNCATE,
|
||||||
|
journal_mode::PERSIST,
|
||||||
|
journal_mode::MEMORY,
|
||||||
|
journal_mode::WAL,
|
||||||
|
journal_mode::OFF,
|
||||||
|
}};
|
||||||
|
for(auto j: all) {
|
||||||
|
if(to_string(j) == upper_str) {
|
||||||
|
return std::make_unique<journal_mode>(j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(_WINNT_)
|
||||||
|
#pragma pop_macro("DELETE")
|
||||||
|
#endif
|
|
@ -0,0 +1,139 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <sqlite3.h>
|
||||||
|
#include <map> // std::map
|
||||||
|
#include <functional> // std::function
|
||||||
|
#include <memory> // std::shared_ptr
|
||||||
|
|
||||||
|
#include "connection_holder.h"
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
struct limit_accessor {
|
||||||
|
using get_connection_t = std::function<connection_ref()>;
|
||||||
|
|
||||||
|
limit_accessor(get_connection_t get_connection_) : get_connection(std::move(get_connection_)) {}
|
||||||
|
|
||||||
|
int length() {
|
||||||
|
return this->get(SQLITE_LIMIT_LENGTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
void length(int newValue) {
|
||||||
|
this->set(SQLITE_LIMIT_LENGTH, newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
int sql_length() {
|
||||||
|
return this->get(SQLITE_LIMIT_SQL_LENGTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sql_length(int newValue) {
|
||||||
|
this->set(SQLITE_LIMIT_SQL_LENGTH, newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
int column() {
|
||||||
|
return this->get(SQLITE_LIMIT_COLUMN);
|
||||||
|
}
|
||||||
|
|
||||||
|
void column(int newValue) {
|
||||||
|
this->set(SQLITE_LIMIT_COLUMN, newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
int expr_depth() {
|
||||||
|
return this->get(SQLITE_LIMIT_EXPR_DEPTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
void expr_depth(int newValue) {
|
||||||
|
this->set(SQLITE_LIMIT_EXPR_DEPTH, newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
int compound_select() {
|
||||||
|
return this->get(SQLITE_LIMIT_COMPOUND_SELECT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void compound_select(int newValue) {
|
||||||
|
this->set(SQLITE_LIMIT_COMPOUND_SELECT, newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
int vdbe_op() {
|
||||||
|
return this->get(SQLITE_LIMIT_VDBE_OP);
|
||||||
|
}
|
||||||
|
|
||||||
|
void vdbe_op(int newValue) {
|
||||||
|
this->set(SQLITE_LIMIT_VDBE_OP, newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
int function_arg() {
|
||||||
|
return this->get(SQLITE_LIMIT_FUNCTION_ARG);
|
||||||
|
}
|
||||||
|
|
||||||
|
void function_arg(int newValue) {
|
||||||
|
this->set(SQLITE_LIMIT_FUNCTION_ARG, newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
int attached() {
|
||||||
|
return this->get(SQLITE_LIMIT_ATTACHED);
|
||||||
|
}
|
||||||
|
|
||||||
|
void attached(int newValue) {
|
||||||
|
this->set(SQLITE_LIMIT_ATTACHED, newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
int like_pattern_length() {
|
||||||
|
return this->get(SQLITE_LIMIT_LIKE_PATTERN_LENGTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
void like_pattern_length(int newValue) {
|
||||||
|
this->set(SQLITE_LIMIT_LIKE_PATTERN_LENGTH, newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
int variable_number() {
|
||||||
|
return this->get(SQLITE_LIMIT_VARIABLE_NUMBER);
|
||||||
|
}
|
||||||
|
|
||||||
|
void variable_number(int newValue) {
|
||||||
|
this->set(SQLITE_LIMIT_VARIABLE_NUMBER, newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
int trigger_depth() {
|
||||||
|
return this->get(SQLITE_LIMIT_TRIGGER_DEPTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
void trigger_depth(int newValue) {
|
||||||
|
this->set(SQLITE_LIMIT_TRIGGER_DEPTH, newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if SQLITE_VERSION_NUMBER >= 3008007
|
||||||
|
int worker_threads() {
|
||||||
|
return this->get(SQLITE_LIMIT_WORKER_THREADS);
|
||||||
|
}
|
||||||
|
|
||||||
|
void worker_threads(int newValue) {
|
||||||
|
this->set(SQLITE_LIMIT_WORKER_THREADS, newValue);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
protected:
|
||||||
|
get_connection_t get_connection;
|
||||||
|
|
||||||
|
friend struct storage_base;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores limit set between connections.
|
||||||
|
*/
|
||||||
|
std::map<int, int> limits;
|
||||||
|
|
||||||
|
int get(int id) {
|
||||||
|
auto connection = this->get_connection();
|
||||||
|
return sqlite3_limit(connection.get(), id, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void set(int id, int newValue) {
|
||||||
|
this->limits[id] = newValue;
|
||||||
|
auto connection = this->get_connection();
|
||||||
|
sqlite3_limit(connection.get(), id, newValue);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Protect an otherwise bindable element so that it is always serialized as a literal value.
|
||||||
|
*/
|
||||||
|
template<class T>
|
||||||
|
struct literal_holder {
|
||||||
|
using type = T;
|
||||||
|
|
||||||
|
type value;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <sqlite3.h>
|
||||||
|
|
||||||
|
#include "object_from_column_builder.h"
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a private row extractor class. It is used for extracting rows as objects instead of tuple.
|
||||||
|
* Main difference from regular `row_extractor` is that this class takes table info which is required
|
||||||
|
* for constructing objects by member pointers. To construct please use `make_row_extractor()`.
|
||||||
|
* Type arguments:
|
||||||
|
* V is value type just like regular `row_extractor` has
|
||||||
|
* T is table info class `table_t`
|
||||||
|
*/
|
||||||
|
template<class V, class Table>
|
||||||
|
struct mapped_row_extractor {
|
||||||
|
using table_type = Table;
|
||||||
|
|
||||||
|
V extract(sqlite3_stmt* stmt, int /*columnIndex*/) const {
|
||||||
|
V res;
|
||||||
|
object_from_column_builder<V> builder{res, stmt};
|
||||||
|
this->tableInfo.for_each_column(builder);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
const table_type& tableInfo;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <type_traits> // std::remove_const
|
||||||
|
|
||||||
|
#include "type_traits.h"
|
||||||
|
#include "alias_traits.h"
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If T is a recordset alias then the typename mapped_type_proxy<T>::type is the unqualified aliased type,
|
||||||
|
* otherwise unqualified T.
|
||||||
|
*/
|
||||||
|
template<class T, class SFINAE = void>
|
||||||
|
struct mapped_type_proxy : std::remove_const<T> {};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct mapped_type_proxy<T, match_if<is_recordset_alias, T>> : std::remove_const<type_t<T>> {};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
using mapped_type_proxy_t = typename mapped_type_proxy<T>::type;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <type_traits> // std::enable_if, std::is_function, std::true_type, std::false_type
|
||||||
|
|
||||||
|
#include "../functional/cxx_universal.h"
|
||||||
|
#include "../functional/cxx_type_traits_polyfill.h"
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
namespace internal {
|
||||||
|
// SFINAE friendly trait to get a member object pointer's field type
|
||||||
|
template<class T>
|
||||||
|
struct object_field_type {};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
using object_field_type_t = typename object_field_type<T>::type;
|
||||||
|
|
||||||
|
template<class F, class O>
|
||||||
|
struct object_field_type<F O::*> : std::enable_if<!std::is_function<F>::value, F> {};
|
||||||
|
|
||||||
|
// SFINAE friendly trait to get a member function pointer's field type (i.e. unqualified return type)
|
||||||
|
template<class T>
|
||||||
|
struct getter_field_type {};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
using getter_field_type_t = typename getter_field_type<T>::type;
|
||||||
|
|
||||||
|
template<class T, class O>
|
||||||
|
struct getter_field_type<T O::*> : getter_field_type<T> {};
|
||||||
|
|
||||||
|
template<class F>
|
||||||
|
struct getter_field_type<F(void) const> : polyfill::remove_cvref<F> {};
|
||||||
|
|
||||||
|
template<class F>
|
||||||
|
struct getter_field_type<F(void)> : polyfill::remove_cvref<F> {};
|
||||||
|
|
||||||
|
#ifdef SQLITE_ORM_NOTHROW_ALIASES_SUPPORTED
|
||||||
|
template<class F>
|
||||||
|
struct getter_field_type<F(void) const noexcept> : polyfill::remove_cvref<F> {};
|
||||||
|
|
||||||
|
template<class F>
|
||||||
|
struct getter_field_type<F(void) noexcept> : polyfill::remove_cvref<F> {};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// SFINAE friendly trait to get a member function pointer's field type (i.e. unqualified parameter type)
|
||||||
|
template<class T>
|
||||||
|
struct setter_field_type {};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
using setter_field_type_t = typename setter_field_type<T>::type;
|
||||||
|
|
||||||
|
template<class T, class O>
|
||||||
|
struct setter_field_type<T O::*> : setter_field_type<T> {};
|
||||||
|
|
||||||
|
template<class F>
|
||||||
|
struct setter_field_type<void(F)> : polyfill::remove_cvref<F> {};
|
||||||
|
|
||||||
|
#ifdef SQLITE_ORM_NOTHROW_ALIASES_SUPPORTED
|
||||||
|
template<class F>
|
||||||
|
struct setter_field_type<void(F) noexcept> : polyfill::remove_cvref<F> {};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
template<class T, class SFINAE = void>
|
||||||
|
struct is_getter : std::false_type {};
|
||||||
|
template<class T>
|
||||||
|
struct is_getter<T, polyfill::void_t<getter_field_type_t<T>>> : std::true_type {};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
SQLITE_ORM_INLINE_VAR constexpr bool is_getter_v = is_getter<T>::value;
|
||||||
|
|
||||||
|
template<class T, class SFINAE = void>
|
||||||
|
struct is_setter : std::false_type {};
|
||||||
|
template<class T>
|
||||||
|
struct is_setter<T, polyfill::void_t<setter_field_type_t<T>>> : std::true_type {};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
SQLITE_ORM_INLINE_VAR constexpr bool is_setter_v = is_setter<T>::value;
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct member_field_type : object_field_type<T>, getter_field_type<T>, setter_field_type<T> {};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
using member_field_type_t = typename member_field_type<T>::type;
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct member_object_type {};
|
||||||
|
|
||||||
|
template<class F, class O>
|
||||||
|
struct member_object_type<F O::*> : polyfill::type_identity<O> {};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
using member_object_type_t = typename member_object_type<T>::type;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,320 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <type_traits> // std::enable_if
|
||||||
|
#include <tuple> // std::tuple
|
||||||
|
#include <utility> // std::pair
|
||||||
|
#include <functional> // std::reference_wrapper
|
||||||
|
#include "functional/cxx_optional.h"
|
||||||
|
|
||||||
|
#include "functional/cxx_type_traits_polyfill.h"
|
||||||
|
#include "tuple_helper/tuple_filter.h"
|
||||||
|
#include "conditions.h"
|
||||||
|
#include "operators.h"
|
||||||
|
#include "select_constraints.h"
|
||||||
|
#include "prepared_statement.h"
|
||||||
|
#include "optional_container.h"
|
||||||
|
#include "core_functions.h"
|
||||||
|
#include "function.h"
|
||||||
|
#include "ast/excluded.h"
|
||||||
|
#include "ast/upsert_clause.h"
|
||||||
|
#include "ast/where.h"
|
||||||
|
#include "ast/into.h"
|
||||||
|
#include "ast/group_by.h"
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
template<class T, class SFINAE = void>
|
||||||
|
struct node_tuple {
|
||||||
|
using type = std::tuple<T>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
using node_tuple_t = typename node_tuple<T>::type;
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct node_tuple<void, void> {
|
||||||
|
using type = std::tuple<>;
|
||||||
|
};
|
||||||
|
#ifdef SQLITE_ORM_OPTIONAL_SUPPORTED
|
||||||
|
template<class T>
|
||||||
|
struct node_tuple<as_optional_t<T>, void> : node_tuple<T> {};
|
||||||
|
#endif // SQLITE_ORM_OPTIONAL_SUPPORTED
|
||||||
|
template<class T>
|
||||||
|
struct node_tuple<std::reference_wrapper<T>, void> : node_tuple<T> {};
|
||||||
|
|
||||||
|
template<class... Args>
|
||||||
|
struct node_tuple<group_by_t<Args...>, void> : node_tuple<std::tuple<Args...>> {};
|
||||||
|
|
||||||
|
template<class T, class... Args>
|
||||||
|
struct node_tuple<group_by_with_having<T, Args...>, void> {
|
||||||
|
using args_tuple = node_tuple_t<std::tuple<Args...>>;
|
||||||
|
using expression_tuple = node_tuple_t<T>;
|
||||||
|
using type = tuple_cat_t<args_tuple, expression_tuple>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct node_tuple<T, match_if<is_upsert_clause, T>> : node_tuple<typename T::actions_tuple> {};
|
||||||
|
|
||||||
|
template<class... Args>
|
||||||
|
struct node_tuple<set_t<Args...>, void> {
|
||||||
|
using type = tuple_cat_t<node_tuple_t<Args>...>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct node_tuple<excluded_t<T>, void> : node_tuple<T> {};
|
||||||
|
|
||||||
|
template<class C>
|
||||||
|
struct node_tuple<where_t<C>, void> : node_tuple<C> {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Column alias
|
||||||
|
*/
|
||||||
|
template<class A>
|
||||||
|
struct node_tuple<alias_holder<A>, void> : node_tuple<void> {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Column alias
|
||||||
|
*/
|
||||||
|
template<char... C>
|
||||||
|
struct node_tuple<column_alias<C...>, void> : node_tuple<void> {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Literal
|
||||||
|
*/
|
||||||
|
template<class T>
|
||||||
|
struct node_tuple<literal_holder<T>, void> : node_tuple<void> {};
|
||||||
|
|
||||||
|
template<class E>
|
||||||
|
struct node_tuple<order_by_t<E>, void> : node_tuple<E> {};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct node_tuple<T, match_if<is_binary_condition, T>> {
|
||||||
|
using node_type = T;
|
||||||
|
using left_type = typename node_type::left_type;
|
||||||
|
using right_type = typename node_type::right_type;
|
||||||
|
using left_node_tuple = node_tuple_t<left_type>;
|
||||||
|
using right_node_tuple = node_tuple_t<right_type>;
|
||||||
|
using type = tuple_cat_t<left_node_tuple, right_node_tuple>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class L, class R, class... Ds>
|
||||||
|
struct node_tuple<binary_operator<L, R, Ds...>, void> {
|
||||||
|
using node_type = binary_operator<L, R, Ds...>;
|
||||||
|
using left_type = typename node_type::left_type;
|
||||||
|
using right_type = typename node_type::right_type;
|
||||||
|
using left_node_tuple = node_tuple_t<left_type>;
|
||||||
|
using right_node_tuple = node_tuple_t<right_type>;
|
||||||
|
using type = tuple_cat_t<left_node_tuple, right_node_tuple>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class... Args>
|
||||||
|
struct node_tuple<columns_t<Args...>, void> {
|
||||||
|
using type = tuple_cat_t<node_tuple_t<Args>...>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class L, class A>
|
||||||
|
struct node_tuple<dynamic_in_t<L, A>, void> {
|
||||||
|
using left_tuple = node_tuple_t<L>;
|
||||||
|
using right_tuple = node_tuple_t<A>;
|
||||||
|
using type = tuple_cat_t<left_tuple, right_tuple>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class L, class... Args>
|
||||||
|
struct node_tuple<in_t<L, Args...>, void> {
|
||||||
|
using left_tuple = node_tuple_t<L>;
|
||||||
|
using right_tuple = tuple_cat_t<node_tuple_t<Args>...>;
|
||||||
|
using type = tuple_cat_t<left_tuple, right_tuple>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct node_tuple<T, match_if<is_compound_operator, T>> {
|
||||||
|
using node_type = T;
|
||||||
|
using left_type = typename node_type::left_type;
|
||||||
|
using right_type = typename node_type::right_type;
|
||||||
|
using left_tuple = node_tuple_t<left_type>;
|
||||||
|
using right_tuple = node_tuple_t<right_type>;
|
||||||
|
using type = tuple_cat_t<left_tuple, right_tuple>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T, class... Args>
|
||||||
|
struct node_tuple<select_t<T, Args...>, void> {
|
||||||
|
using columns_tuple = node_tuple_t<T>;
|
||||||
|
using args_tuple = tuple_cat_t<node_tuple_t<Args>...>;
|
||||||
|
using type = tuple_cat_t<columns_tuple, args_tuple>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class... Args>
|
||||||
|
struct node_tuple<insert_raw_t<Args...>, void> {
|
||||||
|
using type = tuple_cat_t<node_tuple_t<Args>...>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class... Args>
|
||||||
|
struct node_tuple<replace_raw_t<Args...>, void> {
|
||||||
|
using type = tuple_cat_t<node_tuple_t<Args>...>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct node_tuple<into_t<T>, void> : node_tuple<void> {};
|
||||||
|
|
||||||
|
template<class... Args>
|
||||||
|
struct node_tuple<values_t<Args...>, void> {
|
||||||
|
using type = tuple_cat_t<node_tuple_t<Args>...>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class... Args>
|
||||||
|
struct node_tuple<std::tuple<Args...>, void> {
|
||||||
|
using type = tuple_cat_t<node_tuple_t<Args>...>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T, class R, class... Args>
|
||||||
|
struct node_tuple<get_all_t<T, R, Args...>, void> {
|
||||||
|
using type = tuple_cat_t<node_tuple_t<Args>...>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T, class... Args>
|
||||||
|
struct node_tuple<get_all_pointer_t<T, Args...>, void> {
|
||||||
|
using type = tuple_cat_t<node_tuple_t<Args>...>;
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef SQLITE_ORM_OPTIONAL_SUPPORTED
|
||||||
|
template<class T, class... Args>
|
||||||
|
struct node_tuple<get_all_optional_t<T, Args...>, void> {
|
||||||
|
using type = tuple_cat_t<node_tuple_t<Args>...>;
|
||||||
|
};
|
||||||
|
#endif // SQLITE_ORM_OPTIONAL_SUPPORTED
|
||||||
|
|
||||||
|
template<class... Args, class... Wargs>
|
||||||
|
struct node_tuple<update_all_t<set_t<Args...>, Wargs...>, void> {
|
||||||
|
using set_tuple = tuple_cat_t<node_tuple_t<Args>...>;
|
||||||
|
using conditions_tuple = tuple_cat_t<node_tuple_t<Wargs>...>;
|
||||||
|
using type = tuple_cat_t<set_tuple, conditions_tuple>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T, class... Args>
|
||||||
|
struct node_tuple<remove_all_t<T, Args...>, void> {
|
||||||
|
using type = tuple_cat_t<node_tuple_t<Args>...>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct node_tuple<having_t<T>, void> : node_tuple<T> {};
|
||||||
|
|
||||||
|
template<class T, class E>
|
||||||
|
struct node_tuple<cast_t<T, E>, void> : node_tuple<E> {};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct node_tuple<exists_t<T>, void> : node_tuple<T> {};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct node_tuple<optional_container<T>, void> : node_tuple<T> {};
|
||||||
|
|
||||||
|
template<class A, class T, class E>
|
||||||
|
struct node_tuple<like_t<A, T, E>, void> {
|
||||||
|
using arg_tuple = node_tuple_t<A>;
|
||||||
|
using pattern_tuple = node_tuple_t<T>;
|
||||||
|
using escape_tuple = node_tuple_t<E>;
|
||||||
|
using type = tuple_cat_t<arg_tuple, pattern_tuple, escape_tuple>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class A, class T>
|
||||||
|
struct node_tuple<glob_t<A, T>, void> {
|
||||||
|
using arg_tuple = node_tuple_t<A>;
|
||||||
|
using pattern_tuple = node_tuple_t<T>;
|
||||||
|
using type = tuple_cat_t<arg_tuple, pattern_tuple>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class A, class T>
|
||||||
|
struct node_tuple<between_t<A, T>, void> {
|
||||||
|
using expression_tuple = node_tuple_t<A>;
|
||||||
|
using lower_tuple = node_tuple_t<T>;
|
||||||
|
using upper_tuple = node_tuple_t<T>;
|
||||||
|
using type = tuple_cat_t<expression_tuple, lower_tuple, upper_tuple>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct node_tuple<named_collate<T>, void> : node_tuple<T> {};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct node_tuple<is_null_t<T>, void> : node_tuple<T> {};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct node_tuple<is_not_null_t<T>, void> : node_tuple<T> {};
|
||||||
|
|
||||||
|
template<class C>
|
||||||
|
struct node_tuple<negated_condition_t<C>, void> : node_tuple<C> {};
|
||||||
|
|
||||||
|
template<class R, class S, class... Args>
|
||||||
|
struct node_tuple<built_in_function_t<R, S, Args...>, void> {
|
||||||
|
using type = tuple_cat_t<node_tuple_t<Args>...>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class R, class S, class... Args>
|
||||||
|
struct node_tuple<built_in_aggregate_function_t<R, S, Args...>, void> {
|
||||||
|
using type = tuple_cat_t<node_tuple_t<Args>...>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class F, class W>
|
||||||
|
struct node_tuple<filtered_aggregate_function<F, W>, void> {
|
||||||
|
using left_tuple = node_tuple_t<F>;
|
||||||
|
using right_tuple = node_tuple_t<W>;
|
||||||
|
using type = tuple_cat_t<left_tuple, right_tuple>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class F, class... Args>
|
||||||
|
struct node_tuple<function_call<F, Args...>, void> {
|
||||||
|
using type = tuple_cat_t<node_tuple_t<Args>...>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T, class O>
|
||||||
|
struct node_tuple<left_join_t<T, O>, void> : node_tuple<O> {};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct node_tuple<on_t<T>, void> : node_tuple<T> {};
|
||||||
|
|
||||||
|
// note: not strictly necessary as there's no binding support for USING;
|
||||||
|
// we provide it nevertheless, in line with on_t.
|
||||||
|
template<class T, class M>
|
||||||
|
struct node_tuple<using_t<T, M>, void> : node_tuple<column_pointer<T, M>> {};
|
||||||
|
|
||||||
|
template<class T, class O>
|
||||||
|
struct node_tuple<join_t<T, O>, void> : node_tuple<O> {};
|
||||||
|
|
||||||
|
template<class T, class O>
|
||||||
|
struct node_tuple<left_outer_join_t<T, O>, void> : node_tuple<O> {};
|
||||||
|
|
||||||
|
template<class T, class O>
|
||||||
|
struct node_tuple<inner_join_t<T, O>, void> : node_tuple<O> {};
|
||||||
|
|
||||||
|
template<class R, class T, class E, class... Args>
|
||||||
|
struct node_tuple<simple_case_t<R, T, E, Args...>, void> {
|
||||||
|
using case_tuple = node_tuple_t<T>;
|
||||||
|
using args_tuple = tuple_cat_t<node_tuple_t<Args>...>;
|
||||||
|
using else_tuple = node_tuple_t<E>;
|
||||||
|
using type = tuple_cat_t<case_tuple, args_tuple, else_tuple>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class L, class R>
|
||||||
|
struct node_tuple<std::pair<L, R>, void> {
|
||||||
|
using left_tuple = node_tuple_t<L>;
|
||||||
|
using right_tuple = node_tuple_t<R>;
|
||||||
|
using type = tuple_cat_t<left_tuple, right_tuple>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T, class E>
|
||||||
|
struct node_tuple<as_t<T, E>, void> : node_tuple<E> {};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct node_tuple<limit_t<T, false, false, void>, void> : node_tuple<T> {};
|
||||||
|
|
||||||
|
template<class T, class O>
|
||||||
|
struct node_tuple<limit_t<T, true, false, O>, void> {
|
||||||
|
using type = tuple_cat_t<node_tuple_t<T>, node_tuple_t<O>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T, class O>
|
||||||
|
struct node_tuple<limit_t<T, true, true, O>, void> {
|
||||||
|
using type = tuple_cat_t<node_tuple_t<O>, node_tuple_t<T>>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <sqlite3.h>
|
||||||
|
#include <type_traits> // std::is_member_object_pointer
|
||||||
|
|
||||||
|
#include "functional/static_magic.h"
|
||||||
|
#include "row_extractor.h"
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
struct object_from_column_builder_base {
|
||||||
|
sqlite3_stmt* stmt = nullptr;
|
||||||
|
int index = 0;
|
||||||
|
|
||||||
|
#ifndef SQLITE_ORM_AGGREGATE_NSDMI_SUPPORTED
|
||||||
|
object_from_column_builder_base(sqlite3_stmt* stmt) : stmt{stmt} {}
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a cute lambda replacement which is used in several places.
|
||||||
|
*/
|
||||||
|
template<class O>
|
||||||
|
struct object_from_column_builder : object_from_column_builder_base {
|
||||||
|
using object_type = O;
|
||||||
|
|
||||||
|
object_type& object;
|
||||||
|
|
||||||
|
object_from_column_builder(object_type& object_, sqlite3_stmt* stmt_) :
|
||||||
|
object_from_column_builder_base{stmt_}, object(object_) {}
|
||||||
|
|
||||||
|
template<class G, class S>
|
||||||
|
void operator()(const column_field<G, S>& column) {
|
||||||
|
auto value = row_extractor<member_field_type_t<G>>().extract(this->stmt, this->index++);
|
||||||
|
static_if<std::is_member_object_pointer<G>::value>(
|
||||||
|
[&value, &object = this->object](const auto& column) {
|
||||||
|
object.*column.member_pointer = std::move(value);
|
||||||
|
},
|
||||||
|
[&value, &object = this->object](const auto& column) {
|
||||||
|
(object.*column.setter)(std::move(value));
|
||||||
|
})(column);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,278 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <type_traits> // std::false_type, std::true_type
|
||||||
|
#include <utility> // std::move
|
||||||
|
#include "functional/cxx_optional.h"
|
||||||
|
|
||||||
|
#include "tags.h"
|
||||||
|
#include "serialize_result_type.h"
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inherit this class to support arithmetic types overloading
|
||||||
|
*/
|
||||||
|
struct arithmetic_t {};
|
||||||
|
|
||||||
|
template<class L, class R, class... Ds>
|
||||||
|
struct binary_operator : Ds... {
|
||||||
|
using left_type = L;
|
||||||
|
using right_type = R;
|
||||||
|
|
||||||
|
left_type lhs;
|
||||||
|
right_type rhs;
|
||||||
|
|
||||||
|
binary_operator(left_type lhs_, right_type rhs_) : lhs(std::move(lhs_)), rhs(std::move(rhs_)) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct conc_string {
|
||||||
|
serialize_result_type serialize() const {
|
||||||
|
return "||";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Result of concatenation || operator
|
||||||
|
*/
|
||||||
|
template<class L, class R>
|
||||||
|
using conc_t = binary_operator<L, R, conc_string>;
|
||||||
|
|
||||||
|
struct add_string {
|
||||||
|
serialize_result_type serialize() const {
|
||||||
|
return "+";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Result of addition + operator
|
||||||
|
*/
|
||||||
|
template<class L, class R>
|
||||||
|
using add_t = binary_operator<L, R, add_string, arithmetic_t, negatable_t>;
|
||||||
|
|
||||||
|
struct sub_string {
|
||||||
|
serialize_result_type serialize() const {
|
||||||
|
return "-";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Result of substitute - operator
|
||||||
|
*/
|
||||||
|
template<class L, class R>
|
||||||
|
using sub_t = binary_operator<L, R, sub_string, arithmetic_t, negatable_t>;
|
||||||
|
|
||||||
|
struct mul_string {
|
||||||
|
serialize_result_type serialize() const {
|
||||||
|
return "*";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Result of multiply * operator
|
||||||
|
*/
|
||||||
|
template<class L, class R>
|
||||||
|
using mul_t = binary_operator<L, R, mul_string, arithmetic_t, negatable_t>;
|
||||||
|
|
||||||
|
struct div_string {
|
||||||
|
serialize_result_type serialize() const {
|
||||||
|
return "/";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Result of divide / operator
|
||||||
|
*/
|
||||||
|
template<class L, class R>
|
||||||
|
using div_t = binary_operator<L, R, div_string, arithmetic_t, negatable_t>;
|
||||||
|
|
||||||
|
struct mod_operator_string {
|
||||||
|
serialize_result_type serialize() const {
|
||||||
|
return "%";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Result of mod % operator
|
||||||
|
*/
|
||||||
|
template<class L, class R>
|
||||||
|
using mod_t = binary_operator<L, R, mod_operator_string, arithmetic_t, negatable_t>;
|
||||||
|
|
||||||
|
struct bitwise_shift_left_string {
|
||||||
|
serialize_result_type serialize() const {
|
||||||
|
return "<<";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Result of bitwise shift left << operator
|
||||||
|
*/
|
||||||
|
template<class L, class R>
|
||||||
|
using bitwise_shift_left_t = binary_operator<L, R, bitwise_shift_left_string, arithmetic_t, negatable_t>;
|
||||||
|
|
||||||
|
struct bitwise_shift_right_string {
|
||||||
|
serialize_result_type serialize() const {
|
||||||
|
return ">>";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Result of bitwise shift right >> operator
|
||||||
|
*/
|
||||||
|
template<class L, class R>
|
||||||
|
using bitwise_shift_right_t = binary_operator<L, R, bitwise_shift_right_string, arithmetic_t, negatable_t>;
|
||||||
|
|
||||||
|
struct bitwise_and_string {
|
||||||
|
serialize_result_type serialize() const {
|
||||||
|
return "&";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Result of bitwise and & operator
|
||||||
|
*/
|
||||||
|
template<class L, class R>
|
||||||
|
using bitwise_and_t = binary_operator<L, R, bitwise_and_string, arithmetic_t, negatable_t>;
|
||||||
|
|
||||||
|
struct bitwise_or_string {
|
||||||
|
serialize_result_type serialize() const {
|
||||||
|
return "|";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Result of bitwise or | operator
|
||||||
|
*/
|
||||||
|
template<class L, class R>
|
||||||
|
using bitwise_or_t = binary_operator<L, R, bitwise_or_string, arithmetic_t, negatable_t>;
|
||||||
|
|
||||||
|
struct bitwise_not_string {
|
||||||
|
serialize_result_type serialize() const {
|
||||||
|
return "~";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Result of bitwise not ~ operator
|
||||||
|
*/
|
||||||
|
template<class T>
|
||||||
|
struct bitwise_not_t : bitwise_not_string, arithmetic_t, negatable_t {
|
||||||
|
using argument_type = T;
|
||||||
|
|
||||||
|
argument_type argument;
|
||||||
|
|
||||||
|
bitwise_not_t(argument_type argument_) : argument(std::move(argument_)) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct assign_string {
|
||||||
|
serialize_result_type serialize() const {
|
||||||
|
return "=";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Result of assign = operator
|
||||||
|
*/
|
||||||
|
template<class L, class R>
|
||||||
|
using assign_t = binary_operator<L, R, assign_string>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assign operator traits. Common case
|
||||||
|
*/
|
||||||
|
template<class T>
|
||||||
|
struct is_assign_t : public std::false_type {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assign operator traits. Specialized case
|
||||||
|
*/
|
||||||
|
template<class L, class R>
|
||||||
|
struct is_assign_t<assign_t<L, R>> : public std::true_type {};
|
||||||
|
|
||||||
|
template<class L, class... Args>
|
||||||
|
struct in_t;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public interface for || concatenation operator. Example: `select(conc(&User::name, "@gmail.com"));` => SELECT
|
||||||
|
* name || '@gmail.com' FROM users
|
||||||
|
*/
|
||||||
|
template<class L, class R>
|
||||||
|
internal::conc_t<L, R> conc(L l, R r) {
|
||||||
|
return {std::move(l), std::move(r)};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public interface for + operator. Example: `select(add(&User::age, 100));` => SELECT age + 100 FROM users
|
||||||
|
*/
|
||||||
|
template<class L, class R>
|
||||||
|
internal::add_t<L, R> add(L l, R r) {
|
||||||
|
return {std::move(l), std::move(r)};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public interface for - operator. Example: `select(sub(&User::age, 1));` => SELECT age - 1 FROM users
|
||||||
|
*/
|
||||||
|
template<class L, class R>
|
||||||
|
internal::sub_t<L, R> sub(L l, R r) {
|
||||||
|
return {std::move(l), std::move(r)};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public interface for * operator. Example: `select(mul(&User::salary, 2));` => SELECT salary * 2 FROM users
|
||||||
|
*/
|
||||||
|
template<class L, class R>
|
||||||
|
internal::mul_t<L, R> mul(L l, R r) {
|
||||||
|
return {std::move(l), std::move(r)};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public interface for / operator. Example: `select(div(&User::salary, 3));` => SELECT salary / 3 FROM users
|
||||||
|
* @note Please notice that ::div function already exists in pure C standard library inside <cstdlib> header.
|
||||||
|
* If you use `using namespace sqlite_orm` directive you an specify which `div` you call explicitly using `::div` or `sqlite_orm::div` statements.
|
||||||
|
*/
|
||||||
|
template<class L, class R>
|
||||||
|
internal::div_t<L, R> div(L l, R r) {
|
||||||
|
return {std::move(l), std::move(r)};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public interface for % operator. Example: `select(mod(&User::age, 5));` => SELECT age % 5 FROM users
|
||||||
|
*/
|
||||||
|
template<class L, class R>
|
||||||
|
internal::mod_t<L, R> mod(L l, R r) {
|
||||||
|
return {std::move(l), std::move(r)};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class L, class R>
|
||||||
|
internal::bitwise_shift_left_t<L, R> bitwise_shift_left(L l, R r) {
|
||||||
|
return {std::move(l), std::move(r)};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class L, class R>
|
||||||
|
internal::bitwise_shift_right_t<L, R> bitwise_shift_right(L l, R r) {
|
||||||
|
return {std::move(l), std::move(r)};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class L, class R>
|
||||||
|
internal::bitwise_and_t<L, R> bitwise_and(L l, R r) {
|
||||||
|
return {std::move(l), std::move(r)};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class L, class R>
|
||||||
|
internal::bitwise_or_t<L, R> bitwise_or(L l, R r) {
|
||||||
|
return {std::move(l), std::move(r)};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
internal::bitwise_not_t<T> bitwise_not(T t) {
|
||||||
|
return {std::move(t)};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class L, class R>
|
||||||
|
internal::assign_t<L, R> assign(L l, R r) {
|
||||||
|
return {std::move(l), std::move(r)};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a cute class which allows storing something or nothing
|
||||||
|
* depending on template argument. Useful for optional class members
|
||||||
|
*/
|
||||||
|
template<class T>
|
||||||
|
struct optional_container {
|
||||||
|
using type = T;
|
||||||
|
|
||||||
|
type field;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void apply(const L& l) const {
|
||||||
|
l(this->field);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct optional_container<void> {
|
||||||
|
using type = void;
|
||||||
|
|
||||||
|
template<class L>
|
||||||
|
void apply(const L&) const {
|
||||||
|
//..
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string> // std::string
|
||||||
|
#include <sstream> // std::stringstream
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
template<class T, class SFINAE = void>
|
||||||
|
struct order_by_serializer;
|
||||||
|
|
||||||
|
template<class T, class Ctx>
|
||||||
|
std::string serialize_order_by(const T& t, const Ctx& context) {
|
||||||
|
order_by_serializer<T> serializer;
|
||||||
|
return serializer(t, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class O>
|
||||||
|
struct order_by_serializer<order_by_t<O>, void> {
|
||||||
|
using statement_type = order_by_t<O>;
|
||||||
|
|
||||||
|
template<class Ctx>
|
||||||
|
std::string operator()(const statement_type& orderBy, const Ctx& context) const {
|
||||||
|
std::stringstream ss;
|
||||||
|
auto newContext = context;
|
||||||
|
newContext.skip_table_name = false;
|
||||||
|
|
||||||
|
ss << serialize(orderBy.expression, newContext);
|
||||||
|
if(!orderBy._collate_argument.empty()) {
|
||||||
|
ss << " COLLATE " << orderBy._collate_argument;
|
||||||
|
}
|
||||||
|
switch(orderBy.asc_desc) {
|
||||||
|
case 1:
|
||||||
|
ss << " ASC";
|
||||||
|
break;
|
||||||
|
case -1:
|
||||||
|
ss << " DESC";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class C>
|
||||||
|
struct order_by_serializer<dynamic_order_by_t<C>, void> {
|
||||||
|
using statement_type = dynamic_order_by_t<C>;
|
||||||
|
|
||||||
|
template<class Ctx>
|
||||||
|
std::string operator()(const statement_type& orderBy, const Ctx&) const {
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << static_cast<std::string>(orderBy) << " ";
|
||||||
|
int index = 0;
|
||||||
|
for(const dynamic_order_by_entry_t& entry: orderBy) {
|
||||||
|
if(index > 0) {
|
||||||
|
ss << ", ";
|
||||||
|
}
|
||||||
|
|
||||||
|
ss << entry.name;
|
||||||
|
if(!entry._collate_argument.empty()) {
|
||||||
|
ss << " COLLATE " << entry._collate_argument;
|
||||||
|
}
|
||||||
|
switch(entry.asc_desc) {
|
||||||
|
case 1:
|
||||||
|
ss << " ASC";
|
||||||
|
break;
|
||||||
|
case -1:
|
||||||
|
ss << " DESC";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
++index;
|
||||||
|
};
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,173 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <type_traits>
|
||||||
|
#include <memory>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "functional/cxx_universal.h"
|
||||||
|
#include "xdestroy_handling.h"
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps a pointer and tags it with a pointer type,
|
||||||
|
* used for accepting function parameters,
|
||||||
|
* facilitating the 'pointer-passing interface'.
|
||||||
|
*
|
||||||
|
* Template parameters:
|
||||||
|
* - P: The value type, possibly const-qualified.
|
||||||
|
* - T: An integral constant string denoting the pointer type, e.g. `carray_pvt_name`.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
template<typename P, typename T>
|
||||||
|
struct pointer_arg {
|
||||||
|
|
||||||
|
static_assert(std::is_convertible<typename T::value_type, const char*>::value,
|
||||||
|
"`std::integral_constant<>` must be convertible to `const char*`");
|
||||||
|
|
||||||
|
using tag = T;
|
||||||
|
P* p_;
|
||||||
|
|
||||||
|
P* ptr() const noexcept {
|
||||||
|
return p_;
|
||||||
|
}
|
||||||
|
|
||||||
|
operator P*() const noexcept {
|
||||||
|
return p_;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pointer value with associated deleter function,
|
||||||
|
* used for returning or binding pointer values
|
||||||
|
* as part of facilitating the 'pointer-passing interface'.
|
||||||
|
*
|
||||||
|
* Template parameters:
|
||||||
|
* - D: The deleter for the pointer value;
|
||||||
|
* can be one of:
|
||||||
|
* - function pointer
|
||||||
|
* - integral function pointer constant
|
||||||
|
* - state-less (empty) deleter
|
||||||
|
* - non-capturing lambda
|
||||||
|
* - structure implicitly yielding a function pointer
|
||||||
|
*
|
||||||
|
* @note Use one of the factory functions to create a pointer binding,
|
||||||
|
* e.g. bindable_carray_pointer or statically_bindable_carray_pointer().
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```
|
||||||
|
* int64 rememberedId;
|
||||||
|
* storage.select(func<remember_fn>(&Object::id, statically_bindable_carray_pointer(&rememberedId)));
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
template<typename P, typename T, typename D>
|
||||||
|
class pointer_binding {
|
||||||
|
|
||||||
|
P* p_;
|
||||||
|
SQLITE_ORM_NOUNIQUEADDRESS
|
||||||
|
D d_;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// Constructing pointer bindings must go through bindable_pointer()
|
||||||
|
template<class T2, class P2, class D2>
|
||||||
|
friend auto bindable_pointer(P2*, D2) noexcept -> pointer_binding<P2, T2, D2>;
|
||||||
|
template<class B>
|
||||||
|
friend B bindable_pointer(typename B::qualified_type*, typename B::deleter_type) noexcept;
|
||||||
|
|
||||||
|
// Construct from pointer and deleter.
|
||||||
|
// Transfers ownership of the passed in object.
|
||||||
|
pointer_binding(P* p, D d = {}) noexcept : p_{p}, d_{std::move(d)} {}
|
||||||
|
|
||||||
|
public:
|
||||||
|
using qualified_type = P;
|
||||||
|
using tag = T;
|
||||||
|
using deleter_type = D;
|
||||||
|
|
||||||
|
pointer_binding(const pointer_binding&) = delete;
|
||||||
|
pointer_binding& operator=(const pointer_binding&) = delete;
|
||||||
|
pointer_binding& operator=(pointer_binding&&) = delete;
|
||||||
|
|
||||||
|
pointer_binding(pointer_binding&& other) noexcept :
|
||||||
|
p_{std::exchange(other.p_, nullptr)}, d_{std::move(other.d_)} {}
|
||||||
|
|
||||||
|
~pointer_binding() {
|
||||||
|
if(p_) {
|
||||||
|
if(auto xDestroy = get_xdestroy()) {
|
||||||
|
// note: C-casting `P* -> void*` like statement_binder<pointer_binding<P, T, D>>
|
||||||
|
xDestroy((void*)p_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
P* ptr() const noexcept {
|
||||||
|
return p_;
|
||||||
|
}
|
||||||
|
|
||||||
|
P* take_ptr() noexcept {
|
||||||
|
return std::exchange(p_, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
xdestroy_fn_t get_xdestroy() const noexcept {
|
||||||
|
return obtain_xdestroy_for(d_, p_);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Template alias for a static pointer value binding.
|
||||||
|
* 'Static' means that ownership won't be transferred to sqlite,
|
||||||
|
* sqlite doesn't delete it, and sqlite assumes the object
|
||||||
|
* pointed to is valid throughout the lifetime of a statement.
|
||||||
|
*/
|
||||||
|
template<typename P, typename T>
|
||||||
|
using static_pointer_binding = pointer_binding<P, T, null_xdestroy_t>;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap a pointer, its type and its deleter function for binding it to a statement.
|
||||||
|
*
|
||||||
|
* Unless the deleter yields a nullptr 'xDestroy' function the ownership of the pointed-to-object
|
||||||
|
* is transferred to the pointer binding, which will delete it through
|
||||||
|
* the deleter when the statement finishes.
|
||||||
|
*/
|
||||||
|
template<class T, class P, class D>
|
||||||
|
auto bindable_pointer(P* p, D d) noexcept -> pointer_binding<P, T, D> {
|
||||||
|
return {p, std::move(d)};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T, class P, class D>
|
||||||
|
auto bindable_pointer(std::unique_ptr<P, D> p) noexcept -> pointer_binding<P, T, D> {
|
||||||
|
return bindable_pointer<T>(p.release(), p.get_deleter());
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename B>
|
||||||
|
B bindable_pointer(typename B::qualified_type* p, typename B::deleter_type d = {}) noexcept {
|
||||||
|
return B{p, std::move(d)};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap a pointer and its type for binding it to a statement.
|
||||||
|
*
|
||||||
|
* Note: 'Static' means that ownership of the pointed-to-object won't be transferred
|
||||||
|
* and sqlite assumes the object pointed to is valid throughout the lifetime of a statement.
|
||||||
|
*/
|
||||||
|
template<class T, class P>
|
||||||
|
auto statically_bindable_pointer(P* p) noexcept -> static_pointer_binding<P, T> {
|
||||||
|
return bindable_pointer<T>(p, null_xdestroy_f);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename B>
|
||||||
|
B statically_bindable_pointer(typename B::qualified_type* p,
|
||||||
|
typename B::deleter_type* /*exposition*/ = nullptr) noexcept {
|
||||||
|
return bindable_pointer<B>(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forward a pointer value from an argument.
|
||||||
|
*/
|
||||||
|
template<class P, class T>
|
||||||
|
auto rebind_statically(const pointer_arg<P, T>& pv) noexcept -> static_pointer_binding<P, T> {
|
||||||
|
return statically_bindable_pointer<T>(pv.ptr());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,227 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <sqlite3.h>
|
||||||
|
#include <string> // std::string
|
||||||
|
#include <functional> // std::function
|
||||||
|
#include <memory> // std::shared_ptr
|
||||||
|
#include <vector> // std::vector
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
#include "error_code.h"
|
||||||
|
#include "row_extractor.h"
|
||||||
|
#include "journal_mode.h"
|
||||||
|
#include "connection_holder.h"
|
||||||
|
#include "util.h"
|
||||||
|
#include "serializing_util.h"
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
struct storage_base;
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
int getPragmaCallback(void* data, int argc, char** argv, char** x) {
|
||||||
|
return extract_single_value<T>(data, argc, argv, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
inline int getPragmaCallback<std::vector<std::string>>(void* data, int argc, char** argv, char**) {
|
||||||
|
auto& res = *(std::vector<std::string>*)data;
|
||||||
|
res.reserve(argc);
|
||||||
|
for(decltype(argc) i = 0; i < argc; ++i) {
|
||||||
|
auto rowString = row_extractor<std::string>().extract(argv[i]);
|
||||||
|
res.push_back(std::move(rowString));
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct pragma_t {
|
||||||
|
using get_connection_t = std::function<internal::connection_ref()>;
|
||||||
|
|
||||||
|
pragma_t(get_connection_t get_connection_) : get_connection(std::move(get_connection_)) {}
|
||||||
|
|
||||||
|
void busy_timeout(int value) {
|
||||||
|
this->set_pragma("busy_timeout", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
int busy_timeout() {
|
||||||
|
return this->get_pragma<int>("busy_timeout");
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlite_orm::journal_mode journal_mode() {
|
||||||
|
return this->get_pragma<sqlite_orm::journal_mode>("journal_mode");
|
||||||
|
}
|
||||||
|
|
||||||
|
void journal_mode(sqlite_orm::journal_mode value) {
|
||||||
|
this->_journal_mode = -1;
|
||||||
|
this->set_pragma("journal_mode", value);
|
||||||
|
this->_journal_mode = static_cast<decltype(this->_journal_mode)>(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://www.sqlite.org/pragma.html#pragma_application_id
|
||||||
|
*/
|
||||||
|
int application_id() {
|
||||||
|
return this->get_pragma<int>("application_id");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://www.sqlite.org/pragma.html#pragma_application_id
|
||||||
|
*/
|
||||||
|
void application_id(int value) {
|
||||||
|
this->set_pragma("application_id", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
int synchronous() {
|
||||||
|
return this->get_pragma<int>("synchronous");
|
||||||
|
}
|
||||||
|
|
||||||
|
void synchronous(int value) {
|
||||||
|
this->_synchronous = -1;
|
||||||
|
this->set_pragma("synchronous", value);
|
||||||
|
this->_synchronous = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
int user_version() {
|
||||||
|
return this->get_pragma<int>("user_version");
|
||||||
|
}
|
||||||
|
|
||||||
|
void user_version(int value) {
|
||||||
|
this->set_pragma("user_version", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
int auto_vacuum() {
|
||||||
|
return this->get_pragma<int>("auto_vacuum");
|
||||||
|
}
|
||||||
|
|
||||||
|
void auto_vacuum(int value) {
|
||||||
|
this->set_pragma("auto_vacuum", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> integrity_check() {
|
||||||
|
return this->get_pragma<std::vector<std::string>>("integrity_check");
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
std::vector<std::string> integrity_check(T table_name) {
|
||||||
|
std::ostringstream ss;
|
||||||
|
ss << "integrity_check(" << table_name << ")" << std::flush;
|
||||||
|
return this->get_pragma<std::vector<std::string>>(ss.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> integrity_check(int n) {
|
||||||
|
std::ostringstream ss;
|
||||||
|
ss << "integrity_check(" << n << ")" << std::flush;
|
||||||
|
return this->get_pragma<std::vector<std::string>>(ss.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
// will include generated columns in response as opposed to table_info
|
||||||
|
std::vector<sqlite_orm::table_xinfo> table_xinfo(const std::string& tableName) const {
|
||||||
|
auto connection = this->get_connection();
|
||||||
|
|
||||||
|
std::vector<sqlite_orm::table_xinfo> result;
|
||||||
|
std::ostringstream ss;
|
||||||
|
ss << "PRAGMA "
|
||||||
|
"table_xinfo("
|
||||||
|
<< streaming_identifier(tableName) << ")" << std::flush;
|
||||||
|
perform_exec(
|
||||||
|
connection.get(),
|
||||||
|
ss.str(),
|
||||||
|
[](void* data, int argc, char** argv, char**) -> int {
|
||||||
|
auto& res = *(std::vector<sqlite_orm::table_xinfo>*)data;
|
||||||
|
if(argc) {
|
||||||
|
auto index = 0;
|
||||||
|
auto cid = std::atoi(argv[index++]);
|
||||||
|
std::string name = argv[index++];
|
||||||
|
std::string type = argv[index++];
|
||||||
|
bool notnull = !!std::atoi(argv[index++]);
|
||||||
|
std::string dflt_value = argv[index] ? argv[index] : "";
|
||||||
|
++index;
|
||||||
|
auto pk = std::atoi(argv[index++]);
|
||||||
|
auto hidden = std::atoi(argv[index++]);
|
||||||
|
res.emplace_back(cid,
|
||||||
|
std::move(name),
|
||||||
|
std::move(type),
|
||||||
|
notnull,
|
||||||
|
std::move(dflt_value),
|
||||||
|
pk,
|
||||||
|
hidden);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
&result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<sqlite_orm::table_info> table_info(const std::string& tableName) const {
|
||||||
|
auto connection = this->get_connection();
|
||||||
|
|
||||||
|
std::ostringstream ss;
|
||||||
|
ss << "PRAGMA "
|
||||||
|
"table_info("
|
||||||
|
<< streaming_identifier(tableName) << ")" << std::flush;
|
||||||
|
std::vector<sqlite_orm::table_info> result;
|
||||||
|
perform_exec(
|
||||||
|
connection.get(),
|
||||||
|
ss.str(),
|
||||||
|
[](void* data, int argc, char** argv, char**) -> int {
|
||||||
|
auto& res = *(std::vector<sqlite_orm::table_info>*)data;
|
||||||
|
if(argc) {
|
||||||
|
auto index = 0;
|
||||||
|
auto cid = std::atoi(argv[index++]);
|
||||||
|
std::string name = argv[index++];
|
||||||
|
std::string type = argv[index++];
|
||||||
|
bool notnull = !!std::atoi(argv[index++]);
|
||||||
|
std::string dflt_value = argv[index] ? argv[index] : "";
|
||||||
|
++index;
|
||||||
|
auto pk = std::atoi(argv[index++]);
|
||||||
|
res.emplace_back(cid, std::move(name), std::move(type), notnull, std::move(dflt_value), pk);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
&result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend struct storage_base;
|
||||||
|
|
||||||
|
int _synchronous = -1;
|
||||||
|
signed char _journal_mode = -1; // if != -1 stores static_cast<sqlite_orm::journal_mode>(journal_mode)
|
||||||
|
get_connection_t get_connection;
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
T get_pragma(const std::string& name) {
|
||||||
|
auto connection = this->get_connection();
|
||||||
|
T result;
|
||||||
|
perform_exec(connection.get(), "PRAGMA " + name, getPragmaCallback<T>, &result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Yevgeniy Zakharov: I wanted to refactor this function with statements and value bindings
|
||||||
|
* but it turns out that bindings in pragma statements are not supported.
|
||||||
|
*/
|
||||||
|
template<class T>
|
||||||
|
void set_pragma(const std::string& name, const T& value, sqlite3* db = nullptr) {
|
||||||
|
auto con = this->get_connection();
|
||||||
|
if(!db) {
|
||||||
|
db = con.get();
|
||||||
|
}
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "PRAGMA " << name << " = " << value << std::flush;
|
||||||
|
perform_void_exec(db, ss.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_pragma(const std::string& name, const sqlite_orm::journal_mode& value, sqlite3* db = nullptr) {
|
||||||
|
auto con = this->get_connection();
|
||||||
|
if(!db) {
|
||||||
|
db = con.get();
|
||||||
|
}
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "PRAGMA " << name << " = " << to_string(value) << std::flush;
|
||||||
|
perform_void_exec(db, ss.str());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,753 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <sqlite3.h>
|
||||||
|
#include <memory> // std::unique_ptr
|
||||||
|
#include <iterator> // std::iterator_traits
|
||||||
|
#include <string> // std::string
|
||||||
|
#include <type_traits> // std::integral_constant, std::declval
|
||||||
|
#include <utility> // std::pair
|
||||||
|
|
||||||
|
#include "functional/cxx_universal.h"
|
||||||
|
#include "functional/cxx_type_traits_polyfill.h"
|
||||||
|
#include "functional/cxx_functional_polyfill.h"
|
||||||
|
#include "tuple_helper/tuple_filter.h"
|
||||||
|
#include "connection_holder.h"
|
||||||
|
#include "select_constraints.h"
|
||||||
|
#include "values.h"
|
||||||
|
#include "ast/upsert_clause.h"
|
||||||
|
#include "ast/set.h"
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
struct prepared_statement_base {
|
||||||
|
sqlite3_stmt* stmt = nullptr;
|
||||||
|
connection_ref con;
|
||||||
|
|
||||||
|
#ifndef SQLITE_ORM_AGGREGATE_NSDMI_SUPPORTED
|
||||||
|
prepared_statement_base(sqlite3_stmt* stmt, connection_ref con) : stmt{stmt}, con{std::move(con)} {}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
~prepared_statement_base() {
|
||||||
|
sqlite3_finalize(this->stmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string sql() const {
|
||||||
|
// note: sqlite3 internally checks for null before calling
|
||||||
|
// sqlite3_normalized_sql() or sqlite3_expanded_sql(), so check here, too, even if superfluous
|
||||||
|
if(const char* sql = sqlite3_sql(this->stmt)) {
|
||||||
|
return sql;
|
||||||
|
} else {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if SQLITE_VERSION_NUMBER >= 3014000
|
||||||
|
std::string expanded_sql() const {
|
||||||
|
// note: must check return value due to SQLITE_OMIT_TRACE
|
||||||
|
using char_ptr = std::unique_ptr<char, std::integral_constant<decltype(&sqlite3_free), sqlite3_free>>;
|
||||||
|
if(char_ptr sql{sqlite3_expanded_sql(this->stmt)}) {
|
||||||
|
return sql.get();
|
||||||
|
} else {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#if SQLITE_VERSION_NUMBER >= 3026000 and defined(SQLITE_ENABLE_NORMALIZE)
|
||||||
|
std::string normalized_sql() const {
|
||||||
|
if(const char* sql = sqlite3_normalized_sql(this->stmt)) {
|
||||||
|
return sql;
|
||||||
|
} else {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef SQLITE_ORM_STRING_VIEW_SUPPORTED
|
||||||
|
std::string_view column_name(int index) const {
|
||||||
|
return sqlite3_column_name(stmt, index);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct prepared_statement_t : prepared_statement_base {
|
||||||
|
using expression_type = T;
|
||||||
|
|
||||||
|
expression_type expression;
|
||||||
|
|
||||||
|
prepared_statement_t(T expression_, sqlite3_stmt* stmt_, connection_ref con_) :
|
||||||
|
prepared_statement_base{stmt_, std::move(con_)}, expression(std::move(expression_)) {}
|
||||||
|
|
||||||
|
prepared_statement_t(prepared_statement_t&& prepared_stmt) :
|
||||||
|
prepared_statement_base{prepared_stmt.stmt, std::move(prepared_stmt.con)},
|
||||||
|
expression(std::move(prepared_stmt.expression)) {
|
||||||
|
prepared_stmt.stmt = nullptr;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
SQLITE_ORM_INLINE_VAR constexpr bool is_prepared_statement_v =
|
||||||
|
polyfill::is_specialization_of_v<T, prepared_statement_t>;
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
using is_prepared_statement = polyfill::bool_constant<is_prepared_statement_v<T>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* T - type of object to obtain from a database
|
||||||
|
*/
|
||||||
|
template<class T, class R, class... Args>
|
||||||
|
struct get_all_t {
|
||||||
|
using type = T;
|
||||||
|
using return_type = R;
|
||||||
|
|
||||||
|
using conditions_type = std::tuple<Args...>;
|
||||||
|
|
||||||
|
conditions_type conditions;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T, class R, class... Args>
|
||||||
|
struct get_all_pointer_t {
|
||||||
|
using type = T;
|
||||||
|
using return_type = R;
|
||||||
|
|
||||||
|
using conditions_type = std::tuple<Args...>;
|
||||||
|
|
||||||
|
conditions_type conditions;
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef SQLITE_ORM_OPTIONAL_SUPPORTED
|
||||||
|
template<class T, class R, class... Args>
|
||||||
|
struct get_all_optional_t {
|
||||||
|
using type = T;
|
||||||
|
using return_type = R;
|
||||||
|
|
||||||
|
using conditions_type = std::tuple<Args...>;
|
||||||
|
|
||||||
|
conditions_type conditions;
|
||||||
|
};
|
||||||
|
#endif // SQLITE_ORM_OPTIONAL_SUPPORTED
|
||||||
|
|
||||||
|
template<class S, class... Wargs>
|
||||||
|
struct update_all_t {
|
||||||
|
using set_type = S;
|
||||||
|
using conditions_type = std::tuple<Wargs...>;
|
||||||
|
|
||||||
|
static_assert(is_set<S>::value, "update_all_t must have set or dynamic set as the first argument");
|
||||||
|
|
||||||
|
set_type set;
|
||||||
|
conditions_type conditions;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T, class... Args>
|
||||||
|
struct remove_all_t {
|
||||||
|
using type = T;
|
||||||
|
using conditions_type = std::tuple<Args...>;
|
||||||
|
|
||||||
|
conditions_type conditions;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T, class... Ids>
|
||||||
|
struct get_t {
|
||||||
|
using type = T;
|
||||||
|
using ids_type = std::tuple<Ids...>;
|
||||||
|
|
||||||
|
ids_type ids;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T, class... Ids>
|
||||||
|
struct get_pointer_t {
|
||||||
|
using type = T;
|
||||||
|
using ids_type = std::tuple<Ids...>;
|
||||||
|
|
||||||
|
ids_type ids;
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef SQLITE_ORM_OPTIONAL_SUPPORTED
|
||||||
|
template<class T, class... Ids>
|
||||||
|
struct get_optional_t {
|
||||||
|
using type = T;
|
||||||
|
using ids_type = std::tuple<Ids...>;
|
||||||
|
|
||||||
|
ids_type ids;
|
||||||
|
};
|
||||||
|
#endif // SQLITE_ORM_OPTIONAL_SUPPORTED
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct update_t {
|
||||||
|
using type = T;
|
||||||
|
|
||||||
|
type object;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T, class... Ids>
|
||||||
|
struct remove_t {
|
||||||
|
using type = T;
|
||||||
|
using ids_type = std::tuple<Ids...>;
|
||||||
|
|
||||||
|
ids_type ids;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct insert_t {
|
||||||
|
using type = T;
|
||||||
|
|
||||||
|
type object;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
SQLITE_ORM_INLINE_VAR constexpr bool is_insert_v = polyfill::is_specialization_of_v<T, insert_t>;
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
using is_insert = polyfill::bool_constant<is_insert_v<T>>;
|
||||||
|
|
||||||
|
template<class T, class... Cols>
|
||||||
|
struct insert_explicit {
|
||||||
|
using type = T;
|
||||||
|
using columns_type = columns_t<Cols...>;
|
||||||
|
|
||||||
|
type obj;
|
||||||
|
columns_type columns;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct replace_t {
|
||||||
|
using type = T;
|
||||||
|
|
||||||
|
type object;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
SQLITE_ORM_INLINE_VAR constexpr bool is_replace_v = polyfill::is_specialization_of_v<T, replace_t>;
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
using is_replace = polyfill::bool_constant<is_replace_v<T>>;
|
||||||
|
|
||||||
|
template<class It, class Projection, class O>
|
||||||
|
struct insert_range_t {
|
||||||
|
using iterator_type = It;
|
||||||
|
using transformer_type = Projection;
|
||||||
|
using object_type = O;
|
||||||
|
|
||||||
|
std::pair<iterator_type, iterator_type> range;
|
||||||
|
transformer_type transformer;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
SQLITE_ORM_INLINE_VAR constexpr bool is_insert_range_v = polyfill::is_specialization_of_v<T, insert_range_t>;
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
using is_insert_range = polyfill::bool_constant<is_insert_range_v<T>>;
|
||||||
|
|
||||||
|
template<class It, class Projection, class O>
|
||||||
|
struct replace_range_t {
|
||||||
|
using iterator_type = It;
|
||||||
|
using transformer_type = Projection;
|
||||||
|
using object_type = O;
|
||||||
|
|
||||||
|
std::pair<iterator_type, iterator_type> range;
|
||||||
|
transformer_type transformer;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
SQLITE_ORM_INLINE_VAR constexpr bool is_replace_range_v = polyfill::is_specialization_of_v<T, replace_range_t>;
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
using is_replace_range = polyfill::bool_constant<is_replace_range_v<T>>;
|
||||||
|
|
||||||
|
template<class... Args>
|
||||||
|
struct insert_raw_t {
|
||||||
|
using args_tuple = std::tuple<Args...>;
|
||||||
|
|
||||||
|
args_tuple args;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
SQLITE_ORM_INLINE_VAR constexpr bool is_insert_raw_v = polyfill::is_specialization_of_v<T, insert_raw_t>;
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
using is_insert_raw = polyfill::bool_constant<is_insert_raw_v<T>>;
|
||||||
|
|
||||||
|
template<class... Args>
|
||||||
|
struct replace_raw_t {
|
||||||
|
using args_tuple = std::tuple<Args...>;
|
||||||
|
|
||||||
|
args_tuple args;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
SQLITE_ORM_INLINE_VAR constexpr bool is_replace_raw_v = polyfill::is_specialization_of_v<T, replace_raw_t>;
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
using is_replace_raw = polyfill::bool_constant<is_replace_raw_v<T>>;
|
||||||
|
|
||||||
|
struct default_values_t {};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
using is_default_values = std::is_same<T, default_values_t>;
|
||||||
|
|
||||||
|
enum class conflict_action {
|
||||||
|
abort,
|
||||||
|
fail,
|
||||||
|
ignore,
|
||||||
|
replace,
|
||||||
|
rollback,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct insert_constraint {
|
||||||
|
conflict_action action = conflict_action::abort;
|
||||||
|
|
||||||
|
#ifndef SQLITE_ORM_AGGREGATE_NSDMI_SUPPORTED
|
||||||
|
insert_constraint(conflict_action action) : action{action} {}
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
using is_insert_constraint = std::is_same<T, insert_constraint>;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline internal::insert_constraint or_rollback() {
|
||||||
|
return {internal::conflict_action::rollback};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline internal::insert_constraint or_replace() {
|
||||||
|
return {internal::conflict_action::replace};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline internal::insert_constraint or_ignore() {
|
||||||
|
return {internal::conflict_action::ignore};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline internal::insert_constraint or_fail() {
|
||||||
|
return {internal::conflict_action::fail};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline internal::insert_constraint or_abort() {
|
||||||
|
return {internal::conflict_action::abort};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this function to add `DEFAULT VALUES` modifier to raw `INSERT`.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```
|
||||||
|
* storage.insert(into<Singer>(), default_values());
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
inline internal::default_values_t default_values() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Raw insert statement creation routine. Use this if `insert` with object does not fit you. This insert is designed to be able
|
||||||
|
* to call any type of `INSERT` query with no limitations.
|
||||||
|
* @example
|
||||||
|
* ```sql
|
||||||
|
* INSERT INTO users (id, name) VALUES(5, 'Little Mix')
|
||||||
|
* ```
|
||||||
|
* will be
|
||||||
|
* ```c++
|
||||||
|
* auto statement = storage.prepare(insert(into<User>, columns(&User::id, &User::name), values(std::make_tuple(5, "Little Mix"))));
|
||||||
|
* storage.execute(statement));
|
||||||
|
* ```
|
||||||
|
* One more example:
|
||||||
|
* ```sql
|
||||||
|
* INSERT INTO singers (name) VALUES ('Sofia Reyes')('Kungs')
|
||||||
|
* ```
|
||||||
|
* will be
|
||||||
|
* ```c++
|
||||||
|
* auto statement = storage.prepare(insert(into<Singer>(), columns(&Singer::name), values(std::make_tuple("Sofia Reyes"), std::make_tuple("Kungs"))));
|
||||||
|
* storage.execute(statement));
|
||||||
|
* ```
|
||||||
|
* One can use `default_values` to add `DEFAULT VALUES` modifier:
|
||||||
|
* ```sql
|
||||||
|
* INSERT INTO users DEFAULT VALUES
|
||||||
|
* ```
|
||||||
|
* will be
|
||||||
|
* ```c++
|
||||||
|
* auto statement = storage.prepare(insert(into<Singer>(), default_values()));
|
||||||
|
* storage.execute(statement));
|
||||||
|
* ```
|
||||||
|
* Also one can use `INSERT OR ABORT`/`INSERT OR FAIL`/`INSERT OR IGNORE`/`INSERT OR REPLACE`/`INSERT ROLLBACK`:
|
||||||
|
* ```c++
|
||||||
|
* auto statement = storage.prepare(insert(or_ignore(), into<Singer>(), columns(&Singer::name), values(std::make_tuple("Sofia Reyes"), std::make_tuple("Kungs"))));
|
||||||
|
* auto statement2 = storage.prepare(insert(or_rollback(), into<Singer>(), default_values()));
|
||||||
|
* auto statement3 = storage.prepare(insert(or_abort(), into<User>, columns(&User::id, &User::name), values(std::make_tuple(5, "Little Mix"))));
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
template<class... Args>
|
||||||
|
internal::insert_raw_t<Args...> insert(Args... args) {
|
||||||
|
using args_tuple = std::tuple<Args...>;
|
||||||
|
using internal::count_tuple;
|
||||||
|
using internal::is_columns;
|
||||||
|
using internal::is_insert_constraint;
|
||||||
|
using internal::is_into;
|
||||||
|
using internal::is_select;
|
||||||
|
using internal::is_upsert_clause;
|
||||||
|
using internal::is_values;
|
||||||
|
|
||||||
|
constexpr int orArgsCount = count_tuple<args_tuple, is_insert_constraint>::value;
|
||||||
|
static_assert(orArgsCount < 2, "Raw insert must have only one OR... argument");
|
||||||
|
|
||||||
|
constexpr int intoArgsCount = count_tuple<args_tuple, is_into>::value;
|
||||||
|
static_assert(intoArgsCount != 0, "Raw insert must have into<T> argument");
|
||||||
|
static_assert(intoArgsCount < 2, "Raw insert must have only one into<T> argument");
|
||||||
|
|
||||||
|
constexpr int columnsArgsCount = count_tuple<args_tuple, is_columns>::value;
|
||||||
|
static_assert(columnsArgsCount < 2, "Raw insert must have only one columns(...) argument");
|
||||||
|
|
||||||
|
constexpr int valuesArgsCount = count_tuple<args_tuple, is_values>::value;
|
||||||
|
static_assert(valuesArgsCount < 2, "Raw insert must have only one values(...) argument");
|
||||||
|
|
||||||
|
constexpr int defaultValuesCount = count_tuple<args_tuple, internal::is_default_values>::value;
|
||||||
|
static_assert(defaultValuesCount < 2, "Raw insert must have only one default_values() argument");
|
||||||
|
|
||||||
|
constexpr int selectsArgsCount = count_tuple<args_tuple, is_select>::value;
|
||||||
|
static_assert(selectsArgsCount < 2, "Raw insert must have only one select(...) argument");
|
||||||
|
|
||||||
|
constexpr int upsertClausesCount = count_tuple<args_tuple, is_upsert_clause>::value;
|
||||||
|
static_assert(upsertClausesCount <= 2, "Raw insert can contain 2 instances of upsert clause maximum");
|
||||||
|
|
||||||
|
constexpr int argsCount = int(std::tuple_size<args_tuple>::value);
|
||||||
|
static_assert(argsCount == intoArgsCount + columnsArgsCount + valuesArgsCount + defaultValuesCount +
|
||||||
|
selectsArgsCount + orArgsCount + upsertClausesCount,
|
||||||
|
"Raw insert has invalid arguments");
|
||||||
|
|
||||||
|
return {{std::forward<Args>(args)...}};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Raw replace statement creation routine. Use this if `replace` with object does not fit you. This replace is designed to be able
|
||||||
|
* to call any type of `REPLACE` query with no limitations. Actually this is the same query as raw insert except `OR...` option existance.
|
||||||
|
* @example
|
||||||
|
* ```sql
|
||||||
|
* REPLACE INTO users (id, name) VALUES(5, 'Little Mix')
|
||||||
|
* ```
|
||||||
|
* will be
|
||||||
|
* ```c++
|
||||||
|
* auto statement = storage.prepare(replace(into<User>, columns(&User::id, &User::name), values(std::make_tuple(5, "Little Mix"))));
|
||||||
|
* storage.execute(statement));
|
||||||
|
* ```
|
||||||
|
* One more example:
|
||||||
|
* ```sql
|
||||||
|
* REPLACE INTO singers (name) VALUES ('Sofia Reyes')('Kungs')
|
||||||
|
* ```
|
||||||
|
* will be
|
||||||
|
* ```c++
|
||||||
|
* auto statement = storage.prepare(replace(into<Singer>(), columns(&Singer::name), values(std::make_tuple("Sofia Reyes"), std::make_tuple("Kungs"))));
|
||||||
|
* storage.execute(statement));
|
||||||
|
* ```
|
||||||
|
* One can use `default_values` to add `DEFAULT VALUES` modifier:
|
||||||
|
* ```sql
|
||||||
|
* REPLACE INTO users DEFAULT VALUES
|
||||||
|
* ```
|
||||||
|
* will be
|
||||||
|
* ```c++
|
||||||
|
* auto statement = storage.prepare(replace(into<Singer>(), default_values()));
|
||||||
|
* storage.execute(statement));
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
template<class... Args>
|
||||||
|
internal::replace_raw_t<Args...> replace(Args... args) {
|
||||||
|
using args_tuple = std::tuple<Args...>;
|
||||||
|
using internal::count_tuple;
|
||||||
|
using internal::is_columns;
|
||||||
|
using internal::is_into;
|
||||||
|
using internal::is_values;
|
||||||
|
|
||||||
|
constexpr int intoArgsCount = count_tuple<args_tuple, is_into>::value;
|
||||||
|
static_assert(intoArgsCount != 0, "Raw replace must have into<T> argument");
|
||||||
|
static_assert(intoArgsCount < 2, "Raw replace must have only one into<T> argument");
|
||||||
|
|
||||||
|
constexpr int columnsArgsCount = count_tuple<args_tuple, is_columns>::value;
|
||||||
|
static_assert(columnsArgsCount < 2, "Raw replace must have only one columns(...) argument");
|
||||||
|
|
||||||
|
constexpr int valuesArgsCount = count_tuple<args_tuple, is_values>::value;
|
||||||
|
static_assert(valuesArgsCount < 2, "Raw replace must have only one values(...) argument");
|
||||||
|
|
||||||
|
constexpr int defaultValuesCount = count_tuple<args_tuple, internal::is_default_values>::value;
|
||||||
|
static_assert(defaultValuesCount < 2, "Raw replace must have only one default_values() argument");
|
||||||
|
|
||||||
|
constexpr int selectsArgsCount = count_tuple<args_tuple, internal::is_select>::value;
|
||||||
|
static_assert(selectsArgsCount < 2, "Raw replace must have only one select(...) argument");
|
||||||
|
|
||||||
|
constexpr int argsCount = int(std::tuple_size<args_tuple>::value);
|
||||||
|
static_assert(argsCount ==
|
||||||
|
intoArgsCount + columnsArgsCount + valuesArgsCount + defaultValuesCount + selectsArgsCount,
|
||||||
|
"Raw replace has invalid arguments");
|
||||||
|
|
||||||
|
return {{std::forward<Args>(args)...}};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a replace range statement.
|
||||||
|
* The objects in the range are transformed using the specified projection, which defaults to identity projection.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```
|
||||||
|
* std::vector<User> users;
|
||||||
|
* users.push_back(User{1, "Leony"});
|
||||||
|
* auto statement = storage.prepare(replace_range(users.begin(), users.end()));
|
||||||
|
* storage.execute(statement);
|
||||||
|
* ```
|
||||||
|
* @example
|
||||||
|
* ```
|
||||||
|
* std::vector<std::unique_ptr<User>> userPointers;
|
||||||
|
* userPointers.push_back(std::make_unique<User>(1, "Eneli"));
|
||||||
|
* auto statement = storage.prepare(replace_range(userPointers.begin(), userPointers.end(), &std::unique_ptr<User>::operator*));
|
||||||
|
* storage.execute(statement);
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
template<class It, class Projection = polyfill::identity>
|
||||||
|
auto replace_range(It from, It to, Projection project = {}) {
|
||||||
|
using O = std::decay_t<decltype(polyfill::invoke(std::declval<Projection>(), *std::declval<It>()))>;
|
||||||
|
return internal::replace_range_t<It, Projection, O>{{std::move(from), std::move(to)}, std::move(project)};
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create a replace range statement.
|
||||||
|
* Overload of `replace_range(It, It, Projection)` with explicit object type template parameter.
|
||||||
|
*/
|
||||||
|
template<class O, class It, class Projection = polyfill::identity>
|
||||||
|
internal::replace_range_t<It, Projection, O> replace_range(It from, It to, Projection project = {}) {
|
||||||
|
return {{std::move(from), std::move(to)}, std::move(project)};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an insert range statement.
|
||||||
|
* The objects in the range are transformed using the specified projection, which defaults to identity projection.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```
|
||||||
|
* std::vector<User> users;
|
||||||
|
* users.push_back(User{1, "Leony"});
|
||||||
|
* auto statement = storage.prepare(insert_range(users.begin(), users.end()));
|
||||||
|
* storage.execute(statement);
|
||||||
|
* ```
|
||||||
|
* @example
|
||||||
|
* ```
|
||||||
|
* std::vector<std::unique_ptr<User>> userPointers;
|
||||||
|
* userPointers.push_back(std::make_unique<User>(1, "Eneli"));
|
||||||
|
* auto statement = storage.prepare(insert_range(userPointers.begin(), userPointers.end(), &std::unique_ptr<User>::operator*));
|
||||||
|
* storage.execute(statement);
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
template<class It, class Projection = polyfill::identity>
|
||||||
|
auto insert_range(It from, It to, Projection project = {}) {
|
||||||
|
using O = std::decay_t<decltype(polyfill::invoke(std::declval<Projection>(), *std::declval<It>()))>;
|
||||||
|
return internal::insert_range_t<It, Projection, O>{{std::move(from), std::move(to)}, std::move(project)};
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create an insert range statement.
|
||||||
|
* Overload of `insert_range(It, It, Projection)` with explicit object type template parameter.
|
||||||
|
*/
|
||||||
|
template<class O, class It, class Projection = polyfill::identity>
|
||||||
|
internal::insert_range_t<It, Projection, O> insert_range(It from, It to, Projection project = {}) {
|
||||||
|
return {{std::move(from), std::move(to)}, std::move(project)};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a replace statement.
|
||||||
|
* T is an object type mapped to a storage.
|
||||||
|
* Usage: storage.replace(myUserInstance);
|
||||||
|
* Parameter obj is accepted by value. If you want to accept it by ref
|
||||||
|
* please use std::ref function: storage.replace(std::ref(myUserInstance));
|
||||||
|
*/
|
||||||
|
template<class T>
|
||||||
|
internal::replace_t<T> replace(T obj) {
|
||||||
|
return {std::move(obj)};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an insert statement.
|
||||||
|
* T is an object type mapped to a storage.
|
||||||
|
* Usage: storage.insert(myUserInstance);
|
||||||
|
* Parameter obj is accepted by value. If you want to accept it by ref
|
||||||
|
* please use std::ref function: storage.insert(std::ref(myUserInstance));
|
||||||
|
*/
|
||||||
|
template<class T>
|
||||||
|
internal::insert_t<T> insert(T obj) {
|
||||||
|
return {std::move(obj)};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an explicit insert statement.
|
||||||
|
* T is an object type mapped to a storage.
|
||||||
|
* Cols is columns types aparameter pack. Must contain member pointers
|
||||||
|
* Usage: storage.insert(myUserInstance, columns(&User::id, &User::name));
|
||||||
|
* Parameter obj is accepted by value. If you want to accept it by ref
|
||||||
|
* please use std::ref function: storage.insert(std::ref(myUserInstance), columns(&User::id, &User::name));
|
||||||
|
*/
|
||||||
|
template<class T, class... Cols>
|
||||||
|
internal::insert_explicit<T, Cols...> insert(T obj, internal::columns_t<Cols...> cols) {
|
||||||
|
return {std::move(obj), std::move(cols)};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a remove statement
|
||||||
|
* T is an object type mapped to a storage.
|
||||||
|
* Usage: remove<User>(5);
|
||||||
|
*/
|
||||||
|
template<class T, class... Ids>
|
||||||
|
internal::remove_t<T, Ids...> remove(Ids... ids) {
|
||||||
|
std::tuple<Ids...> idsTuple{std::forward<Ids>(ids)...};
|
||||||
|
return {std::move(idsTuple)};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an update statement.
|
||||||
|
* T is an object type mapped to a storage.
|
||||||
|
* Usage: storage.update(myUserInstance);
|
||||||
|
* Parameter obj is accepted by value. If you want to accept it by ref
|
||||||
|
* please use std::ref function: storage.update(std::ref(myUserInstance));
|
||||||
|
*/
|
||||||
|
template<class T>
|
||||||
|
internal::update_t<T> update(T obj) {
|
||||||
|
return {std::move(obj)};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a get statement.
|
||||||
|
* T is an object type mapped to a storage.
|
||||||
|
* Usage: get<User>(5);
|
||||||
|
*/
|
||||||
|
template<class T, class... Ids>
|
||||||
|
internal::get_t<T, Ids...> get(Ids... ids) {
|
||||||
|
std::tuple<Ids...> idsTuple{std::forward<Ids>(ids)...};
|
||||||
|
return {std::move(idsTuple)};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a get pointer statement.
|
||||||
|
* T is an object type mapped to a storage.
|
||||||
|
* Usage: get_pointer<User>(5);
|
||||||
|
*/
|
||||||
|
template<class T, class... Ids>
|
||||||
|
internal::get_pointer_t<T, Ids...> get_pointer(Ids... ids) {
|
||||||
|
std::tuple<Ids...> idsTuple{std::forward<Ids>(ids)...};
|
||||||
|
return {std::move(idsTuple)};
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef SQLITE_ORM_OPTIONAL_SUPPORTED
|
||||||
|
/**
|
||||||
|
* Create a get optional statement.
|
||||||
|
* T is an object type mapped to a storage.
|
||||||
|
* Usage: get_optional<User>(5);
|
||||||
|
*/
|
||||||
|
template<class T, class... Ids>
|
||||||
|
internal::get_optional_t<T, Ids...> get_optional(Ids... ids) {
|
||||||
|
std::tuple<Ids...> idsTuple{std::forward<Ids>(ids)...};
|
||||||
|
return {std::move(idsTuple)};
|
||||||
|
}
|
||||||
|
#endif // SQLITE_ORM_OPTIONAL_SUPPORTED
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a remove all statement.
|
||||||
|
* T is an object type mapped to a storage.
|
||||||
|
* Usage: storage.remove_all<User>(...);
|
||||||
|
*/
|
||||||
|
template<class T, class... Args>
|
||||||
|
internal::remove_all_t<T, Args...> remove_all(Args... args) {
|
||||||
|
using args_tuple = std::tuple<Args...>;
|
||||||
|
internal::validate_conditions<args_tuple>();
|
||||||
|
args_tuple conditions{std::forward<Args>(args)...};
|
||||||
|
return {std::move(conditions)};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a get all statement.
|
||||||
|
* T is an object type mapped to a storage.
|
||||||
|
* Usage: storage.get_all<User>(...);
|
||||||
|
*/
|
||||||
|
template<class T, class... Args>
|
||||||
|
internal::get_all_t<T, std::vector<T>, Args...> get_all(Args... args) {
|
||||||
|
using args_tuple = std::tuple<Args...>;
|
||||||
|
internal::validate_conditions<args_tuple>();
|
||||||
|
args_tuple conditions{std::forward<Args>(args)...};
|
||||||
|
return {std::move(conditions)};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a get all statement.
|
||||||
|
* T is an object type mapped to a storage.
|
||||||
|
* R is a container type. std::vector<T> is default
|
||||||
|
* Usage: storage.get_all<User>(...);
|
||||||
|
*/
|
||||||
|
template<class T, class R, class... Args>
|
||||||
|
internal::get_all_t<T, R, Args...> get_all(Args... args) {
|
||||||
|
using args_tuple = std::tuple<Args...>;
|
||||||
|
internal::validate_conditions<args_tuple>();
|
||||||
|
args_tuple conditions{std::forward<Args>(args)...};
|
||||||
|
return {std::move(conditions)};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an update all statement.
|
||||||
|
* Usage: storage.update_all(set(...), ...);
|
||||||
|
*/
|
||||||
|
template<class S, class... Wargs>
|
||||||
|
internal::update_all_t<S, Wargs...> update_all(S set, Wargs... wh) {
|
||||||
|
static_assert(internal::is_set<S>::value, "first argument in update_all can be either set or dynamic_set");
|
||||||
|
using args_tuple = std::tuple<Wargs...>;
|
||||||
|
internal::validate_conditions<args_tuple>();
|
||||||
|
args_tuple conditions{std::forward<Wargs>(wh)...};
|
||||||
|
return {std::move(set), std::move(conditions)};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a get all pointer statement.
|
||||||
|
* T is an object type mapped to a storage.
|
||||||
|
* Usage: storage.get_all_pointer<User>(...);
|
||||||
|
*/
|
||||||
|
template<class T, class... Args>
|
||||||
|
internal::get_all_pointer_t<T, std::vector<std::unique_ptr<T>>, Args...> get_all_pointer(Args... args) {
|
||||||
|
using args_tuple = std::tuple<Args...>;
|
||||||
|
internal::validate_conditions<args_tuple>();
|
||||||
|
args_tuple conditions{std::forward<Args>(args)...};
|
||||||
|
return {std::move(conditions)};
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Create a get all pointer statement.
|
||||||
|
* T is an object type mapped to a storage.
|
||||||
|
* R is a container return type. std::vector<std::unique_ptr<T>> is default
|
||||||
|
* Usage: storage.get_all_pointer<User>(...);
|
||||||
|
*/
|
||||||
|
template<class T, class R, class... Args>
|
||||||
|
internal::get_all_pointer_t<T, R, Args...> get_all_pointer(Args... args) {
|
||||||
|
using args_tuple = std::tuple<Args...>;
|
||||||
|
internal::validate_conditions<args_tuple>();
|
||||||
|
args_tuple conditions{std::forward<Args>(args)...};
|
||||||
|
return {std::move(conditions)};
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef SQLITE_ORM_OPTIONAL_SUPPORTED
|
||||||
|
/**
|
||||||
|
* Create a get all optional statement.
|
||||||
|
* T is an object type mapped to a storage.
|
||||||
|
* Usage: storage.get_all_optional<User>(...);
|
||||||
|
*/
|
||||||
|
template<class T, class... Args>
|
||||||
|
internal::get_all_optional_t<T, std::vector<std::optional<T>>, Args...> get_all_optional(Args... args) {
|
||||||
|
using args_tuple = std::tuple<Args...>;
|
||||||
|
internal::validate_conditions<args_tuple>();
|
||||||
|
args_tuple conditions{std::forward<Args>(args)...};
|
||||||
|
return {std::move(conditions)};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a get all optional statement.
|
||||||
|
* T is an object type mapped to a storage.
|
||||||
|
* R is a container return type. std::vector<std::optional<T>> is default
|
||||||
|
* Usage: storage.get_all_optional<User>(...);
|
||||||
|
*/
|
||||||
|
template<class T, class R, class... Args>
|
||||||
|
internal::get_all_optional_t<T, R, Args...> get_all_optional(Args... args) {
|
||||||
|
using args_tuple = std::tuple<Args...>;
|
||||||
|
internal::validate_conditions<args_tuple>();
|
||||||
|
args_tuple conditions{std::forward<Args>(args)...};
|
||||||
|
return {std::move(conditions)};
|
||||||
|
}
|
||||||
|
#endif // SQLITE_ORM_OPTIONAL_SUPPORTED
|
||||||
|
}
|
|
@ -0,0 +1,340 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <sqlite3.h>
|
||||||
|
#include <type_traits> // std::enable_if_t, std::is_arithmetic, std::is_same, std::enable_if
|
||||||
|
#include <stdlib.h> // atof, atoi, atoll
|
||||||
|
#include <system_error> // std::system_error
|
||||||
|
#include <string> // std::string, std::wstring
|
||||||
|
#ifndef SQLITE_ORM_OMITS_CODECVT
|
||||||
|
#include <codecvt> // std::wstring_convert, std::codecvt_utf8_utf16
|
||||||
|
#endif // SQLITE_ORM_OMITS_CODECVT
|
||||||
|
#include <vector> // std::vector
|
||||||
|
#include <cstring> // strlen
|
||||||
|
#include <locale>
|
||||||
|
#include <algorithm> // std::copy
|
||||||
|
#include <iterator> // std::back_inserter
|
||||||
|
#include <tuple> // std::tuple, std::tuple_size, std::tuple_element
|
||||||
|
|
||||||
|
#include "functional/cxx_universal.h"
|
||||||
|
#include "arithmetic_tag.h"
|
||||||
|
#include "pointer_value.h"
|
||||||
|
#include "journal_mode.h"
|
||||||
|
#include "error_code.h"
|
||||||
|
#include "is_std_ptr.h"
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class used to cast values from argv to V class
|
||||||
|
* which depends from column type.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
template<class V, typename Enable = void>
|
||||||
|
struct row_extractor {
|
||||||
|
// used in sqlite3_exec (select)
|
||||||
|
V extract(const char* row_value) const = delete;
|
||||||
|
|
||||||
|
// used in sqlite_column (iteration, get_all)
|
||||||
|
V extract(sqlite3_stmt* stmt, int columnIndex) const = delete;
|
||||||
|
|
||||||
|
// used in user defined functions
|
||||||
|
V extract(sqlite3_value* value) const = delete;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class R>
|
||||||
|
int extract_single_value(void* data, int argc, char** argv, char**) {
|
||||||
|
auto& res = *(R*)data;
|
||||||
|
if(argc) {
|
||||||
|
res = row_extractor<R>{}.extract(argv[0]);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specialization for the 'pointer-passing interface'.
|
||||||
|
*
|
||||||
|
* @note The 'pointer-passing' interface doesn't support (and in fact prohibits)
|
||||||
|
* extracting pointers from columns.
|
||||||
|
*/
|
||||||
|
template<class P, class T>
|
||||||
|
struct row_extractor<pointer_arg<P, T>, void> {
|
||||||
|
using V = pointer_arg<P, T>;
|
||||||
|
|
||||||
|
V extract(sqlite3_value* value) const {
|
||||||
|
return {(P*)sqlite3_value_pointer(value, T::value)};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Undefine using pointer_binding<> for querying values
|
||||||
|
*/
|
||||||
|
template<class P, class T, class D>
|
||||||
|
struct row_extractor<pointer_binding<P, T, D>, void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specialization for arithmetic types.
|
||||||
|
*/
|
||||||
|
template<class V>
|
||||||
|
struct row_extractor<V, std::enable_if_t<std::is_arithmetic<V>::value>> {
|
||||||
|
V extract(const char* row_value) const {
|
||||||
|
return this->extract(row_value, tag());
|
||||||
|
}
|
||||||
|
|
||||||
|
V extract(sqlite3_stmt* stmt, int columnIndex) const {
|
||||||
|
return this->extract(stmt, columnIndex, tag());
|
||||||
|
}
|
||||||
|
|
||||||
|
V extract(sqlite3_value* value) const {
|
||||||
|
return this->extract(value, tag());
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
using tag = arithmetic_tag_t<V>;
|
||||||
|
|
||||||
|
V extract(const char* row_value, const int_or_smaller_tag&) const {
|
||||||
|
return static_cast<V>(atoi(row_value));
|
||||||
|
}
|
||||||
|
|
||||||
|
V extract(sqlite3_stmt* stmt, int columnIndex, const int_or_smaller_tag&) const {
|
||||||
|
return static_cast<V>(sqlite3_column_int(stmt, columnIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
V extract(sqlite3_value* value, const int_or_smaller_tag&) const {
|
||||||
|
return static_cast<V>(sqlite3_value_int(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
V extract(const char* row_value, const bigint_tag&) const {
|
||||||
|
return static_cast<V>(atoll(row_value));
|
||||||
|
}
|
||||||
|
|
||||||
|
V extract(sqlite3_stmt* stmt, int columnIndex, const bigint_tag&) const {
|
||||||
|
return static_cast<V>(sqlite3_column_int64(stmt, columnIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
V extract(sqlite3_value* value, const bigint_tag&) const {
|
||||||
|
return static_cast<V>(sqlite3_value_int64(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
V extract(const char* row_value, const real_tag&) const {
|
||||||
|
return static_cast<V>(atof(row_value));
|
||||||
|
}
|
||||||
|
|
||||||
|
V extract(sqlite3_stmt* stmt, int columnIndex, const real_tag&) const {
|
||||||
|
return static_cast<V>(sqlite3_column_double(stmt, columnIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
V extract(sqlite3_value* value, const real_tag&) const {
|
||||||
|
return static_cast<V>(sqlite3_value_double(value));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specialization for std::string.
|
||||||
|
*/
|
||||||
|
template<>
|
||||||
|
struct row_extractor<std::string, void> {
|
||||||
|
std::string extract(const char* row_value) const {
|
||||||
|
if(row_value) {
|
||||||
|
return row_value;
|
||||||
|
} else {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string extract(sqlite3_stmt* stmt, int columnIndex) const {
|
||||||
|
if(auto cStr = (const char*)sqlite3_column_text(stmt, columnIndex)) {
|
||||||
|
return cStr;
|
||||||
|
} else {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string extract(sqlite3_value* value) const {
|
||||||
|
if(auto cStr = (const char*)sqlite3_value_text(value)) {
|
||||||
|
return cStr;
|
||||||
|
} else {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#ifndef SQLITE_ORM_OMITS_CODECVT
|
||||||
|
/**
|
||||||
|
* Specialization for std::wstring.
|
||||||
|
*/
|
||||||
|
template<>
|
||||||
|
struct row_extractor<std::wstring, void> {
|
||||||
|
std::wstring extract(const char* row_value) const {
|
||||||
|
if(row_value) {
|
||||||
|
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
|
||||||
|
return converter.from_bytes(row_value);
|
||||||
|
} else {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::wstring extract(sqlite3_stmt* stmt, int columnIndex) const {
|
||||||
|
auto cStr = (const char*)sqlite3_column_text(stmt, columnIndex);
|
||||||
|
if(cStr) {
|
||||||
|
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
|
||||||
|
return converter.from_bytes(cStr);
|
||||||
|
} else {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::wstring extract(sqlite3_value* value) const {
|
||||||
|
if(auto cStr = (const wchar_t*)sqlite3_value_text16(value)) {
|
||||||
|
return cStr;
|
||||||
|
} else {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#endif // SQLITE_ORM_OMITS_CODECVT
|
||||||
|
|
||||||
|
template<class V>
|
||||||
|
struct row_extractor<V, std::enable_if_t<is_std_ptr<V>::value>> {
|
||||||
|
using unqualified_type = std::remove_cv_t<typename V::element_type>;
|
||||||
|
|
||||||
|
V extract(const char* row_value) const {
|
||||||
|
if(row_value) {
|
||||||
|
return is_std_ptr<V>::make(row_extractor<unqualified_type>().extract(row_value));
|
||||||
|
} else {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
V extract(sqlite3_stmt* stmt, int columnIndex) const {
|
||||||
|
auto type = sqlite3_column_type(stmt, columnIndex);
|
||||||
|
if(type != SQLITE_NULL) {
|
||||||
|
return is_std_ptr<V>::make(row_extractor<unqualified_type>().extract(stmt, columnIndex));
|
||||||
|
} else {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
V extract(sqlite3_value* value) const {
|
||||||
|
auto type = sqlite3_value_type(value);
|
||||||
|
if(type != SQLITE_NULL) {
|
||||||
|
return is_std_ptr<V>::make(row_extractor<unqualified_type>().extract(value));
|
||||||
|
} else {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef SQLITE_ORM_OPTIONAL_SUPPORTED
|
||||||
|
template<class V>
|
||||||
|
struct row_extractor<V, std::enable_if_t<polyfill::is_specialization_of_v<V, std::optional>>> {
|
||||||
|
using unqualified_type = std::remove_cv_t<typename V::value_type>;
|
||||||
|
|
||||||
|
V extract(const char* row_value) const {
|
||||||
|
if(row_value) {
|
||||||
|
return std::make_optional(row_extractor<unqualified_type>().extract(row_value));
|
||||||
|
} else {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
V extract(sqlite3_stmt* stmt, int columnIndex) const {
|
||||||
|
auto type = sqlite3_column_type(stmt, columnIndex);
|
||||||
|
if(type != SQLITE_NULL) {
|
||||||
|
return std::make_optional(row_extractor<unqualified_type>().extract(stmt, columnIndex));
|
||||||
|
} else {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
V extract(sqlite3_value* value) const {
|
||||||
|
auto type = sqlite3_value_type(value);
|
||||||
|
if(type != SQLITE_NULL) {
|
||||||
|
return std::make_optional(row_extractor<unqualified_type>().extract(value));
|
||||||
|
} else {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#endif // SQLITE_ORM_OPTIONAL_SUPPORTED
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct row_extractor<nullptr_t> {
|
||||||
|
nullptr_t extract(const char* /*row_value*/) const {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
nullptr_t extract(sqlite3_stmt*, int /*columnIndex*/) const {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
nullptr_t extract(sqlite3_value*) const {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Specialization for std::vector<char>.
|
||||||
|
*/
|
||||||
|
template<>
|
||||||
|
struct row_extractor<std::vector<char>> {
|
||||||
|
std::vector<char> extract(const char* row_value) const {
|
||||||
|
return {row_value, row_value + (row_value ? ::strlen(row_value) : 0)};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<char> extract(sqlite3_stmt* stmt, int columnIndex) const {
|
||||||
|
auto bytes = static_cast<const char*>(sqlite3_column_blob(stmt, columnIndex));
|
||||||
|
auto len = static_cast<size_t>(sqlite3_column_bytes(stmt, columnIndex));
|
||||||
|
return {bytes, bytes + len};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<char> extract(sqlite3_value* value) const {
|
||||||
|
auto bytes = static_cast<const char*>(sqlite3_value_blob(value));
|
||||||
|
auto len = static_cast<size_t>(sqlite3_value_bytes(value));
|
||||||
|
return {bytes, bytes + len};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class... Args>
|
||||||
|
struct row_extractor<std::tuple<Args...>> {
|
||||||
|
|
||||||
|
std::tuple<Args...> extract(char** argv) const {
|
||||||
|
return this->extract(argv, std::make_index_sequence<sizeof...(Args)>{});
|
||||||
|
}
|
||||||
|
|
||||||
|
std::tuple<Args...> extract(sqlite3_stmt* stmt, int /*columnIndex*/) const {
|
||||||
|
return this->extract(stmt, std::make_index_sequence<sizeof...(Args)>{});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
template<size_t... Idx>
|
||||||
|
std::tuple<Args...> extract(sqlite3_stmt* stmt, std::index_sequence<Idx...>) const {
|
||||||
|
return std::tuple<Args...>{row_extractor<Args>{}.extract(stmt, Idx)...};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<size_t... Idx>
|
||||||
|
std::tuple<Args...> extract(char** argv, std::index_sequence<Idx...>) const {
|
||||||
|
return std::tuple<Args...>{row_extractor<Args>{}.extract(argv[Idx])...};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specialization for journal_mode.
|
||||||
|
*/
|
||||||
|
template<>
|
||||||
|
struct row_extractor<journal_mode, void> {
|
||||||
|
journal_mode extract(const char* row_value) const {
|
||||||
|
if(row_value) {
|
||||||
|
if(auto res = internal::journal_mode_from_string(row_value)) {
|
||||||
|
return std::move(*res);
|
||||||
|
} else {
|
||||||
|
throw std::system_error{orm_error_code::incorrect_journal_mode_string};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw std::system_error{orm_error_code::incorrect_journal_mode_string};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
journal_mode extract(sqlite3_stmt* stmt, int columnIndex) const {
|
||||||
|
auto cStr = (const char*)sqlite3_column_text(stmt, columnIndex);
|
||||||
|
return this->extract(cStr);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "functional/cxx_universal.h"
|
||||||
|
#include "row_extractor.h"
|
||||||
|
#include "mapped_row_extractor.h"
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
row_extractor<T> make_row_extractor(nullptr_t) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T, class Table>
|
||||||
|
mapped_row_extractor<T, Table> make_row_extractor(const Table* table) {
|
||||||
|
return {*table};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string> // std::string
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
struct rowid_t {
|
||||||
|
operator std::string() const {
|
||||||
|
return "rowid";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct oid_t {
|
||||||
|
operator std::string() const {
|
||||||
|
return "oid";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct _rowid_t {
|
||||||
|
operator std::string() const {
|
||||||
|
return "_rowid_";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct table_rowid_t : public rowid_t {
|
||||||
|
using type = T;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct table_oid_t : public oid_t {
|
||||||
|
using type = T;
|
||||||
|
};
|
||||||
|
template<class T>
|
||||||
|
struct table__rowid_t : public _rowid_t {
|
||||||
|
using type = T;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
inline internal::rowid_t rowid() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline internal::oid_t oid() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline internal::_rowid_t _rowid_() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
internal::table_rowid_t<T> rowid() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
internal::table_oid_t<T> oid() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
internal::table__rowid_t<T> _rowid_() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,433 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <type_traits> // std::remove_const
|
||||||
|
#include <string> // std::string
|
||||||
|
#include <utility> // std::move
|
||||||
|
#include <tuple> // std::tuple, std::get, std::tuple_size
|
||||||
|
#include "functional/cxx_optional.h"
|
||||||
|
|
||||||
|
#include "functional/cxx_universal.h"
|
||||||
|
#include "functional/cxx_type_traits_polyfill.h"
|
||||||
|
#include "is_base_of_template.h"
|
||||||
|
#include "tuple_helper/tuple_filter.h"
|
||||||
|
#include "optional_container.h"
|
||||||
|
#include "ast/where.h"
|
||||||
|
#include "ast/group_by.h"
|
||||||
|
#include "core_functions.h"
|
||||||
|
#include "alias_traits.h"
|
||||||
|
#include "column_pointer.h"
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
#ifdef SQLITE_ORM_OPTIONAL_SUPPORTED
|
||||||
|
template<class T>
|
||||||
|
struct as_optional_t {
|
||||||
|
using value_type = T;
|
||||||
|
|
||||||
|
value_type value;
|
||||||
|
};
|
||||||
|
#endif // SQLITE_ORM_OPTIONAL_SUPPORTED
|
||||||
|
|
||||||
|
struct distinct_string {
|
||||||
|
operator std::string() const {
|
||||||
|
return "DISTINCT";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DISCTINCT generic container.
|
||||||
|
*/
|
||||||
|
template<class T>
|
||||||
|
struct distinct_t : distinct_string {
|
||||||
|
using value_type = T;
|
||||||
|
|
||||||
|
value_type value;
|
||||||
|
|
||||||
|
distinct_t(value_type value_) : value(std::move(value_)) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct all_string {
|
||||||
|
operator std::string() const {
|
||||||
|
return "ALL";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ALL generic container.
|
||||||
|
*/
|
||||||
|
template<class T>
|
||||||
|
struct all_t : all_string {
|
||||||
|
T value;
|
||||||
|
|
||||||
|
all_t(T value_) : value(std::move(value_)) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class... Args>
|
||||||
|
struct columns_t {
|
||||||
|
using columns_type = std::tuple<Args...>;
|
||||||
|
|
||||||
|
columns_type columns;
|
||||||
|
bool distinct = false;
|
||||||
|
|
||||||
|
static constexpr int count = std::tuple_size<columns_type>::value;
|
||||||
|
|
||||||
|
#ifndef SQLITE_ORM_AGGREGATE_NSDMI_SUPPORTED
|
||||||
|
columns_t(columns_type columns) : columns{std::move(columns)} {}
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
SQLITE_ORM_INLINE_VAR constexpr bool is_columns_v = polyfill::is_specialization_of_v<T, columns_t>;
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
using is_columns = polyfill::bool_constant<is_columns_v<T>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subselect object type.
|
||||||
|
*/
|
||||||
|
template<class T, class... Args>
|
||||||
|
struct select_t {
|
||||||
|
using return_type = T;
|
||||||
|
using conditions_type = std::tuple<Args...>;
|
||||||
|
|
||||||
|
return_type col;
|
||||||
|
conditions_type conditions;
|
||||||
|
bool highest_level = false;
|
||||||
|
|
||||||
|
#ifndef SQLITE_ORM_AGGREGATE_NSDMI_SUPPORTED
|
||||||
|
select_t(return_type col, conditions_type conditions) :
|
||||||
|
col{std::move(col)}, conditions{std::move(conditions)} {}
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
SQLITE_ORM_INLINE_VAR constexpr bool is_select_v = polyfill::is_specialization_of_v<T, select_t>;
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
using is_select = polyfill::bool_constant<is_select_v<T>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base for UNION, UNION ALL, EXCEPT and INTERSECT
|
||||||
|
*/
|
||||||
|
template<class L, class R>
|
||||||
|
struct compound_operator {
|
||||||
|
using left_type = L;
|
||||||
|
using right_type = R;
|
||||||
|
|
||||||
|
left_type left;
|
||||||
|
right_type right;
|
||||||
|
|
||||||
|
compound_operator(left_type l, right_type r) : left(std::move(l)), right(std::move(r)) {
|
||||||
|
this->left.highest_level = true;
|
||||||
|
this->right.highest_level = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
SQLITE_ORM_INLINE_VAR constexpr bool is_compound_operator_v = is_base_of_template_v<T, compound_operator>;
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
using is_compound_operator = polyfill::bool_constant<is_compound_operator_v<T>>;
|
||||||
|
|
||||||
|
struct union_base {
|
||||||
|
bool all = false;
|
||||||
|
|
||||||
|
#ifndef SQLITE_ORM_AGGREGATE_NSDMI_SUPPORTED
|
||||||
|
union_base(bool all) : all{all} {}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
operator std::string() const {
|
||||||
|
if(!this->all) {
|
||||||
|
return "UNION";
|
||||||
|
} else {
|
||||||
|
return "UNION ALL";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UNION object type.
|
||||||
|
*/
|
||||||
|
template<class L, class R>
|
||||||
|
struct union_t : public compound_operator<L, R>, union_base {
|
||||||
|
using left_type = typename compound_operator<L, R>::left_type;
|
||||||
|
using right_type = typename compound_operator<L, R>::right_type;
|
||||||
|
|
||||||
|
union_t(left_type l, right_type r, bool all_) :
|
||||||
|
compound_operator<L, R>{std::move(l), std::move(r)}, union_base{all_} {}
|
||||||
|
|
||||||
|
union_t(left_type l, right_type r) : union_t{std::move(l), std::move(r), false} {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct except_string {
|
||||||
|
operator std::string() const {
|
||||||
|
return "EXCEPT";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EXCEPT object type.
|
||||||
|
*/
|
||||||
|
template<class L, class R>
|
||||||
|
struct except_t : compound_operator<L, R>, except_string {
|
||||||
|
using super = compound_operator<L, R>;
|
||||||
|
using left_type = typename super::left_type;
|
||||||
|
using right_type = typename super::right_type;
|
||||||
|
|
||||||
|
using super::super;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct intersect_string {
|
||||||
|
operator std::string() const {
|
||||||
|
return "INTERSECT";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* INTERSECT object type.
|
||||||
|
*/
|
||||||
|
template<class L, class R>
|
||||||
|
struct intersect_t : compound_operator<L, R>, intersect_string {
|
||||||
|
using super = compound_operator<L, R>;
|
||||||
|
using left_type = typename super::left_type;
|
||||||
|
using right_type = typename super::right_type;
|
||||||
|
|
||||||
|
using super::super;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic way to get DISTINCT value from any type.
|
||||||
|
*/
|
||||||
|
template<class T>
|
||||||
|
bool get_distinct(const T&) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class... Args>
|
||||||
|
bool get_distinct(const columns_t<Args...>& cols) {
|
||||||
|
return cols.distinct;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct asterisk_t {
|
||||||
|
using type = T;
|
||||||
|
|
||||||
|
bool defined_order = false;
|
||||||
|
|
||||||
|
#ifndef SQLITE_ORM_AGGREGATE_NSDMI_SUPPORTED
|
||||||
|
asterisk_t(bool definedOrder) : defined_order{definedOrder} {}
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct object_t {
|
||||||
|
using type = T;
|
||||||
|
|
||||||
|
bool defined_order = false;
|
||||||
|
|
||||||
|
#ifndef SQLITE_ORM_AGGREGATE_NSDMI_SUPPORTED
|
||||||
|
object_t(bool definedOrder) : defined_order{definedOrder} {}
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct then_t {
|
||||||
|
using expression_type = T;
|
||||||
|
|
||||||
|
expression_type expression;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class R, class T, class E, class... Args>
|
||||||
|
struct simple_case_t {
|
||||||
|
using return_type = R;
|
||||||
|
using case_expression_type = T;
|
||||||
|
using args_type = std::tuple<Args...>;
|
||||||
|
using else_expression_type = E;
|
||||||
|
|
||||||
|
optional_container<case_expression_type> case_expression;
|
||||||
|
args_type args;
|
||||||
|
optional_container<else_expression_type> else_expression;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* T is a case expression type
|
||||||
|
* E is else type (void is ELSE is omitted)
|
||||||
|
* Args... is a pack of WHEN expressions
|
||||||
|
*/
|
||||||
|
template<class R, class T, class E, class... Args>
|
||||||
|
struct simple_case_builder {
|
||||||
|
using return_type = R;
|
||||||
|
using case_expression_type = T;
|
||||||
|
using args_type = std::tuple<Args...>;
|
||||||
|
using else_expression_type = E;
|
||||||
|
|
||||||
|
optional_container<case_expression_type> case_expression;
|
||||||
|
args_type args;
|
||||||
|
optional_container<else_expression_type> else_expression;
|
||||||
|
|
||||||
|
template<class W, class Th>
|
||||||
|
simple_case_builder<R, T, E, Args..., std::pair<W, Th>> when(W w, then_t<Th> t) {
|
||||||
|
using result_args_type = std::tuple<Args..., std::pair<W, Th>>;
|
||||||
|
std::pair<W, Th> newPair{std::move(w), std::move(t.expression)};
|
||||||
|
result_args_type result_args = std::tuple_cat(std::move(this->args), std::make_tuple(newPair));
|
||||||
|
std::get<std::tuple_size<result_args_type>::value - 1>(result_args) = std::move(newPair);
|
||||||
|
return {std::move(this->case_expression), std::move(result_args), std::move(this->else_expression)};
|
||||||
|
}
|
||||||
|
|
||||||
|
simple_case_t<R, T, E, Args...> end() {
|
||||||
|
return {std::move(this->case_expression), std::move(args), std::move(this->else_expression)};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class El>
|
||||||
|
simple_case_builder<R, T, El, Args...> else_(El el) {
|
||||||
|
return {{std::move(this->case_expression)}, std::move(args), {std::move(el)}};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
void validate_conditions() {
|
||||||
|
static_assert(count_tuple<T, is_where>::value <= 1, "a single query cannot contain > 1 WHERE blocks");
|
||||||
|
static_assert(count_tuple<T, is_group_by>::value <= 1, "a single query cannot contain > 1 GROUP BY blocks");
|
||||||
|
static_assert(count_tuple<T, is_order_by>::value <= 1, "a single query cannot contain > 1 ORDER BY blocks");
|
||||||
|
static_assert(count_tuple<T, is_limit>::value <= 1, "a single query cannot contain > 1 LIMIT blocks");
|
||||||
|
static_assert(count_tuple<T, is_from>::value <= 1, "a single query cannot contain > 1 FROM blocks");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef SQLITE_ORM_OPTIONAL_SUPPORTED
|
||||||
|
template<class T>
|
||||||
|
internal::as_optional_t<T> as_optional(T value) {
|
||||||
|
return {std::move(value)};
|
||||||
|
}
|
||||||
|
#endif // SQLITE_ORM_OPTIONAL_SUPPORTED
|
||||||
|
template<class T>
|
||||||
|
internal::then_t<T> then(T t) {
|
||||||
|
return {std::move(t)};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class R, class T>
|
||||||
|
internal::simple_case_builder<R, T, void> case_(T t) {
|
||||||
|
return {{std::move(t)}};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class R>
|
||||||
|
internal::simple_case_builder<R, void, void> case_() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
internal::distinct_t<T> distinct(T t) {
|
||||||
|
return {std::move(t)};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
internal::all_t<T> all(T t) {
|
||||||
|
return {std::move(t)};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class... Args>
|
||||||
|
internal::columns_t<Args...> distinct(internal::columns_t<Args...> cols) {
|
||||||
|
cols.distinct = true;
|
||||||
|
return cols;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class... Args>
|
||||||
|
internal::columns_t<Args...> columns(Args... args) {
|
||||||
|
return {std::make_tuple<Args...>(std::forward<Args>(args)...)};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use it like this:
|
||||||
|
* struct MyType : BaseType { ... };
|
||||||
|
* storage.select(column<MyType>(&BaseType::id));
|
||||||
|
*/
|
||||||
|
template<class T, class F>
|
||||||
|
internal::column_pointer<T, F> column(F f) {
|
||||||
|
return {std::move(f)};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public function for subselect query. Is useful in UNION queries.
|
||||||
|
*/
|
||||||
|
template<class T, class... Args>
|
||||||
|
internal::select_t<T, Args...> select(T t, Args... args) {
|
||||||
|
using args_tuple = std::tuple<Args...>;
|
||||||
|
internal::validate_conditions<args_tuple>();
|
||||||
|
return {std::move(t), std::make_tuple(std::forward<Args>(args)...)};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public function for UNION operator.
|
||||||
|
* lhs and rhs are subselect objects.
|
||||||
|
* Look through example in examples/union.cpp
|
||||||
|
*/
|
||||||
|
template<class L, class R>
|
||||||
|
internal::union_t<L, R> union_(L lhs, R rhs) {
|
||||||
|
return {std::move(lhs), std::move(rhs)};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public function for EXCEPT operator.
|
||||||
|
* lhs and rhs are subselect objects.
|
||||||
|
* Look through example in examples/except.cpp
|
||||||
|
*/
|
||||||
|
template<class L, class R>
|
||||||
|
internal::except_t<L, R> except(L lhs, R rhs) {
|
||||||
|
return {std::move(lhs), std::move(rhs)};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class L, class R>
|
||||||
|
internal::intersect_t<L, R> intersect(L lhs, R rhs) {
|
||||||
|
return {std::move(lhs), std::move(rhs)};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public function for UNION ALL operator.
|
||||||
|
* lhs and rhs are subselect objects.
|
||||||
|
* Look through example in examples/union.cpp
|
||||||
|
*/
|
||||||
|
template<class L, class R>
|
||||||
|
internal::union_t<L, R> union_all(L lhs, R rhs) {
|
||||||
|
return {std::move(lhs), std::move(rhs), true};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `SELECT * FROM T` expression that fetches results as tuples.
|
||||||
|
* T is a type mapped to a storage, or an alias of it.
|
||||||
|
* The `definedOrder` parameter denotes the expected order of result columns.
|
||||||
|
* The default is the implicit order as returned by SQLite, which may differ from the defined order
|
||||||
|
* if the schema of a table has been changed.
|
||||||
|
* By specifying the defined order, the columns are written out in the resulting select SQL string.
|
||||||
|
*
|
||||||
|
* In pseudo code:
|
||||||
|
* select(asterisk<User>(false)) -> SELECT * from User
|
||||||
|
* select(asterisk<User>(true)) -> SELECT id, name from User
|
||||||
|
*
|
||||||
|
* Example: auto rows = storage.select(asterisk<User>());
|
||||||
|
* // decltype(rows) is std::vector<std::tuple<...all columns in implicitly stored order...>>
|
||||||
|
* Example: auto rows = storage.select(asterisk<User>(true));
|
||||||
|
* // decltype(rows) is std::vector<std::tuple<...all columns in declared make_table order...>>
|
||||||
|
*
|
||||||
|
* If you need to fetch results as objects instead of tuples please use `object<T>()`.
|
||||||
|
*/
|
||||||
|
template<class T>
|
||||||
|
internal::asterisk_t<T> asterisk(bool definedOrder = false) {
|
||||||
|
return {definedOrder};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `SELECT * FROM T` expression that fetches results as objects of type T.
|
||||||
|
* T is a type mapped to a storage, or an alias of it.
|
||||||
|
*
|
||||||
|
* Example: auto rows = storage.select(object<User>());
|
||||||
|
* // decltype(rows) is std::vector<User>, where the User objects are constructed from columns in implicitly stored order
|
||||||
|
* Example: auto rows = storage.select(object<User>(true));
|
||||||
|
* // decltype(rows) is std::vector<User>, where the User objects are constructed from columns in declared make_table order
|
||||||
|
*
|
||||||
|
* If you need to fetch results as tuples instead of objects please use `asterisk<T>()`.
|
||||||
|
*/
|
||||||
|
template<class T>
|
||||||
|
internal::object_t<T> object(bool definedOrder = false) {
|
||||||
|
return {definedOrder};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "functional/cxx_string_view.h"
|
||||||
|
#ifndef SQLITE_ORM_STRING_VIEW_SUPPORTED
|
||||||
|
#include <string> // std::string
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
namespace internal {
|
||||||
|
#ifdef SQLITE_ORM_STRING_VIEW_SUPPORTED
|
||||||
|
using serialize_result_type = std::string_view;
|
||||||
|
using serialize_arg_type = std::string_view;
|
||||||
|
#else
|
||||||
|
using serialize_result_type = std::string;
|
||||||
|
using serialize_arg_type = const std::string&;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
struct serializer_context_base {
|
||||||
|
bool replace_bindable_with_question = false;
|
||||||
|
bool skip_table_name = true;
|
||||||
|
bool use_parentheses = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class DBOs>
|
||||||
|
struct serializer_context : serializer_context_base {
|
||||||
|
using db_objects_type = DBOs;
|
||||||
|
|
||||||
|
const db_objects_type& db_objects;
|
||||||
|
|
||||||
|
serializer_context(const db_objects_type& dbObjects) : db_objects{dbObjects} {}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class S>
|
||||||
|
struct serializer_context_builder {
|
||||||
|
using storage_type = S;
|
||||||
|
using db_objects_type = typename storage_type::db_objects_type;
|
||||||
|
|
||||||
|
serializer_context_builder(const storage_type& storage_) : storage{storage_} {}
|
||||||
|
|
||||||
|
serializer_context<db_objects_type> operator()() const {
|
||||||
|
return {obtain_db_objects(this->storage)};
|
||||||
|
}
|
||||||
|
|
||||||
|
const storage_type& storage;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,403 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <type_traits> // std::index_sequence
|
||||||
|
#include <tuple>
|
||||||
|
#include <array>
|
||||||
|
#include <string>
|
||||||
|
#include <ostream>
|
||||||
|
#include <utility> // std::exchange, std::tuple_size
|
||||||
|
|
||||||
|
#include "functional/cxx_universal.h" // ::size_t
|
||||||
|
#include "functional/cxx_type_traits_polyfill.h"
|
||||||
|
#include "tuple_helper/tuple_iteration.h"
|
||||||
|
#include "error_code.h"
|
||||||
|
#include "serializer_context.h"
|
||||||
|
#include "serialize_result_type.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
namespace internal {
|
||||||
|
template<class O>
|
||||||
|
struct order_by_t;
|
||||||
|
|
||||||
|
template<class T, class DBOs>
|
||||||
|
std::string serialize(const T&, const serializer_context<DBOs>&);
|
||||||
|
|
||||||
|
template<class T, class Ctx>
|
||||||
|
std::string serialize_order_by(const T&, const Ctx&);
|
||||||
|
|
||||||
|
inline void stream_sql_escaped(std::ostream& os, serialize_arg_type str, char char2Escape) {
|
||||||
|
for(size_t offset = 0, next; true; offset = next + 1) {
|
||||||
|
next = str.find(char2Escape, offset);
|
||||||
|
|
||||||
|
if(next == str.npos) {
|
||||||
|
os.write(str.data() + offset, str.size() - offset);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
os.write(str.data() + offset, next - offset + 1);
|
||||||
|
os.write(&char2Escape, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void stream_identifier(std::ostream& ss,
|
||||||
|
serialize_arg_type qualifier,
|
||||||
|
serialize_arg_type identifier,
|
||||||
|
serialize_arg_type alias) {
|
||||||
|
constexpr char quoteChar = '"';
|
||||||
|
constexpr char qualified[] = {quoteChar, '.', '\0'};
|
||||||
|
constexpr char aliased[] = {' ', quoteChar, '\0'};
|
||||||
|
|
||||||
|
// note: In practice, escaping double quotes in identifiers is arguably overkill,
|
||||||
|
// but since the SQLite grammar allows it, it's better to be safe than sorry.
|
||||||
|
|
||||||
|
if(!qualifier.empty()) {
|
||||||
|
ss << quoteChar;
|
||||||
|
stream_sql_escaped(ss, qualifier, quoteChar);
|
||||||
|
ss << qualified;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
ss << quoteChar;
|
||||||
|
stream_sql_escaped(ss, identifier, quoteChar);
|
||||||
|
ss << quoteChar;
|
||||||
|
}
|
||||||
|
if(!alias.empty()) {
|
||||||
|
ss << aliased;
|
||||||
|
stream_sql_escaped(ss, alias, quoteChar);
|
||||||
|
ss << quoteChar;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void stream_identifier(std::ostream& ss, const std::string& identifier, const std::string& alias) {
|
||||||
|
return stream_identifier(ss, "", identifier, alias);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void stream_identifier(std::ostream& ss, const std::string& identifier) {
|
||||||
|
return stream_identifier(ss, "", identifier, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Tpl, size_t... Is>
|
||||||
|
void stream_identifier(std::ostream& ss, const Tpl& tpl, std::index_sequence<Is...>) {
|
||||||
|
static_assert(sizeof...(Is) > 0 && sizeof...(Is) <= 3, "");
|
||||||
|
return stream_identifier(ss, std::get<Is>(tpl)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Tpl, std::enable_if_t<polyfill::is_detected_v<type_t, std::tuple_size<Tpl>>, bool> = true>
|
||||||
|
void stream_identifier(std::ostream& ss, const Tpl& tpl) {
|
||||||
|
return stream_identifier(ss, tpl, std::make_index_sequence<std::tuple_size<Tpl>::value>{});
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class stream_as {
|
||||||
|
conditions_tuple,
|
||||||
|
actions_tuple,
|
||||||
|
expressions_tuple,
|
||||||
|
dynamic_expressions,
|
||||||
|
serialized,
|
||||||
|
identifier,
|
||||||
|
identifiers,
|
||||||
|
values_placeholders,
|
||||||
|
table_columns,
|
||||||
|
non_generated_columns,
|
||||||
|
field_values_excluding,
|
||||||
|
mapped_columns_expressions,
|
||||||
|
column_constraints,
|
||||||
|
};
|
||||||
|
|
||||||
|
template<stream_as mode>
|
||||||
|
struct streaming {
|
||||||
|
template<class... Ts>
|
||||||
|
auto operator()(const Ts&... ts) const {
|
||||||
|
return std::forward_as_tuple(*this, ts...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<size_t... Idx>
|
||||||
|
constexpr std::index_sequence<1u + Idx...> offset_index(std::index_sequence<Idx...>) const {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
constexpr streaming<stream_as::conditions_tuple> streaming_conditions_tuple{};
|
||||||
|
constexpr streaming<stream_as::actions_tuple> streaming_actions_tuple{};
|
||||||
|
constexpr streaming<stream_as::expressions_tuple> streaming_expressions_tuple{};
|
||||||
|
constexpr streaming<stream_as::dynamic_expressions> streaming_dynamic_expressions{};
|
||||||
|
constexpr streaming<stream_as::serialized> streaming_serialized{};
|
||||||
|
constexpr streaming<stream_as::identifier> streaming_identifier{};
|
||||||
|
constexpr streaming<stream_as::identifiers> streaming_identifiers{};
|
||||||
|
constexpr streaming<stream_as::values_placeholders> streaming_values_placeholders{};
|
||||||
|
constexpr streaming<stream_as::table_columns> streaming_table_column_names{};
|
||||||
|
constexpr streaming<stream_as::non_generated_columns> streaming_non_generated_column_names{};
|
||||||
|
constexpr streaming<stream_as::field_values_excluding> streaming_field_values_excluding{};
|
||||||
|
constexpr streaming<stream_as::mapped_columns_expressions> streaming_mapped_columns_expressions{};
|
||||||
|
constexpr streaming<stream_as::column_constraints> streaming_column_constraints{};
|
||||||
|
|
||||||
|
// serialize and stream a tuple of condition expressions;
|
||||||
|
// space + space-separated
|
||||||
|
template<class T, class Ctx>
|
||||||
|
std::ostream& operator<<(std::ostream& ss,
|
||||||
|
std::tuple<const streaming<stream_as::conditions_tuple>&, T, Ctx> tpl) {
|
||||||
|
const auto& conditions = get<1>(tpl);
|
||||||
|
auto& context = get<2>(tpl);
|
||||||
|
|
||||||
|
iterate_tuple(conditions, [&ss, &context](auto& c) {
|
||||||
|
ss << " " << serialize(c, context);
|
||||||
|
});
|
||||||
|
return ss;
|
||||||
|
}
|
||||||
|
|
||||||
|
// serialize and stream a tuple of action expressions;
|
||||||
|
// space-separated
|
||||||
|
template<class T, class Ctx>
|
||||||
|
std::ostream& operator<<(std::ostream& ss, std::tuple<const streaming<stream_as::actions_tuple>&, T, Ctx> tpl) {
|
||||||
|
const auto& actions = get<1>(tpl);
|
||||||
|
auto& context = get<2>(tpl);
|
||||||
|
|
||||||
|
iterate_tuple(actions, [&ss, &context, first = true](auto& action) mutable {
|
||||||
|
constexpr std::array<const char*, 2> sep = {" ", ""};
|
||||||
|
ss << sep[std::exchange(first, false)] << serialize(action, context);
|
||||||
|
});
|
||||||
|
return ss;
|
||||||
|
}
|
||||||
|
|
||||||
|
// serialize and stream a tuple of expressions;
|
||||||
|
// comma-separated
|
||||||
|
template<class T, class Ctx>
|
||||||
|
std::ostream& operator<<(std::ostream& ss,
|
||||||
|
std::tuple<const streaming<stream_as::expressions_tuple>&, T, Ctx> tpl) {
|
||||||
|
const auto& args = get<1>(tpl);
|
||||||
|
auto& context = get<2>(tpl);
|
||||||
|
|
||||||
|
iterate_tuple(args, [&ss, &context, first = true](auto& arg) mutable {
|
||||||
|
constexpr std::array<const char*, 2> sep = {", ", ""};
|
||||||
|
ss << sep[std::exchange(first, false)] << serialize(arg, context);
|
||||||
|
});
|
||||||
|
return ss;
|
||||||
|
}
|
||||||
|
|
||||||
|
// serialize and stream multi_order_by arguments;
|
||||||
|
// comma-separated
|
||||||
|
template<class... Os, class Ctx>
|
||||||
|
std::ostream& operator<<(
|
||||||
|
std::ostream& ss,
|
||||||
|
std::tuple<const streaming<stream_as::expressions_tuple>&, const std::tuple<order_by_t<Os>...>&, Ctx> tpl) {
|
||||||
|
const auto& args = get<1>(tpl);
|
||||||
|
auto& context = get<2>(tpl);
|
||||||
|
|
||||||
|
iterate_tuple(args, [&ss, &context, first = true](auto& arg) mutable {
|
||||||
|
constexpr std::array<const char*, 2> sep = {", ", ""};
|
||||||
|
ss << sep[std::exchange(first, false)] << serialize_order_by(arg, context);
|
||||||
|
});
|
||||||
|
return ss;
|
||||||
|
}
|
||||||
|
|
||||||
|
// serialize and stream a vector of expressions;
|
||||||
|
// comma-separated
|
||||||
|
template<class C, class Ctx>
|
||||||
|
std::ostream& operator<<(std::ostream& ss,
|
||||||
|
std::tuple<const streaming<stream_as::dynamic_expressions>&, C, Ctx> tpl) {
|
||||||
|
const auto& args = get<1>(tpl);
|
||||||
|
auto& context = get<2>(tpl);
|
||||||
|
|
||||||
|
constexpr std::array<const char*, 2> sep = {", ", ""};
|
||||||
|
for(size_t i = 0, first = true; i < args.size(); ++i) {
|
||||||
|
ss << sep[std::exchange(first, false)] << serialize(args[i], context);
|
||||||
|
}
|
||||||
|
return ss;
|
||||||
|
}
|
||||||
|
|
||||||
|
// stream a vector of already serialized strings;
|
||||||
|
// comma-separated
|
||||||
|
template<class C>
|
||||||
|
std::ostream& operator<<(std::ostream& ss, std::tuple<const streaming<stream_as::serialized>&, C> tpl) {
|
||||||
|
const auto& strings = get<1>(tpl);
|
||||||
|
|
||||||
|
constexpr std::array<const char*, 2> sep = {", ", ""};
|
||||||
|
for(size_t i = 0, first = true; i < strings.size(); ++i) {
|
||||||
|
ss << sep[std::exchange(first, false)] << strings[i];
|
||||||
|
}
|
||||||
|
return ss;
|
||||||
|
}
|
||||||
|
|
||||||
|
// stream an identifier described by a variadic string pack, which is one of:
|
||||||
|
// 1. identifier
|
||||||
|
// 2. identifier, alias
|
||||||
|
// 3. qualifier, identifier, alias
|
||||||
|
template<class... Strings>
|
||||||
|
std::ostream& operator<<(std::ostream& ss,
|
||||||
|
std::tuple<const streaming<stream_as::identifier>&, Strings...> tpl) {
|
||||||
|
stream_identifier(ss, tpl, streaming_identifier.offset_index(std::index_sequence_for<Strings...>{}));
|
||||||
|
return ss;
|
||||||
|
}
|
||||||
|
|
||||||
|
// stream a container of identifiers described by a string or a tuple, which is one of:
|
||||||
|
// 1. identifier
|
||||||
|
// 1. tuple(identifier)
|
||||||
|
// 2. tuple(identifier, alias), pair(identifier, alias)
|
||||||
|
// 3. tuple(qualifier, identifier, alias)
|
||||||
|
//
|
||||||
|
// comma-separated
|
||||||
|
template<class C>
|
||||||
|
std::ostream& operator<<(std::ostream& ss, std::tuple<const streaming<stream_as::identifiers>&, C> tpl) {
|
||||||
|
const auto& identifiers = get<1>(tpl);
|
||||||
|
|
||||||
|
constexpr std::array<const char*, 2> sep = {", ", ""};
|
||||||
|
bool first = true;
|
||||||
|
for(auto& identifier: identifiers) {
|
||||||
|
ss << sep[std::exchange(first, false)];
|
||||||
|
stream_identifier(ss, identifier);
|
||||||
|
}
|
||||||
|
return ss;
|
||||||
|
}
|
||||||
|
|
||||||
|
// stream placeholders as part of a values clause
|
||||||
|
template<class... Ts>
|
||||||
|
std::ostream& operator<<(std::ostream& ss,
|
||||||
|
std::tuple<const streaming<stream_as::values_placeholders>&, Ts...> tpl) {
|
||||||
|
const size_t& columnsCount = get<1>(tpl);
|
||||||
|
const ptrdiff_t& valuesCount = get<2>(tpl);
|
||||||
|
|
||||||
|
if(!valuesCount || !columnsCount) {
|
||||||
|
return ss;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string result;
|
||||||
|
result.reserve((1 + (columnsCount * 1) + (columnsCount * 2 - 2) + 1) * valuesCount + (valuesCount * 2 - 2));
|
||||||
|
|
||||||
|
constexpr std::array<const char*, 2> sep = {", ", ""};
|
||||||
|
for(ptrdiff_t i = 0, first = true; i < valuesCount; ++i) {
|
||||||
|
result += sep[std::exchange(first, false)];
|
||||||
|
result += "(";
|
||||||
|
for(size_t i = 0, first = true; i < columnsCount; ++i) {
|
||||||
|
result += sep[std::exchange(first, false)];
|
||||||
|
result += "?";
|
||||||
|
}
|
||||||
|
result += ")";
|
||||||
|
}
|
||||||
|
ss << result;
|
||||||
|
return ss;
|
||||||
|
}
|
||||||
|
|
||||||
|
// stream a table's column identifiers, possibly qualified;
|
||||||
|
// comma-separated
|
||||||
|
template<class Table>
|
||||||
|
std::ostream& operator<<(std::ostream& ss,
|
||||||
|
std::tuple<const streaming<stream_as::table_columns>&, Table, const bool&> tpl) {
|
||||||
|
const auto& table = get<1>(tpl);
|
||||||
|
const bool& qualified = get<2>(tpl);
|
||||||
|
|
||||||
|
table.for_each_column([&ss, &tableName = qualified ? table.name : std::string{}, first = true](
|
||||||
|
const column_identifier& column) mutable {
|
||||||
|
constexpr std::array<const char*, 2> sep = {", ", ""};
|
||||||
|
ss << sep[std::exchange(first, false)];
|
||||||
|
stream_identifier(ss, tableName, column.name, std::string{});
|
||||||
|
});
|
||||||
|
return ss;
|
||||||
|
}
|
||||||
|
|
||||||
|
// stream a table's non-generated column identifiers, unqualified;
|
||||||
|
// comma-separated
|
||||||
|
template<class Table>
|
||||||
|
std::ostream& operator<<(std::ostream& ss,
|
||||||
|
std::tuple<const streaming<stream_as::non_generated_columns>&, Table> tpl) {
|
||||||
|
const auto& table = get<1>(tpl);
|
||||||
|
|
||||||
|
table.template for_each_column_excluding<is_generated_always>(
|
||||||
|
[&ss, first = true](const column_identifier& column) mutable {
|
||||||
|
constexpr std::array<const char*, 2> sep = {", ", ""};
|
||||||
|
ss << sep[std::exchange(first, false)];
|
||||||
|
stream_identifier(ss, column.name);
|
||||||
|
});
|
||||||
|
return ss;
|
||||||
|
}
|
||||||
|
|
||||||
|
// stream a table's non-generated column identifiers, unqualified;
|
||||||
|
// comma-separated
|
||||||
|
template<class PredFnCls, class L, class Ctx, class Obj>
|
||||||
|
std::ostream&
|
||||||
|
operator<<(std::ostream& ss,
|
||||||
|
std::tuple<const streaming<stream_as::field_values_excluding>&, PredFnCls, L, Ctx, Obj> tpl) {
|
||||||
|
using check_if_excluded = polyfill::remove_cvref_t<std::tuple_element_t<1, decltype(tpl)>>;
|
||||||
|
auto& excluded = get<2>(tpl);
|
||||||
|
auto& context = get<3>(tpl);
|
||||||
|
auto& object = get<4>(tpl);
|
||||||
|
using object_type = polyfill::remove_cvref_t<decltype(object)>;
|
||||||
|
auto& table = pick_table<object_type>(context.db_objects);
|
||||||
|
|
||||||
|
table.template for_each_column_excluding<check_if_excluded>(call_as_template_base<column_field>(
|
||||||
|
[&ss, &excluded, &context, &object, first = true](auto& column) mutable {
|
||||||
|
if(excluded(column)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr std::array<const char*, 2> sep = {", ", ""};
|
||||||
|
ss << sep[std::exchange(first, false)]
|
||||||
|
<< serialize(polyfill::invoke(column.member_pointer, object), context);
|
||||||
|
}));
|
||||||
|
return ss;
|
||||||
|
}
|
||||||
|
|
||||||
|
// stream a tuple of mapped columns (which are member pointers or column pointers);
|
||||||
|
// comma-separated
|
||||||
|
template<class T, class Ctx>
|
||||||
|
std::ostream& operator<<(std::ostream& ss,
|
||||||
|
std::tuple<const streaming<stream_as::mapped_columns_expressions>&, T, Ctx> tpl) {
|
||||||
|
const auto& columns = get<1>(tpl);
|
||||||
|
auto& context = get<2>(tpl);
|
||||||
|
|
||||||
|
iterate_tuple(columns, [&ss, &context, first = true](auto& colRef) mutable {
|
||||||
|
const std::string* columnName = find_column_name(context.db_objects, colRef);
|
||||||
|
if(!columnName) {
|
||||||
|
throw std::system_error{orm_error_code::column_not_found};
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr std::array<const char*, 2> sep = {", ", ""};
|
||||||
|
ss << sep[std::exchange(first, false)];
|
||||||
|
stream_identifier(ss, *columnName);
|
||||||
|
});
|
||||||
|
return ss;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class... Op, class Ctx>
|
||||||
|
std::ostream& operator<<(std::ostream& ss,
|
||||||
|
std::tuple<const streaming<stream_as::column_constraints>&,
|
||||||
|
const column_constraints<Op...>&,
|
||||||
|
const bool&,
|
||||||
|
Ctx> tpl) {
|
||||||
|
const auto& column = get<1>(tpl);
|
||||||
|
const bool& isNotNull = get<2>(tpl);
|
||||||
|
auto& context = get<3>(tpl);
|
||||||
|
|
||||||
|
using constraints_type = constraints_type_t<column_constraints<Op...>>;
|
||||||
|
constexpr size_t constraintsCount = std::tuple_size<constraints_type>::value;
|
||||||
|
if(constraintsCount) {
|
||||||
|
std::vector<std::string> constraintsStrings;
|
||||||
|
constraintsStrings.reserve(constraintsCount);
|
||||||
|
int primaryKeyIndex = -1;
|
||||||
|
int autoincrementIndex = -1;
|
||||||
|
int tupleIndex = 0;
|
||||||
|
iterate_tuple(column.constraints,
|
||||||
|
[&constraintsStrings, &primaryKeyIndex, &autoincrementIndex, &tupleIndex, &context](
|
||||||
|
auto& constraint) {
|
||||||
|
using constraint_type = std::decay_t<decltype(constraint)>;
|
||||||
|
constraintsStrings.push_back(serialize(constraint, context));
|
||||||
|
if(is_primary_key_v<constraint_type>) {
|
||||||
|
primaryKeyIndex = tupleIndex;
|
||||||
|
} else if(is_autoincrement_v<constraint_type>) {
|
||||||
|
autoincrementIndex = tupleIndex;
|
||||||
|
}
|
||||||
|
++tupleIndex;
|
||||||
|
});
|
||||||
|
if(primaryKeyIndex != -1 && autoincrementIndex != -1 && autoincrementIndex < primaryKeyIndex) {
|
||||||
|
iter_swap(constraintsStrings.begin() + primaryKeyIndex,
|
||||||
|
constraintsStrings.begin() + autoincrementIndex);
|
||||||
|
}
|
||||||
|
for(auto& str: constraintsStrings) {
|
||||||
|
ss << str << ' ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(isNotNull) {
|
||||||
|
ss << "NOT NULL ";
|
||||||
|
}
|
||||||
|
|
||||||
|
return ss;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,361 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <sqlite3.h>
|
||||||
|
#include <type_traits> // std::enable_if_t, std::is_arithmetic, std::is_same, std::true_type, std::false_type, std::make_index_sequence, std::index_sequence
|
||||||
|
#include <memory> // std::default_delete
|
||||||
|
#include <string> // std::string, std::wstring
|
||||||
|
#include <vector> // std::vector
|
||||||
|
#include <cstring> // ::strncpy, ::strlen
|
||||||
|
#include "functional/cxx_string_view.h"
|
||||||
|
#ifndef SQLITE_ORM_STRING_VIEW_SUPPORTED
|
||||||
|
#include <cwchar> // ::wcsncpy, ::wcslen
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "functional/cxx_universal.h"
|
||||||
|
#include "functional/cxx_type_traits_polyfill.h"
|
||||||
|
#include "functional/cxx_functional_polyfill.h"
|
||||||
|
#include "is_std_ptr.h"
|
||||||
|
#include "tuple_helper/tuple_filter.h"
|
||||||
|
#include "error_code.h"
|
||||||
|
#include "arithmetic_tag.h"
|
||||||
|
#include "xdestroy_handling.h"
|
||||||
|
#include "pointer_value.h"
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class used for binding fields to sqlite3 statements.
|
||||||
|
*/
|
||||||
|
template<class V, typename Enable = void>
|
||||||
|
struct statement_binder;
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
template<class T, class SFINAE = void>
|
||||||
|
SQLITE_ORM_INLINE_VAR constexpr bool is_bindable_v = false;
|
||||||
|
template<class T>
|
||||||
|
SQLITE_ORM_INLINE_VAR constexpr bool is_bindable_v<T, polyfill::void_t<decltype(statement_binder<T>())>> = true
|
||||||
|
// note : msvc 14.0 needs the parentheses constructor, otherwise `is_bindable<const char*>` isn't recognised.
|
||||||
|
// The strangest thing is that this is mutually exclusive with `is_printable_v`.
|
||||||
|
;
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
using is_bindable = polyfill::bool_constant<is_bindable_v<T>>;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specialization for 'pointer-passing interface'.
|
||||||
|
*/
|
||||||
|
template<class P, class T, class D>
|
||||||
|
struct statement_binder<pointer_binding<P, T, D>, void> {
|
||||||
|
using V = pointer_binding<P, T, D>;
|
||||||
|
|
||||||
|
// ownership of pointed-to-object is left untouched and remains at prepared statement's AST expression
|
||||||
|
int bind(sqlite3_stmt* stmt, int index, const V& value) const {
|
||||||
|
// note: C-casting `P* -> void*`, internal::xdestroy_proxy() does the inverse
|
||||||
|
return sqlite3_bind_pointer(stmt, index, (void*)value.ptr(), T::value, null_xdestroy_f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ownership of pointed-to-object is transferred to sqlite
|
||||||
|
void result(sqlite3_context* context, V& value) const {
|
||||||
|
// note: C-casting `P* -> void*`,
|
||||||
|
// row_extractor<pointer_arg<P, T>>::extract() and internal::xdestroy_proxy() do the inverse
|
||||||
|
sqlite3_result_pointer(context, (void*)value.take_ptr(), T::value, value.get_xdestroy());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specialization for arithmetic types.
|
||||||
|
*/
|
||||||
|
template<class V>
|
||||||
|
struct statement_binder<V, std::enable_if_t<std::is_arithmetic<V>::value>> {
|
||||||
|
|
||||||
|
int bind(sqlite3_stmt* stmt, int index, const V& value) const {
|
||||||
|
return this->bind(stmt, index, value, tag());
|
||||||
|
}
|
||||||
|
|
||||||
|
void result(sqlite3_context* context, const V& value) const {
|
||||||
|
this->result(context, value, tag());
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
using tag = arithmetic_tag_t<V>;
|
||||||
|
|
||||||
|
int bind(sqlite3_stmt* stmt, int index, const V& value, int_or_smaller_tag) const {
|
||||||
|
return sqlite3_bind_int(stmt, index, static_cast<int>(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
void result(sqlite3_context* context, const V& value, int_or_smaller_tag) const {
|
||||||
|
sqlite3_result_int(context, static_cast<int>(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
int bind(sqlite3_stmt* stmt, int index, const V& value, bigint_tag) const {
|
||||||
|
return sqlite3_bind_int64(stmt, index, static_cast<sqlite3_int64>(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
void result(sqlite3_context* context, const V& value, bigint_tag) const {
|
||||||
|
sqlite3_result_int64(context, static_cast<sqlite3_int64>(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
int bind(sqlite3_stmt* stmt, int index, const V& value, real_tag) const {
|
||||||
|
return sqlite3_bind_double(stmt, index, static_cast<double>(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
void result(sqlite3_context* context, const V& value, real_tag) const {
|
||||||
|
sqlite3_result_double(context, static_cast<double>(value));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specialization for std::string and C-string.
|
||||||
|
*/
|
||||||
|
template<class V>
|
||||||
|
struct statement_binder<V,
|
||||||
|
std::enable_if_t<polyfill::disjunction_v<std::is_base_of<std::string, V>,
|
||||||
|
std::is_same<V, const char*>
|
||||||
|
#ifdef SQLITE_ORM_STRING_VIEW_SUPPORTED
|
||||||
|
,
|
||||||
|
std::is_same<V, std::string_view>
|
||||||
|
#endif
|
||||||
|
>>> {
|
||||||
|
|
||||||
|
int bind(sqlite3_stmt* stmt, int index, const V& value) const {
|
||||||
|
auto stringData = this->string_data(value);
|
||||||
|
return sqlite3_bind_text(stmt, index, stringData.first, stringData.second, SQLITE_TRANSIENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void result(sqlite3_context* context, const V& value) const {
|
||||||
|
auto stringData = this->string_data(value);
|
||||||
|
auto dataCopy = new char[stringData.second + 1];
|
||||||
|
constexpr auto deleter = std::default_delete<char[]>{};
|
||||||
|
::strncpy(dataCopy, stringData.first, stringData.second + 1);
|
||||||
|
sqlite3_result_text(context, dataCopy, stringData.second, obtain_xdestroy_for(deleter, dataCopy));
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
#ifdef SQLITE_ORM_STRING_VIEW_SUPPORTED
|
||||||
|
std::pair<const char*, int> string_data(const std::string_view& s) const {
|
||||||
|
return {s.data(), int(s.size())};
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
std::pair<const char*, int> string_data(const std::string& s) const {
|
||||||
|
return {s.c_str(), int(s.size())};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<const char*, int> string_data(const char* s) const {
|
||||||
|
return {s, int(::strlen(s))};
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifndef SQLITE_ORM_OMITS_CODECVT
|
||||||
|
template<class V>
|
||||||
|
struct statement_binder<V,
|
||||||
|
std::enable_if_t<polyfill::disjunction_v<std::is_base_of<std::wstring, V>,
|
||||||
|
std::is_same<V, const wchar_t*>
|
||||||
|
#ifdef SQLITE_ORM_STRING_VIEW_SUPPORTED
|
||||||
|
,
|
||||||
|
std::is_same<V, std::wstring_view>
|
||||||
|
#endif
|
||||||
|
>>> {
|
||||||
|
|
||||||
|
int bind(sqlite3_stmt* stmt, int index, const V& value) const {
|
||||||
|
auto stringData = this->string_data(value);
|
||||||
|
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
|
||||||
|
std::string utf8Str = converter.to_bytes(stringData.first, stringData.first + stringData.second);
|
||||||
|
return statement_binder<decltype(utf8Str)>().bind(stmt, index, utf8Str);
|
||||||
|
}
|
||||||
|
|
||||||
|
void result(sqlite3_context* context, const V& value) const {
|
||||||
|
auto stringData = this->string_data(value);
|
||||||
|
sqlite3_result_text16(context, stringData.first, stringData.second, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
#ifdef SQLITE_ORM_STRING_VIEW_SUPPORTED
|
||||||
|
std::pair<const wchar_t*, int> string_data(const std::wstring_view& s) const {
|
||||||
|
return {s.data(), int(s.size())};
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
std::pair<const wchar_t*, int> string_data(const std::wstring& s) const {
|
||||||
|
return {s.c_str(), int(s.size())};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<const wchar_t*, int> string_data(const wchar_t* s) const {
|
||||||
|
return {s, int(::wcslen(s))};
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specialization for nullptr_t.
|
||||||
|
*/
|
||||||
|
template<>
|
||||||
|
struct statement_binder<nullptr_t, void> {
|
||||||
|
int bind(sqlite3_stmt* stmt, int index, const nullptr_t&) const {
|
||||||
|
return sqlite3_bind_null(stmt, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
void result(sqlite3_context* context, const nullptr_t&) const {
|
||||||
|
sqlite3_result_null(context);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef SQLITE_ORM_OPTIONAL_SUPPORTED
|
||||||
|
/**
|
||||||
|
* Specialization for std::nullopt_t.
|
||||||
|
*/
|
||||||
|
template<>
|
||||||
|
struct statement_binder<std::nullopt_t, void> {
|
||||||
|
int bind(sqlite3_stmt* stmt, int index, const std::nullopt_t&) const {
|
||||||
|
return sqlite3_bind_null(stmt, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
void result(sqlite3_context* context, const std::nullopt_t&) const {
|
||||||
|
sqlite3_result_null(context);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#endif // SQLITE_ORM_OPTIONAL_SUPPORTED
|
||||||
|
|
||||||
|
template<class V>
|
||||||
|
struct statement_binder<
|
||||||
|
V,
|
||||||
|
std::enable_if_t<is_std_ptr<V>::value && internal::is_bindable_v<std::remove_cv_t<typename V::element_type>>>> {
|
||||||
|
using unqualified_type = std::remove_cv_t<typename V::element_type>;
|
||||||
|
|
||||||
|
int bind(sqlite3_stmt* stmt, int index, const V& value) const {
|
||||||
|
if(value) {
|
||||||
|
return statement_binder<unqualified_type>().bind(stmt, index, *value);
|
||||||
|
} else {
|
||||||
|
return statement_binder<nullptr_t>().bind(stmt, index, nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specialization for binary data (std::vector<char>).
|
||||||
|
*/
|
||||||
|
template<>
|
||||||
|
struct statement_binder<std::vector<char>, void> {
|
||||||
|
int bind(sqlite3_stmt* stmt, int index, const std::vector<char>& value) const {
|
||||||
|
if(!value.empty()) {
|
||||||
|
return sqlite3_bind_blob(stmt, index, (const void*)&value.front(), int(value.size()), SQLITE_TRANSIENT);
|
||||||
|
} else {
|
||||||
|
return sqlite3_bind_blob(stmt, index, "", 0, SQLITE_TRANSIENT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void result(sqlite3_context* context, const std::vector<char>& value) const {
|
||||||
|
if(!value.empty()) {
|
||||||
|
sqlite3_result_blob(context, (const void*)&value.front(), int(value.size()), nullptr);
|
||||||
|
} else {
|
||||||
|
sqlite3_result_blob(context, "", 0, nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef SQLITE_ORM_OPTIONAL_SUPPORTED
|
||||||
|
template<class V>
|
||||||
|
struct statement_binder<V,
|
||||||
|
std::enable_if_t<polyfill::is_specialization_of_v<V, std::optional> &&
|
||||||
|
internal::is_bindable_v<std::remove_cv_t<typename V::value_type>>>> {
|
||||||
|
using unqualified_type = std::remove_cv_t<typename V::value_type>;
|
||||||
|
|
||||||
|
int bind(sqlite3_stmt* stmt, int index, const V& value) const {
|
||||||
|
if(value) {
|
||||||
|
return statement_binder<unqualified_type>().bind(stmt, index, *value);
|
||||||
|
} else {
|
||||||
|
return statement_binder<std::nullopt_t>().bind(stmt, index, std::nullopt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#endif // SQLITE_ORM_OPTIONAL_SUPPORTED
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
struct conditional_binder {
|
||||||
|
sqlite3_stmt* stmt = nullptr;
|
||||||
|
int index = 1;
|
||||||
|
|
||||||
|
explicit conditional_binder(sqlite3_stmt* stmt) : stmt{stmt} {}
|
||||||
|
|
||||||
|
template<class T, satisfies<is_bindable, T> = true>
|
||||||
|
void operator()(const T& t) {
|
||||||
|
int rc = statement_binder<T>{}.bind(this->stmt, this->index++, t);
|
||||||
|
if(SQLITE_OK != rc) {
|
||||||
|
throw_translated_sqlite_error(this->stmt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T, satisfies_not<is_bindable, T> = true>
|
||||||
|
void operator()(const T&) const {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct field_value_binder : conditional_binder {
|
||||||
|
using conditional_binder::conditional_binder;
|
||||||
|
using conditional_binder::operator();
|
||||||
|
|
||||||
|
template<class T, satisfies_not<is_bindable, T> = true>
|
||||||
|
void operator()(const T&) const = delete;
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
void operator()(const T* value) {
|
||||||
|
if(!value) {
|
||||||
|
throw std::system_error{orm_error_code::value_is_null};
|
||||||
|
}
|
||||||
|
(*this)(*value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct tuple_value_binder {
|
||||||
|
sqlite3_stmt* stmt = nullptr;
|
||||||
|
|
||||||
|
explicit tuple_value_binder(sqlite3_stmt* stmt) : stmt{stmt} {}
|
||||||
|
|
||||||
|
template<class Tpl, class Projection>
|
||||||
|
void operator()(const Tpl& tpl, Projection project) const {
|
||||||
|
(*this)(tpl,
|
||||||
|
std::make_index_sequence<std::tuple_size<Tpl>::value>{},
|
||||||
|
std::forward<Projection>(project));
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
#ifdef SQLITE_ORM_FOLD_EXPRESSIONS_SUPPORTED
|
||||||
|
template<class Tpl, size_t... Idx, class Projection>
|
||||||
|
void operator()(const Tpl& tpl, std::index_sequence<Idx...>, Projection project) const {
|
||||||
|
(this->bind(polyfill::invoke(project, std::get<Idx>(tpl)), Idx), ...);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
template<class Tpl, size_t I, size_t... Idx, class Projection>
|
||||||
|
void operator()(const Tpl& tpl, std::index_sequence<I, Idx...>, Projection project) const {
|
||||||
|
this->bind(polyfill::invoke(project, std::get<I>(tpl)), I);
|
||||||
|
(*this)(tpl, std::index_sequence<Idx...>{}, std::forward<Projection>(project));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Tpl, class Projection>
|
||||||
|
void operator()(const Tpl&, std::index_sequence<>, Projection) const {}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
void bind(const T& t, size_t idx) const {
|
||||||
|
int rc = statement_binder<T>{}.bind(this->stmt, int(idx + 1), t);
|
||||||
|
if(SQLITE_OK != rc) {
|
||||||
|
throw_translated_sqlite_error(this->stmt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
void bind(const T* value, size_t idx) const {
|
||||||
|
if(!value) {
|
||||||
|
throw std::system_error{orm_error_code::value_is_null};
|
||||||
|
}
|
||||||
|
(*this)(*value, idx);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class Tpl>
|
||||||
|
using bindable_filter_t = filter_tuple_t<Tpl, is_bindable>;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <sqlite3.h>
|
||||||
|
#include <memory> // std::unique_ptr
|
||||||
|
#include <type_traits> // std::integral_constant
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Guard class which finalizes `sqlite3_stmt` in dtor
|
||||||
|
*/
|
||||||
|
using statement_finalizer =
|
||||||
|
std::unique_ptr<sqlite3_stmt, std::integral_constant<decltype(&sqlite3_finalize), sqlite3_finalize>>;
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,790 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <sqlite3.h>
|
||||||
|
#include <functional> // std::function, std::bind
|
||||||
|
#include <string> // std::string
|
||||||
|
#include <sstream> // std::stringstream
|
||||||
|
#include <utility> // std::move
|
||||||
|
#include <system_error> // std::system_error
|
||||||
|
#include <vector> // std::vector
|
||||||
|
#include <memory> // std::make_unique, std::unique_ptr
|
||||||
|
#include <map> // std::map
|
||||||
|
#include <type_traits> // std::decay, std::is_same
|
||||||
|
#include <algorithm> // std::find_if
|
||||||
|
|
||||||
|
#include "functional/cxx_universal.h"
|
||||||
|
#include "functional/static_magic.h"
|
||||||
|
#include "tuple_helper/tuple_iteration.h"
|
||||||
|
#include "pragma.h"
|
||||||
|
#include "limit_accessor.h"
|
||||||
|
#include "transaction_guard.h"
|
||||||
|
#include "row_extractor.h"
|
||||||
|
#include "connection_holder.h"
|
||||||
|
#include "backup.h"
|
||||||
|
#include "function.h"
|
||||||
|
#include "values_to_tuple.h"
|
||||||
|
#include "arg_values.h"
|
||||||
|
#include "util.h"
|
||||||
|
#include "serializing_util.h"
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
struct storage_base {
|
||||||
|
using collating_function = std::function<int(int, const void*, int, const void*)>;
|
||||||
|
|
||||||
|
std::function<void(sqlite3*)> on_open;
|
||||||
|
pragma_t pragma;
|
||||||
|
limit_accessor limit;
|
||||||
|
|
||||||
|
transaction_guard_t transaction_guard() {
|
||||||
|
this->begin_transaction();
|
||||||
|
return {this->get_connection(),
|
||||||
|
std::bind(&storage_base::commit, this),
|
||||||
|
std::bind(&storage_base::rollback, this)};
|
||||||
|
}
|
||||||
|
|
||||||
|
void drop_index(const std::string& indexName) {
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "DROP INDEX " << quote_identifier(indexName) << std::flush;
|
||||||
|
perform_void_exec(this->get_connection().get(), ss.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void drop_trigger(const std::string& triggerName) {
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "DROP TRIGGER " << quote_identifier(triggerName) << std::flush;
|
||||||
|
perform_void_exec(this->get_connection().get(), ss.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void vacuum() {
|
||||||
|
perform_void_exec(this->get_connection().get(), "VACUUM");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drops table with given name.
|
||||||
|
*/
|
||||||
|
void drop_table(const std::string& tableName) {
|
||||||
|
this->drop_table_internal(this->get_connection().get(), tableName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rename table named `from` to `to`.
|
||||||
|
*/
|
||||||
|
void rename_table(const std::string& from, const std::string& to) {
|
||||||
|
this->rename_table(this->get_connection().get(), from, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void rename_table(sqlite3* db, const std::string& oldName, const std::string& newName) const {
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "ALTER TABLE " << streaming_identifier(oldName) << " RENAME TO " << streaming_identifier(newName)
|
||||||
|
<< std::flush;
|
||||||
|
perform_void_exec(db, ss.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether table exists in db. Doesn't check storage itself - works only with actual database.
|
||||||
|
* Note: table can be not mapped to a storage
|
||||||
|
* @return true if table with a given name exists in db, false otherwise.
|
||||||
|
*/
|
||||||
|
bool table_exists(const std::string& tableName) {
|
||||||
|
auto con = this->get_connection();
|
||||||
|
return this->table_exists(con.get(), tableName);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool table_exists(sqlite3* db, const std::string& tableName) const {
|
||||||
|
bool result = false;
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "SELECT COUNT(*) FROM sqlite_master WHERE type = " << streaming_identifier("table")
|
||||||
|
<< " AND name = " << quote_string_literal(tableName) << std::flush;
|
||||||
|
perform_exec(
|
||||||
|
db,
|
||||||
|
ss.str(),
|
||||||
|
[](void* data, int argc, char** argv, char** /*azColName*/) -> int {
|
||||||
|
auto& res = *(bool*)data;
|
||||||
|
if(argc) {
|
||||||
|
res = !!std::atoi(argv[0]);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
&result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void add_generated_cols(std::vector<const table_xinfo*>& columnsToAdd,
|
||||||
|
const std::vector<table_xinfo>& storageTableInfo) {
|
||||||
|
// iterate through storage columns
|
||||||
|
for(const table_xinfo& storageColumnInfo: storageTableInfo) {
|
||||||
|
if(storageColumnInfo.hidden) {
|
||||||
|
columnsToAdd.push_back(&storageColumnInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* sqlite3_changes function.
|
||||||
|
*/
|
||||||
|
int changes() {
|
||||||
|
auto con = this->get_connection();
|
||||||
|
return sqlite3_changes(con.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sqlite3_total_changes function.
|
||||||
|
*/
|
||||||
|
int total_changes() {
|
||||||
|
auto con = this->get_connection();
|
||||||
|
return sqlite3_total_changes(con.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
int64 last_insert_rowid() {
|
||||||
|
auto con = this->get_connection();
|
||||||
|
return sqlite3_last_insert_rowid(con.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
int busy_timeout(int ms) {
|
||||||
|
auto con = this->get_connection();
|
||||||
|
return sqlite3_busy_timeout(con.get(), ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns libsqlite3 version, not sqlite_orm
|
||||||
|
*/
|
||||||
|
std::string libversion() {
|
||||||
|
return sqlite3_libversion();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool transaction(const std::function<bool()>& f) {
|
||||||
|
auto guard = this->transaction_guard();
|
||||||
|
return guard.commit_on_destroy = f();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string current_timestamp() {
|
||||||
|
auto con = this->get_connection();
|
||||||
|
return this->current_timestamp(con.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
#if SQLITE_VERSION_NUMBER >= 3007010
|
||||||
|
/**
|
||||||
|
* \fn db_release_memory
|
||||||
|
* \brief Releases freeable memory of database. It is function can/should be called periodically by
|
||||||
|
* application, if application has less memory usage constraint. \note sqlite3_db_release_memory added
|
||||||
|
* in 3.7.10 https://sqlite.org/changes.html
|
||||||
|
*/
|
||||||
|
int db_release_memory() {
|
||||||
|
auto con = this->get_connection();
|
||||||
|
return sqlite3_db_release_memory(con.get());
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns existing permanent table names in database. Doesn't check storage itself - works only with
|
||||||
|
* actual database.
|
||||||
|
* @return Returns list of tables in database.
|
||||||
|
*/
|
||||||
|
std::vector<std::string> table_names() {
|
||||||
|
auto con = this->get_connection();
|
||||||
|
std::vector<std::string> tableNames;
|
||||||
|
using data_t = std::vector<std::string>;
|
||||||
|
perform_exec(
|
||||||
|
con.get(),
|
||||||
|
"SELECT name FROM sqlite_master WHERE type='table'",
|
||||||
|
[](void* data, int argc, char** argv, char** /*columnName*/) -> int {
|
||||||
|
auto& tableNames_ = *(data_t*)data;
|
||||||
|
for(int i = 0; i < argc; ++i) {
|
||||||
|
if(argv[i]) {
|
||||||
|
tableNames_.emplace_back(argv[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
&tableNames);
|
||||||
|
return tableNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
void open_forever() {
|
||||||
|
this->isOpenedForever = true;
|
||||||
|
this->connection->retain();
|
||||||
|
if(1 == this->connection->retain_count()) {
|
||||||
|
this->on_open_internal(this->connection->get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call this to create user defined scalar function. Can be called at any time no matter connection is opened or no.
|
||||||
|
* T - function class. T must have operator() overload and static name function like this:
|
||||||
|
* ```
|
||||||
|
* struct SqrtFunction {
|
||||||
|
*
|
||||||
|
* double operator()(double arg) const {
|
||||||
|
* return std::sqrt(arg);
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* static const char *name() {
|
||||||
|
* return "SQRT";
|
||||||
|
* }
|
||||||
|
* };
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Note: Currently, a function's name must not contain white-space characters, because it doesn't get quoted.
|
||||||
|
*/
|
||||||
|
template<class F>
|
||||||
|
void create_scalar_function() {
|
||||||
|
static_assert(is_scalar_function_v<F>, "F can't be an aggregate function");
|
||||||
|
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << F::name() << std::flush;
|
||||||
|
auto name = ss.str();
|
||||||
|
using args_tuple = typename callable_arguments<F>::args_tuple;
|
||||||
|
using return_type = typename callable_arguments<F>::return_type;
|
||||||
|
constexpr auto argsCount = std::is_same<args_tuple, std::tuple<arg_values>>::value
|
||||||
|
? -1
|
||||||
|
: int(std::tuple_size<args_tuple>::value);
|
||||||
|
this->scalarFunctions.emplace_back(new user_defined_scalar_function_t{
|
||||||
|
std::move(name),
|
||||||
|
argsCount,
|
||||||
|
[]() -> int* {
|
||||||
|
return (int*)(new F());
|
||||||
|
},
|
||||||
|
/* call = */
|
||||||
|
[](sqlite3_context* context, void* functionVoidPointer, int argsCount, sqlite3_value** values) {
|
||||||
|
auto& function = *static_cast<F*>(functionVoidPointer);
|
||||||
|
args_tuple argsTuple;
|
||||||
|
values_to_tuple{}(values, argsTuple, argsCount);
|
||||||
|
auto result = call(function, std::move(argsTuple));
|
||||||
|
statement_binder<return_type>().result(context, result);
|
||||||
|
},
|
||||||
|
delete_function_callback<F>,
|
||||||
|
});
|
||||||
|
|
||||||
|
if(this->connection->retain_count() > 0) {
|
||||||
|
sqlite3* db = this->connection->get();
|
||||||
|
try_to_create_function(db,
|
||||||
|
static_cast<user_defined_scalar_function_t&>(*this->scalarFunctions.back()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call this to create user defined aggregate function. Can be called at any time no matter connection is opened or no.
|
||||||
|
* T - function class. T must have step member function, fin member function and static name function like this:
|
||||||
|
* ```
|
||||||
|
* struct MeanFunction {
|
||||||
|
* double total = 0;
|
||||||
|
* int count = 0;
|
||||||
|
*
|
||||||
|
* void step(double value) {
|
||||||
|
* total += value;
|
||||||
|
* ++count;
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* int fin() const {
|
||||||
|
* return total / count;
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* static std::string name() {
|
||||||
|
* return "MEAN";
|
||||||
|
* }
|
||||||
|
* };
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Note: Currently, a function's name must not contain white-space characters, because it doesn't get quoted.
|
||||||
|
*/
|
||||||
|
template<class F>
|
||||||
|
void create_aggregate_function() {
|
||||||
|
static_assert(is_aggregate_function_v<F>, "F can't be a scalar function");
|
||||||
|
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << F::name() << std::flush;
|
||||||
|
auto name = ss.str();
|
||||||
|
using args_tuple = typename callable_arguments<F>::args_tuple;
|
||||||
|
using return_type = typename callable_arguments<F>::return_type;
|
||||||
|
constexpr auto argsCount = std::is_same<args_tuple, std::tuple<arg_values>>::value
|
||||||
|
? -1
|
||||||
|
: int(std::tuple_size<args_tuple>::value);
|
||||||
|
this->aggregateFunctions.emplace_back(new user_defined_aggregate_function_t{
|
||||||
|
std::move(name),
|
||||||
|
argsCount,
|
||||||
|
/* create = */
|
||||||
|
[]() -> int* {
|
||||||
|
return (int*)(new F());
|
||||||
|
},
|
||||||
|
/* step = */
|
||||||
|
[](sqlite3_context*, void* functionVoidPointer, int argsCount, sqlite3_value** values) {
|
||||||
|
auto& function = *static_cast<F*>(functionVoidPointer);
|
||||||
|
args_tuple argsTuple;
|
||||||
|
values_to_tuple{}(values, argsTuple, argsCount);
|
||||||
|
call(function, &F::step, std::move(argsTuple));
|
||||||
|
},
|
||||||
|
/* finalCall = */
|
||||||
|
[](sqlite3_context* context, void* functionVoidPointer) {
|
||||||
|
auto& function = *static_cast<F*>(functionVoidPointer);
|
||||||
|
auto result = function.fin();
|
||||||
|
statement_binder<return_type>().result(context, result);
|
||||||
|
},
|
||||||
|
delete_function_callback<F>,
|
||||||
|
});
|
||||||
|
|
||||||
|
if(this->connection->retain_count() > 0) {
|
||||||
|
sqlite3* db = this->connection->get();
|
||||||
|
try_to_create_function(
|
||||||
|
db,
|
||||||
|
static_cast<user_defined_aggregate_function_t&>(*this->aggregateFunctions.back()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use it to delete scalar function you created before. Can be called at any time no matter connection is open or no.
|
||||||
|
*/
|
||||||
|
template<class F>
|
||||||
|
void delete_scalar_function() {
|
||||||
|
static_assert(is_scalar_function_v<F>, "F cannot be an aggregate function");
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << F::name() << std::flush;
|
||||||
|
this->delete_function_impl(ss.str(), this->scalarFunctions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use it to delete aggregate function you created before. Can be called at any time no matter connection is open or no.
|
||||||
|
*/
|
||||||
|
template<class F>
|
||||||
|
void delete_aggregate_function() {
|
||||||
|
static_assert(is_aggregate_function_v<F>, "F cannot be a scalar function");
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << F::name() << std::flush;
|
||||||
|
this->delete_function_impl(ss.str(), this->aggregateFunctions);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class C>
|
||||||
|
void create_collation() {
|
||||||
|
collating_function func = [](int leftLength, const void* lhs, int rightLength, const void* rhs) {
|
||||||
|
C collatingObject;
|
||||||
|
return collatingObject(leftLength, lhs, rightLength, rhs);
|
||||||
|
};
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << C::name() << std::flush;
|
||||||
|
this->create_collation(ss.str(), std::move(func));
|
||||||
|
}
|
||||||
|
|
||||||
|
void create_collation(const std::string& name, collating_function f) {
|
||||||
|
collating_function* function = nullptr;
|
||||||
|
const auto functionExists = bool(f);
|
||||||
|
if(functionExists) {
|
||||||
|
function = &(collatingFunctions[name] = std::move(f));
|
||||||
|
} else {
|
||||||
|
collatingFunctions.erase(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// create collations if db is open
|
||||||
|
if(this->connection->retain_count() > 0) {
|
||||||
|
sqlite3* db = this->connection->get();
|
||||||
|
auto resultCode = sqlite3_create_collation(db,
|
||||||
|
name.c_str(),
|
||||||
|
SQLITE_UTF8,
|
||||||
|
function,
|
||||||
|
functionExists ? collate_callback : nullptr);
|
||||||
|
if(resultCode != SQLITE_OK) {
|
||||||
|
throw_translated_sqlite_error(db);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class C>
|
||||||
|
void delete_collation() {
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << C::name() << std::flush;
|
||||||
|
this->create_collation(ss.str(), {});
|
||||||
|
}
|
||||||
|
|
||||||
|
void begin_transaction() {
|
||||||
|
this->begin_transaction_internal("BEGIN TRANSACTION");
|
||||||
|
}
|
||||||
|
|
||||||
|
void begin_deferred_transaction() {
|
||||||
|
this->begin_transaction_internal("BEGIN DEFERRED TRANSACTION");
|
||||||
|
}
|
||||||
|
|
||||||
|
void begin_immediate_transaction() {
|
||||||
|
this->begin_transaction_internal("BEGIN IMMEDIATE TRANSACTION");
|
||||||
|
}
|
||||||
|
|
||||||
|
void begin_exclusive_transaction() {
|
||||||
|
this->begin_transaction_internal("BEGIN EXCLUSIVE TRANSACTION");
|
||||||
|
}
|
||||||
|
|
||||||
|
void commit() {
|
||||||
|
sqlite3* db = this->connection->get();
|
||||||
|
perform_void_exec(db, "COMMIT");
|
||||||
|
this->connection->release();
|
||||||
|
if(this->connection->retain_count() < 0) {
|
||||||
|
throw std::system_error{orm_error_code::no_active_transaction};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void rollback() {
|
||||||
|
sqlite3* db = this->connection->get();
|
||||||
|
perform_void_exec(db, "ROLLBACK");
|
||||||
|
this->connection->release();
|
||||||
|
if(this->connection->retain_count() < 0) {
|
||||||
|
throw std::system_error{orm_error_code::no_active_transaction};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void backup_to(const std::string& filename) {
|
||||||
|
auto backup = this->make_backup_to(filename);
|
||||||
|
backup.step(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void backup_to(storage_base& other) {
|
||||||
|
auto backup = this->make_backup_to(other);
|
||||||
|
backup.step(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void backup_from(const std::string& filename) {
|
||||||
|
auto backup = this->make_backup_from(filename);
|
||||||
|
backup.step(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void backup_from(storage_base& other) {
|
||||||
|
auto backup = this->make_backup_from(other);
|
||||||
|
backup.step(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
backup_t make_backup_to(const std::string& filename) {
|
||||||
|
auto holder = std::make_unique<connection_holder>(filename);
|
||||||
|
connection_ref conRef{*holder};
|
||||||
|
return {conRef, "main", this->get_connection(), "main", std::move(holder)};
|
||||||
|
}
|
||||||
|
|
||||||
|
backup_t make_backup_to(storage_base& other) {
|
||||||
|
return {other.get_connection(), "main", this->get_connection(), "main", {}};
|
||||||
|
}
|
||||||
|
|
||||||
|
backup_t make_backup_from(const std::string& filename) {
|
||||||
|
auto holder = std::make_unique<connection_holder>(filename);
|
||||||
|
connection_ref conRef{*holder};
|
||||||
|
return {this->get_connection(), "main", conRef, "main", std::move(holder)};
|
||||||
|
}
|
||||||
|
|
||||||
|
backup_t make_backup_from(storage_base& other) {
|
||||||
|
return {this->get_connection(), "main", other.get_connection(), "main", {}};
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& filename() const {
|
||||||
|
return this->connection->filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether connection to database is opened right now.
|
||||||
|
* Returns always `true` for in memory databases.
|
||||||
|
*/
|
||||||
|
bool is_opened() const {
|
||||||
|
return this->connection->retain_count() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* returning false when there is a transaction in place
|
||||||
|
* otherwise true; function is not const because it has to call get_connection()
|
||||||
|
*/
|
||||||
|
bool get_autocommit() {
|
||||||
|
auto con = this->get_connection();
|
||||||
|
return sqlite3_get_autocommit(con.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
int busy_handler(std::function<int(int)> handler) {
|
||||||
|
_busy_handler = std::move(handler);
|
||||||
|
if(this->is_opened()) {
|
||||||
|
if(_busy_handler) {
|
||||||
|
return sqlite3_busy_handler(this->connection->get(), busy_handler_callback, this);
|
||||||
|
} else {
|
||||||
|
return sqlite3_busy_handler(this->connection->get(), nullptr, nullptr);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return SQLITE_OK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
storage_base(std::string filename, int foreignKeysCount) :
|
||||||
|
pragma(std::bind(&storage_base::get_connection, this)),
|
||||||
|
limit(std::bind(&storage_base::get_connection, this)),
|
||||||
|
inMemory(filename.empty() || filename == ":memory:"),
|
||||||
|
connection(std::make_unique<connection_holder>(std::move(filename))),
|
||||||
|
cachedForeignKeysCount(foreignKeysCount) {
|
||||||
|
if(this->inMemory) {
|
||||||
|
this->connection->retain();
|
||||||
|
this->on_open_internal(this->connection->get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
storage_base(const storage_base& other) :
|
||||||
|
on_open(other.on_open), pragma(std::bind(&storage_base::get_connection, this)),
|
||||||
|
limit(std::bind(&storage_base::get_connection, this)), inMemory(other.inMemory),
|
||||||
|
connection(std::make_unique<connection_holder>(other.connection->filename)),
|
||||||
|
cachedForeignKeysCount(other.cachedForeignKeysCount) {
|
||||||
|
if(this->inMemory) {
|
||||||
|
this->connection->retain();
|
||||||
|
this->on_open_internal(this->connection->get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~storage_base() {
|
||||||
|
if(this->isOpenedForever) {
|
||||||
|
this->connection->release();
|
||||||
|
}
|
||||||
|
if(this->inMemory) {
|
||||||
|
this->connection->release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void begin_transaction_internal(const std::string& query) {
|
||||||
|
this->connection->retain();
|
||||||
|
if(1 == this->connection->retain_count()) {
|
||||||
|
this->on_open_internal(this->connection->get());
|
||||||
|
}
|
||||||
|
sqlite3* db = this->connection->get();
|
||||||
|
perform_void_exec(db, query);
|
||||||
|
}
|
||||||
|
|
||||||
|
connection_ref get_connection() {
|
||||||
|
connection_ref res{*this->connection};
|
||||||
|
if(1 == this->connection->retain_count()) {
|
||||||
|
this->on_open_internal(this->connection->get());
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if SQLITE_VERSION_NUMBER >= 3006019
|
||||||
|
|
||||||
|
void foreign_keys(sqlite3* db, bool value) {
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "PRAGMA foreign_keys = " << value << std::flush;
|
||||||
|
perform_void_exec(db, ss.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool foreign_keys(sqlite3* db) {
|
||||||
|
bool result = false;
|
||||||
|
perform_exec(db, "PRAGMA foreign_keys", extract_single_value<bool>, &result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
void on_open_internal(sqlite3* db) {
|
||||||
|
|
||||||
|
#if SQLITE_VERSION_NUMBER >= 3006019
|
||||||
|
if(this->cachedForeignKeysCount) {
|
||||||
|
this->foreign_keys(db, true);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if(this->pragma._synchronous != -1) {
|
||||||
|
this->pragma.synchronous(this->pragma._synchronous);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this->pragma._journal_mode != -1) {
|
||||||
|
this->pragma.set_pragma("journal_mode", static_cast<journal_mode>(this->pragma._journal_mode), db);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(auto& p: this->collatingFunctions) {
|
||||||
|
auto resultCode =
|
||||||
|
sqlite3_create_collation(db, p.first.c_str(), SQLITE_UTF8, &p.second, collate_callback);
|
||||||
|
if(resultCode != SQLITE_OK) {
|
||||||
|
throw_translated_sqlite_error(db);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(auto& p: this->limit.limits) {
|
||||||
|
sqlite3_limit(db, p.first, p.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(_busy_handler) {
|
||||||
|
sqlite3_busy_handler(this->connection->get(), busy_handler_callback, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(auto& functionPointer: this->scalarFunctions) {
|
||||||
|
try_to_create_function(db, static_cast<user_defined_scalar_function_t&>(*functionPointer));
|
||||||
|
}
|
||||||
|
|
||||||
|
for(auto& functionPointer: this->aggregateFunctions) {
|
||||||
|
try_to_create_function(db, static_cast<user_defined_aggregate_function_t&>(*functionPointer));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this->on_open) {
|
||||||
|
this->on_open(db);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void delete_function_impl(const std::string& name,
|
||||||
|
std::vector<std::unique_ptr<user_defined_function_base>>& functionsVector) const {
|
||||||
|
auto it = find_if(functionsVector.begin(), functionsVector.end(), [&name](auto& functionPointer) {
|
||||||
|
return functionPointer->name == name;
|
||||||
|
});
|
||||||
|
if(it != functionsVector.end()) {
|
||||||
|
functionsVector.erase(it);
|
||||||
|
it = functionsVector.end();
|
||||||
|
|
||||||
|
if(this->connection->retain_count() > 0) {
|
||||||
|
sqlite3* db = this->connection->get();
|
||||||
|
auto resultCode = sqlite3_create_function_v2(db,
|
||||||
|
name.c_str(),
|
||||||
|
0,
|
||||||
|
SQLITE_UTF8,
|
||||||
|
nullptr,
|
||||||
|
nullptr,
|
||||||
|
nullptr,
|
||||||
|
nullptr,
|
||||||
|
nullptr);
|
||||||
|
if(resultCode != SQLITE_OK) {
|
||||||
|
throw_translated_sqlite_error(db);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw std::system_error{orm_error_code::function_not_found};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void try_to_create_function(sqlite3* db, user_defined_scalar_function_t& function) {
|
||||||
|
auto resultCode = sqlite3_create_function_v2(db,
|
||||||
|
function.name.c_str(),
|
||||||
|
function.argumentsCount,
|
||||||
|
SQLITE_UTF8,
|
||||||
|
&function,
|
||||||
|
scalar_function_callback,
|
||||||
|
nullptr,
|
||||||
|
nullptr,
|
||||||
|
nullptr);
|
||||||
|
if(resultCode != SQLITE_OK) {
|
||||||
|
throw_translated_sqlite_error(db);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void try_to_create_function(sqlite3* db, user_defined_aggregate_function_t& function) {
|
||||||
|
auto resultCode = sqlite3_create_function(db,
|
||||||
|
function.name.c_str(),
|
||||||
|
function.argumentsCount,
|
||||||
|
SQLITE_UTF8,
|
||||||
|
&function,
|
||||||
|
nullptr,
|
||||||
|
aggregate_function_step_callback,
|
||||||
|
aggregate_function_final_callback);
|
||||||
|
if(resultCode != SQLITE_OK) {
|
||||||
|
throw_translated_sqlite_error(resultCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
aggregate_function_step_callback(sqlite3_context* context, int argsCount, sqlite3_value** values) {
|
||||||
|
auto functionVoidPointer = sqlite3_user_data(context);
|
||||||
|
auto functionPointer = static_cast<user_defined_aggregate_function_t*>(functionVoidPointer);
|
||||||
|
auto aggregateContextVoidPointer = sqlite3_aggregate_context(context, sizeof(int**));
|
||||||
|
auto aggregateContextIntPointer = static_cast<int**>(aggregateContextVoidPointer);
|
||||||
|
if(*aggregateContextIntPointer == nullptr) {
|
||||||
|
*aggregateContextIntPointer = functionPointer->create();
|
||||||
|
}
|
||||||
|
functionPointer->step(context, *aggregateContextIntPointer, argsCount, values);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void aggregate_function_final_callback(sqlite3_context* context) {
|
||||||
|
auto functionVoidPointer = sqlite3_user_data(context);
|
||||||
|
auto functionPointer = static_cast<user_defined_aggregate_function_t*>(functionVoidPointer);
|
||||||
|
auto aggregateContextVoidPointer = sqlite3_aggregate_context(context, sizeof(int**));
|
||||||
|
auto aggregateContextIntPointer = static_cast<int**>(aggregateContextVoidPointer);
|
||||||
|
functionPointer->finalCall(context, *aggregateContextIntPointer);
|
||||||
|
functionPointer->destroy(*aggregateContextIntPointer);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void scalar_function_callback(sqlite3_context* context, int argsCount, sqlite3_value** values) {
|
||||||
|
auto functionVoidPointer = sqlite3_user_data(context);
|
||||||
|
auto functionPointer = static_cast<user_defined_scalar_function_t*>(functionVoidPointer);
|
||||||
|
std::unique_ptr<int, void (*)(int*)> callablePointer(functionPointer->create(),
|
||||||
|
functionPointer->destroy);
|
||||||
|
if(functionPointer->argumentsCount != -1 && functionPointer->argumentsCount != argsCount) {
|
||||||
|
throw std::system_error{orm_error_code::arguments_count_does_not_match};
|
||||||
|
}
|
||||||
|
functionPointer->run(context, functionPointer, argsCount, values);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class F>
|
||||||
|
static void delete_function_callback(int* pointer) {
|
||||||
|
auto voidPointer = static_cast<void*>(pointer);
|
||||||
|
auto fPointer = static_cast<F*>(voidPointer);
|
||||||
|
delete fPointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string current_timestamp(sqlite3* db) {
|
||||||
|
std::string result;
|
||||||
|
perform_exec(db, "SELECT CURRENT_TIMESTAMP", extract_single_value<std::string>, &result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void drop_table_internal(sqlite3* db, const std::string& tableName) {
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "DROP TABLE " << streaming_identifier(tableName) << std::flush;
|
||||||
|
perform_void_exec(db, ss.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
static int collate_callback(void* arg, int leftLen, const void* lhs, int rightLen, const void* rhs) {
|
||||||
|
auto& f = *(collating_function*)arg;
|
||||||
|
return f(leftLen, lhs, rightLen, rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int busy_handler_callback(void* selfPointer, int triesCount) {
|
||||||
|
auto& storage = *static_cast<storage_base*>(selfPointer);
|
||||||
|
if(storage._busy_handler) {
|
||||||
|
return storage._busy_handler(triesCount);
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool calculate_remove_add_columns(std::vector<const table_xinfo*>& columnsToAdd,
|
||||||
|
std::vector<table_xinfo>& storageTableInfo,
|
||||||
|
std::vector<table_xinfo>& dbTableInfo) const {
|
||||||
|
bool notEqual = false;
|
||||||
|
|
||||||
|
// iterate through storage columns
|
||||||
|
for(size_t storageColumnInfoIndex = 0; storageColumnInfoIndex < storageTableInfo.size();
|
||||||
|
++storageColumnInfoIndex) {
|
||||||
|
|
||||||
|
// get storage's column info
|
||||||
|
auto& storageColumnInfo = storageTableInfo[storageColumnInfoIndex];
|
||||||
|
auto& columnName = storageColumnInfo.name;
|
||||||
|
|
||||||
|
// search for a column in db eith the same name
|
||||||
|
auto dbColumnInfoIt = std::find_if(dbTableInfo.begin(), dbTableInfo.end(), [&columnName](auto& ti) {
|
||||||
|
return ti.name == columnName;
|
||||||
|
});
|
||||||
|
if(dbColumnInfoIt != dbTableInfo.end()) {
|
||||||
|
auto& dbColumnInfo = *dbColumnInfoIt;
|
||||||
|
auto columnsAreEqual =
|
||||||
|
dbColumnInfo.name == storageColumnInfo.name &&
|
||||||
|
dbColumnInfo.notnull == storageColumnInfo.notnull &&
|
||||||
|
(!dbColumnInfo.dflt_value.empty()) == (!storageColumnInfo.dflt_value.empty()) &&
|
||||||
|
dbColumnInfo.pk == storageColumnInfo.pk &&
|
||||||
|
(dbColumnInfo.hidden == 0) == (storageColumnInfo.hidden == 0);
|
||||||
|
if(!columnsAreEqual) {
|
||||||
|
notEqual = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
dbTableInfo.erase(dbColumnInfoIt);
|
||||||
|
storageTableInfo.erase(storageTableInfo.begin() +
|
||||||
|
static_cast<ptrdiff_t>(storageColumnInfoIndex));
|
||||||
|
--storageColumnInfoIndex;
|
||||||
|
} else {
|
||||||
|
columnsToAdd.push_back(&storageColumnInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return notEqual;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool inMemory;
|
||||||
|
bool isOpenedForever = false;
|
||||||
|
std::unique_ptr<connection_holder> connection;
|
||||||
|
std::map<std::string, collating_function> collatingFunctions;
|
||||||
|
const int cachedForeignKeysCount;
|
||||||
|
std::function<int(int)> _busy_handler;
|
||||||
|
std::vector<std::unique_ptr<user_defined_function_base>> scalarFunctions;
|
||||||
|
std::vector<std::unique_ptr<user_defined_function_base>> aggregateFunctions;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string> // std::string
|
||||||
|
|
||||||
|
#include "functional/cxx_universal.h" // ::nullptr_t
|
||||||
|
#include "functional/static_magic.h"
|
||||||
|
#include "tuple_helper/tuple_filter.h"
|
||||||
|
#include "tuple_helper/tuple_iteration.h"
|
||||||
|
#include "type_traits.h"
|
||||||
|
#include "select_constraints.h"
|
||||||
|
#include "storage_lookup.h"
|
||||||
|
|
||||||
|
// interface functions
|
||||||
|
namespace sqlite_orm {
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
template<class DBOs>
|
||||||
|
using tables_index_sequence = filter_tuple_sequence_t<DBOs, is_table>;
|
||||||
|
|
||||||
|
template<class DBOs, satisfies<is_db_objects, DBOs> = true>
|
||||||
|
int foreign_keys_count(const DBOs& dbObjects) {
|
||||||
|
int res = 0;
|
||||||
|
iterate_tuple<true>(dbObjects, tables_index_sequence<DBOs>{}, [&res](const auto& table) {
|
||||||
|
res += table.foreign_keys_count();
|
||||||
|
});
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Lookup, class DBOs, satisfies<is_db_objects, DBOs>>
|
||||||
|
auto lookup_table(const DBOs& dbObjects) {
|
||||||
|
return static_if<is_mapped_v<DBOs, Lookup>>(
|
||||||
|
[](const auto& dbObjects) {
|
||||||
|
return &pick_table<Lookup>(dbObjects);
|
||||||
|
},
|
||||||
|
empty_callable<nullptr_t>())(dbObjects);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Lookup, class DBOs, satisfies<is_db_objects, DBOs>>
|
||||||
|
decltype(auto) lookup_table_name(const DBOs& dbObjects) {
|
||||||
|
return static_if<is_mapped_v<DBOs, Lookup>>(
|
||||||
|
[](const auto& dbObjects) -> const std::string& {
|
||||||
|
return pick_table<Lookup>(dbObjects).name;
|
||||||
|
},
|
||||||
|
empty_callable<std::string>())(dbObjects);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find column name by its type and member pointer.
|
||||||
|
*/
|
||||||
|
template<class O, class F, class DBOs, satisfies<is_db_objects, DBOs> = true>
|
||||||
|
const std::string* find_column_name(const DBOs& dbObjects, F O::*field) {
|
||||||
|
return pick_table<O>(dbObjects).find_column_name(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Materialize column pointer:
|
||||||
|
* 1. by explicit object type and member pointer.
|
||||||
|
*/
|
||||||
|
template<class O, class F, class DBOs, satisfies<is_db_objects, DBOs> = true>
|
||||||
|
constexpr decltype(auto) materialize_column_pointer(const DBOs&, const column_pointer<O, F>& cp) {
|
||||||
|
return cp.field;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find column name by:
|
||||||
|
* 1. by explicit object type and member pointer.
|
||||||
|
*/
|
||||||
|
template<class O, class F, class DBOs, satisfies<is_db_objects, DBOs> = true>
|
||||||
|
const std::string* find_column_name(const DBOs& dbObjects, const column_pointer<O, F>& cp) {
|
||||||
|
auto field = materialize_column_pointer(dbObjects, cp);
|
||||||
|
return pick_table<O>(dbObjects).find_column_name(field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,144 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <type_traits> // std::true_type, std::false_type, std::remove_const, std::enable_if, std::is_base_of, std::is_void
|
||||||
|
#include <tuple>
|
||||||
|
#include <utility> // std::index_sequence, std::make_index_sequence
|
||||||
|
|
||||||
|
#include "functional/cxx_universal.h"
|
||||||
|
#include "functional/cxx_type_traits_polyfill.h"
|
||||||
|
#include "type_traits.h"
|
||||||
|
|
||||||
|
namespace sqlite_orm {
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
template<class... DBO>
|
||||||
|
struct storage_t;
|
||||||
|
|
||||||
|
template<class... DBO>
|
||||||
|
using db_objects_tuple = std::tuple<DBO...>;
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct is_storage : std::false_type {};
|
||||||
|
|
||||||
|
template<class... DBO>
|
||||||
|
struct is_storage<storage_t<DBO...>> : std::true_type {};
|
||||||
|
template<class... DBO>
|
||||||
|
struct is_storage<const storage_t<DBO...>> : std::true_type {};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct is_db_objects : std::false_type {};
|
||||||
|
|
||||||
|
template<class... DBO>
|
||||||
|
struct is_db_objects<db_objects_tuple<DBO...>> : std::true_type {};
|
||||||
|
template<class... DBO>
|
||||||
|
struct is_db_objects<const db_objects_tuple<DBO...>> : std::true_type {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* std::true_type if given object is mapped, std::false_type otherwise.
|
||||||
|
*
|
||||||
|
* Note: unlike table_t<>, index_t<>::object_type and trigger_t<>::object_type is always void.
|
||||||
|
*/
|
||||||
|
template<typename DBO, typename Lookup>
|
||||||
|
struct object_type_matches : polyfill::conjunction<polyfill::negation<std::is_void<object_type_t<DBO>>>,
|
||||||
|
std::is_same<Lookup, object_type_t<DBO>>> {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* std::true_type if given lookup type (object) is mapped, std::false_type otherwise.
|
||||||
|
*/
|
||||||
|
template<typename DBO, typename Lookup>
|
||||||
|
struct lookup_type_matches : polyfill::disjunction<object_type_matches<DBO, Lookup>> {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// pick/lookup metafunctions
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indirect enabler for DBO, accepting an index to disambiguate non-unique DBOs
|
||||||
|
*/
|
||||||
|
template<class Lookup, size_t Ix, class DBO>
|
||||||
|
struct enable_found_table : std::enable_if<lookup_type_matches<DBO, Lookup>::value, DBO> {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SFINAE friendly facility to pick a table definition (`table_t`) from a tuple of database objects.
|
||||||
|
*
|
||||||
|
* Lookup - mapped data type
|
||||||
|
* Seq - index sequence matching the number of DBOs
|
||||||
|
* DBOs - db_objects_tuple type
|
||||||
|
*/
|
||||||
|
template<class Lookup, class Seq, class DBOs>
|
||||||
|
struct storage_pick_table;
|
||||||
|
|
||||||
|
template<class Lookup, size_t... Ix, class... DBO>
|
||||||
|
struct storage_pick_table<Lookup, std::index_sequence<Ix...>, db_objects_tuple<DBO...>>
|
||||||
|
: enable_found_table<Lookup, Ix, DBO>... {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SFINAE friendly facility to pick a table definition (`table_t`) from a tuple of database objects.
|
||||||
|
*
|
||||||
|
* Lookup - 'table' type, mapped data type
|
||||||
|
* DBOs - db_objects_tuple type, possibly const-qualified
|
||||||
|
*/
|
||||||
|
template<class Lookup, class DBOs>
|
||||||
|
using storage_pick_table_t = typename storage_pick_table<Lookup,
|
||||||
|
std::make_index_sequence<std::tuple_size<DBOs>::value>,
|
||||||
|
std::remove_const_t<DBOs>>::type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a table definition (`table_t`) from a tuple of database objects;
|
||||||
|
* `std::nonesuch` if not found.
|
||||||
|
*
|
||||||
|
* DBOs - db_objects_tuple type
|
||||||
|
* Lookup - mapped data type
|
||||||
|
*/
|
||||||
|
template<class Lookup, class DBOs>
|
||||||
|
struct storage_find_table : polyfill::detected_or<polyfill::nonesuch, storage_pick_table_t, Lookup, DBOs> {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a table definition (`table_t`) from a tuple of database objects;
|
||||||
|
* `std::nonesuch` if not found.
|
||||||
|
*
|
||||||
|
* DBOs - db_objects_tuple type, possibly const-qualified
|
||||||
|
* Lookup - mapped data type
|
||||||
|
*/
|
||||||
|
template<class Lookup, class DBOs>
|
||||||
|
using storage_find_table_t = typename storage_find_table<Lookup, std::remove_const_t<DBOs>>::type;
|
||||||
|
|
||||||
|
#ifndef SQLITE_ORM_BROKEN_VARIADIC_PACK_EXPANSION
|
||||||
|
template<class DBOs, class Lookup, class SFINAE = void>
|
||||||
|
struct is_mapped : std::false_type {};
|
||||||
|
template<class DBOs, class Lookup>
|
||||||
|
struct is_mapped<DBOs, Lookup, polyfill::void_t<storage_pick_table_t<Lookup, DBOs>>> : std::true_type {};
|
||||||
|
#else
|
||||||
|
template<class DBOs, class Lookup, class SFINAE = storage_find_table_t<Lookup, DBOs>>
|
||||||
|
struct is_mapped : std::true_type {};
|
||||||
|
template<class DBOs, class Lookup>
|
||||||
|
struct is_mapped<DBOs, Lookup, polyfill::nonesuch> : std::false_type {};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
template<class DBOs, class Lookup>
|
||||||
|
SQLITE_ORM_INLINE_VAR constexpr bool is_mapped_v = is_mapped<DBOs, Lookup>::value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// runtime lookup functions
|
||||||
|
namespace sqlite_orm {
|
||||||
|
namespace internal {
|
||||||
|
/**
|
||||||
|
* Pick the table definition for the specified lookup type from the given tuple of schema objects.
|
||||||
|
*
|
||||||
|
* Note: This function requires Lookup to be mapped, otherwise it is removed from the overload resolution set.
|
||||||
|
*/
|
||||||
|
template<class Lookup, class DBOs, satisfies<is_mapped, DBOs, Lookup> = true>
|
||||||
|
auto& pick_table(DBOs& dbObjects) {
|
||||||
|
using table_type = storage_pick_table_t<Lookup, DBOs>;
|
||||||
|
return std::get<table_type>(dbObjects);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Lookup, class DBOs, satisfies<is_db_objects, DBOs> = true>
|
||||||
|
auto lookup_table(const DBOs& dbObjects);
|
||||||
|
|
||||||
|
template<class Lookup, class DBOs, satisfies<is_db_objects, DBOs> = true>
|
||||||
|
decltype(auto) lookup_table_name(const DBOs& dbObjects);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue