diff --git a/CMakeLists.txt b/CMakeLists.txt index 1d29498..3aa8b58 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -270,14 +270,16 @@ if(BUILD_TESTS) target_link_libraries(RingTest ${TEST_LIBRARIES}) if(NOT WIN32) - add_executable(CommandTest ${SOURCE_FILES} ${HEADER_FILES} test/CommandTest.cpp src/log/LogSinks.cpp src/log/LogSinks.h) - target_link_libraries(CommandTest ${TEST_LIBRARIES}) + add_executable(CommandTest test/CommandTest.cpp src/query/command3.cpp src/query/Command.cpp src/query/escape.cpp src/converters/converter.cpp src/Variable.cpp) + target_link_libraries(CommandTest DataPipes::core::shared jsoncpp_lib ${glib20_DIR}/lib/x86_64-linux-gnu/libffi.so.7 ${nice_DIR}/lib/libnice.so.10) add_executable(WebsocketTest ${SOURCE_FILES} ${HEADER_FILES} test/WSSTest.cpp src/log/LogSinks.cpp src/log/LogSinks.h) target_link_libraries(WebsocketTest ${TEST_LIBRARIES}) - add_executable(SQLTest ${SOURCE_FILES} ${HEADER_FILES} test/SQLTest.cpp src/log/LogSinks.cpp src/log/LogSinks.h) - target_link_libraries(SQLTest ${TEST_LIBRARIES}) + #add_executable(SQLTest ${SOURCE_FILES} ${HEADER_FILES} test/SQLTest.cpp src/log/LogSinks.cpp src/log/LogSinks.h) + #target_link_libraries(SQLTest ${TEST_LIBRARIES}) + add_executable(SQL2Test test/SQL2Test.cpp src/Variable.cpp) + target_link_libraries(SQL2Test sqlite3) add_executable(ChannelTest ${SOURCE_FILES} ${HEADER_FILES} test/ChannelTest.cpp src/log/LogSinks.cpp src/log/LogSinks.h) target_link_libraries(ChannelTest ${TEST_LIBRARIES}) diff --git a/src/PermissionManager.cpp b/src/PermissionManager.cpp index af5df2a..8df3448 100644 --- a/src/PermissionManager.cpp +++ b/src/PermissionManager.cpp @@ -935,6 +935,7 @@ inline void init_mapping() { if(teamspeak::unmapping.empty()) teamspeak::unmapping = build_unmapping(); } +/* template inline deque operator+(const deque& a, const deque& b) { deque result; @@ -942,6 +943,7 @@ inline deque operator+(const deque& a, const deque& b) { result.insert(result.end(), b.begin(), b.end()); return result; } +*/ inline deque mmget(teamspeak::MapType& map, teamspeak::GroupType type, const std::string& key) { return map[type].count(key) > 0 ? map[type].find(key)->second : deque{}; @@ -953,16 +955,29 @@ inline std::deque map_entry(std::string key, teamspeak::GroupType t if(key.find("_needed_modify_power_") == 1) { key = key.substr(strlen("x_needed_modify_power_")); - deque result; - if(map_table[type].count("i_" + key) > 0 || map_table[teamspeak::GroupType::GENERAL].count("i_" + key) > 0) result = mmget(map_table, type, "i_" + key) + mmget(map_table, teamspeak::GroupType::GENERAL, "i_" + key); - else if(map_table[type].count("b_" + key) > 0 || map_table[teamspeak::GroupType::GENERAL].count("b_" + key) > 0) result = mmget(map_table, type, "b_" + key) + mmget(map_table, teamspeak::GroupType::GENERAL, "b_" + key); - else result = {"x_" + key}; + + std::deque result{}; + auto mapped_type = mmget(map_table, type, "i_" + key); + result.insert(result.end(), mapped_type.begin(), mapped_type.end()); + if(type != teamspeak::GroupType::GENERAL) { + auto mapped_general = mmget(map_table, teamspeak::GroupType::GENERAL, "i_" + key); + result.insert(result.end(), mapped_general.begin(), mapped_general.end()); + } for(auto& entry : result) entry = "i_needed_modify_power_" + entry.substr(2); return result; } - if(map_table[type].count(key) > 0 || map_table[teamspeak::GroupType::GENERAL].count(key) > 0) return mmget(map_table, type, key) + mmget(map_table, teamspeak::GroupType::GENERAL, key); + if(map_table[type].count(key) > 0 || map_table[teamspeak::GroupType::GENERAL].count(key) > 0) { + std::deque result{}; + auto mapped_type = mmget(map_table, type, key); + result.insert(result.end(), mapped_type.begin(), mapped_type.end()); + if(type != teamspeak::GroupType::GENERAL) { + auto mapped_general = mmget(map_table, teamspeak::GroupType::GENERAL, key); + result.insert(result.end(), mapped_general.begin(), mapped_general.end()); + } + return result; + } return {key}; } diff --git a/src/PermissionManager.h b/src/PermissionManager.h index b1682c4..c0ffd74 100644 --- a/src/PermissionManager.h +++ b/src/PermissionManager.h @@ -813,11 +813,11 @@ namespace ts { }; struct PermissionFlaggedValue { - PermissionValue value = permNotGranted; - bool has_value = false; + PermissionValue value{permNotGranted}; + bool has_value{false}; - constexpr bool has_power() const { return this->has_value && (this->value > 0 || this->value == -1); } - constexpr bool has_infinite_power() const { return this->has_value && this->value == -1; } + [[nodiscard]] constexpr bool has_power() const { return this->has_value && (this->value > 0 || this->value == -1); } + [[nodiscard]] constexpr bool has_infinite_power() const { return this->has_value && this->value == -1; } inline bool operator==(const PermissionFlaggedValue& other) const { return other.value == this->value && other.has_value == this->has_value; } inline bool operator!=(const PermissionFlaggedValue& other) const { return !(*this == other); } diff --git a/src/Properties.h b/src/Properties.h index bec4d10..ec3a6c0 100644 --- a/src/Properties.h +++ b/src/Properties.h @@ -149,11 +149,11 @@ namespace ts { VIRTUALSERVER_HOSTMESSAGE, //available when connected, not updated while connected VIRTUALSERVER_HOSTMESSAGE_MODE, //available when connected, not updated while connected VIRTUALSERVER_FILEBASE, //not available to clients, stores the folder used for file transfers - VIRTUALSERVER_DEFAULT_SERVER_GROUP, //the manager permissions server group that a new manager gets assigned - VIRTUALSERVER_DEFAULT_MUSIC_GROUP, //the manager permissions server group that a new manager gets assigned - VIRTUALSERVER_DEFAULT_CHANNEL_GROUP, //the channel permissions group that a new manager gets assigned when joining a channel + VIRTUALSERVER_DEFAULT_SERVER_GROUP, //the client permissions server group that a new client gets assigned + VIRTUALSERVER_DEFAULT_MUSIC_GROUP, //the client permissions server group that a new client gets assigned + VIRTUALSERVER_DEFAULT_CHANNEL_GROUP, //the channel permissions group that a new client gets assigned when joining a channel VIRTUALSERVER_FLAG_PASSWORD, //only available on request (=> requestServerVariables) - VIRTUALSERVER_DEFAULT_CHANNEL_ADMIN_GROUP, //the channel permissions group that a manager gets assigned when creating a channel + VIRTUALSERVER_DEFAULT_CHANNEL_ADMIN_GROUP, //the channel permissions group that a client gets assigned when creating a channel VIRTUALSERVER_MAX_DOWNLOAD_TOTAL_BANDWIDTH, //only available on request (=> requestServerVariables) VIRTUALSERVER_MAX_UPLOAD_TOTAL_BANDWIDTH, //only available on request (=> requestServerVariables) VIRTUALSERVER_HOSTBANNER_URL, //available when connected, always up-to-date @@ -238,7 +238,7 @@ namespace ts { CHANNEL_NAME, //Available for all channels that are "in view", always up-to-date CHANNEL_TOPIC, //Available for all channels that are "in view", always up-to-date CHANNEL_DESCRIPTION, //Must be requested (=> requestChannelDescription) - CHANNEL_PASSWORD, //not available manager side + CHANNEL_PASSWORD, //not available client side CHANNEL_CODEC, //Available for all channels that are "in view", always up-to-date CHANNEL_CODEC_QUALITY, //Available for all channels that are "in view", always up-to-date CHANNEL_MAXCLIENTS, //Available for all channels that are "in view", always up-to-date @@ -250,13 +250,13 @@ namespace ts { CHANNEL_FLAG_PASSWORD, //Available for all channels that are "in view", always up-to-date CHANNEL_CODEC_LATENCY_FACTOR, //Available for all channels that are "in view", always up-to-date CHANNEL_CODEC_IS_UNENCRYPTED, //Available for all channels that are "in view", always up-to-date - CHANNEL_SECURITY_SALT, //Not available manager side, not used in teamspeak, only SDK. Sets the options+salt for security hash. + CHANNEL_SECURITY_SALT, //Not available client side, not used in teamspeak, only SDK. Sets the options+salt for security hash. CHANNEL_DELETE_DELAY, //How many seconds to wait before deleting this channel CHANNEL_FLAG_MAXCLIENTS_UNLIMITED, //Available for all channels that are "in view", always up-to-date CHANNEL_FLAG_MAXFAMILYCLIENTS_UNLIMITED,//Available for all channels that are "in view", always up-to-date CHANNEL_FLAG_MAXFAMILYCLIENTS_INHERITED,//Available for all channels that are "in view", always up-to-date - CHANNEL_FLAG_ARE_SUBSCRIBED, //Only available manager side, stores whether we are subscribed to this channel - CHANNEL_FILEPATH, //not available manager side, the folder used for file-transfers for this channel + CHANNEL_FLAG_ARE_SUBSCRIBED, //Only available client side, stores whether we are subscribed to this channel + CHANNEL_FILEPATH, //not available client side, the folder used for file-transfers for this channel CHANNEL_NEEDED_TALK_POWER, //Available for all channels that are "in view", always up-to-date CHANNEL_FORCED_SILENCE, //Available for all channels that are "in view", always up-to-date CHANNEL_NAME_PHONETIC, //Available for all channels that are "in view", always up-to-date @@ -289,62 +289,62 @@ namespace ts { enum ClientProperties { CLIENT_UNDEFINED, CLIENT_BEGINMARKER, - CLIENT_UNIQUE_IDENTIFIER = CLIENT_BEGINMARKER, //automatically up-to-date for any manager "in view", can be used to identify this particular manager installation - CLIENT_NICKNAME, //automatically up-to-date for any manager "in view" + CLIENT_UNIQUE_IDENTIFIER = CLIENT_BEGINMARKER, //automatically up-to-date for any client "in view", can be used to identify this particular client installation + CLIENT_NICKNAME, //automatically up-to-date for any client "in view" CLIENT_VERSION, //for other clients than ourself, this needs to be requested (=> requestClientVariables) CLIENT_PLATFORM, //for other clients than ourself, this needs to be requested (=> requestClientVariables) - CLIENT_FLAG_TALKING, //automatically up-to-date for any manager that can be heard (in room / whisper) - CLIENT_INPUT_MUTED, //automatically up-to-date for any manager "in view", this clients microphone mute status - CLIENT_OUTPUT_MUTED, //automatically up-to-date for any manager "in view", this clients headphones/speakers/mic combined mute status - CLIENT_OUTPUTONLY_MUTED, //automatically up-to-date for any manager "in view", this clients headphones/speakers only mute status - CLIENT_INPUT_HARDWARE, //automatically up-to-date for any manager "in view", this clients microphone hardware status (is the capture device opened?) - CLIENT_OUTPUT_HARDWARE, //automatically up-to-date for any manager "in view", this clients headphone/speakers hardware status (is the playback device opened?) + CLIENT_FLAG_TALKING, //automatically up-to-date for any client that can be heard (in room / whisper) + CLIENT_INPUT_MUTED, //automatically up-to-date for any client "in view", this clients microphone mute status + CLIENT_OUTPUT_MUTED, //automatically up-to-date for any client "in view", this clients headphones/speakers/mic combined mute status + CLIENT_OUTPUTONLY_MUTED, //automatically up-to-date for any client "in view", this clients headphones/speakers only mute status + CLIENT_INPUT_HARDWARE, //automatically up-to-date for any client "in view", this clients microphone hardware status (is the capture device opened?) + CLIENT_OUTPUT_HARDWARE, //automatically up-to-date for any client "in view", this clients headphone/speakers hardware status (is the playback device opened?) CLIENT_DEFAULT_CHANNEL, //only usable for ourself, the default channel we used to connect on our last connection attempt CLIENT_DEFAULT_CHANNEL_PASSWORD, //internal use CLIENT_SERVER_PASSWORD, //internal use - CLIENT_META_DATA, //automatically up-to-date for any manager "in view", not used by TeamSpeak, free storage for sdk users - CLIENT_IS_RECORDING, //automatically up-to-date for any manager "in view" + CLIENT_META_DATA, //automatically up-to-date for any client "in view", not used by TeamSpeak, free storage for sdk users + CLIENT_IS_RECORDING, //automatically up-to-date for any client "in view" CLIENT_VERSION_SIGN, //sign - CLIENT_SECURITY_HASH, //SDK use, not used by teamspeak. Hash is provided by an outside source. A channel will use the security salt + other manager data to calculate a hash, which must be the same as the one provided here. + CLIENT_SECURITY_HASH, //SDK use, not used by teamspeak. Hash is provided by an outside source. A channel will use the security salt + other client data to calculate a hash, which must be the same as the one provided here. //Rare properties CLIENT_KEY_OFFSET, //internal use CLIENT_LOGIN_NAME, //used for serverquery clients, makes no sense on normal clients currently CLIENT_LOGIN_PASSWORD, //used for serverquery clients, makes no sense on normal clients currently - CLIENT_DATABASE_ID, //automatically up-to-date for any manager "in view", only valid with PERMISSION feature, holds database manager id + CLIENT_DATABASE_ID, //automatically up-to-date for any client "in view", only valid with PERMISSION feature, holds database client id CLIENT_ID, //clid! CLIENT_HARDWARE_ID, //hwid! - CLIENT_CHANNEL_GROUP_ID, //automatically up-to-date for any manager "in view", only valid with PERMISSION feature, holds database manager id - CLIENT_SERVERGROUPS, //automatically up-to-date for any manager "in view", only valid with PERMISSION feature, holds all servergroups manager belongs too - CLIENT_CREATED, //this needs to be requested (=> requestClientVariables), first time this manager connected to this server - CLIENT_LASTCONNECTED, //this needs to be requested (=> requestClientVariables), last time this manager connected to this server - CLIENT_TOTALCONNECTIONS, //this needs to be requested (=> requestClientVariables), how many times this manager connected to this server - CLIENT_AWAY, //automatically up-to-date for any manager "in view", this clients away status - CLIENT_AWAY_MESSAGE, //automatically up-to-date for any manager "in view", this clients away message - CLIENT_TYPE, //automatically up-to-date for any manager "in view", determines if this is a real manager or a server-query connection - CLIENT_TYPE_EXACT, //automatically up-to-date for any manager "in view", determines if this is a real manager or a server-query connection - CLIENT_FLAG_AVATAR, //automatically up-to-date for any manager "in view", this manager got an avatar - CLIENT_TALK_POWER, //automatically up-to-date for any manager "in view", only valid with PERMISSION feature, holds database manager id - CLIENT_TALK_REQUEST, //automatically up-to-date for any manager "in view", only valid with PERMISSION feature, holds timestamp where manager requested to talk - CLIENT_TALK_REQUEST_MSG, //automatically up-to-date for any manager "in view", only valid with PERMISSION feature, holds matter for the request - CLIENT_DESCRIPTION, //automatically up-to-date for any manager "in view" - CLIENT_IS_TALKER, //automatically up-to-date for any manager "in view" + CLIENT_CHANNEL_GROUP_ID, //automatically up-to-date for any client "in view", only valid with PERMISSION feature, holds database client id + CLIENT_SERVERGROUPS, //automatically up-to-date for any client "in view", only valid with PERMISSION feature, holds all servergroups client belongs too + CLIENT_CREATED, //this needs to be requested (=> requestClientVariables), first time this client connected to this server + CLIENT_LASTCONNECTED, //this needs to be requested (=> requestClientVariables), last time this client connected to this server + CLIENT_TOTALCONNECTIONS, //this needs to be requested (=> requestClientVariables), how many times this client connected to this server + CLIENT_AWAY, //automatically up-to-date for any client "in view", this clients away status + CLIENT_AWAY_MESSAGE, //automatically up-to-date for any client "in view", this clients away message + CLIENT_TYPE, //automatically up-to-date for any client "in view", determines if this is a real client or a server-query connection + CLIENT_TYPE_EXACT, //automatically up-to-date for any client "in view", determines if this is a real client or a server-query connection + CLIENT_FLAG_AVATAR, //automatically up-to-date for any client "in view", this client got an avatar + CLIENT_TALK_POWER, //automatically up-to-date for any client "in view", only valid with PERMISSION feature, holds database client id + CLIENT_TALK_REQUEST, //automatically up-to-date for any client "in view", only valid with PERMISSION feature, holds timestamp where client requested to talk + CLIENT_TALK_REQUEST_MSG, //automatically up-to-date for any client "in view", only valid with PERMISSION feature, holds matter for the request + CLIENT_DESCRIPTION, //automatically up-to-date for any client "in view" + CLIENT_IS_TALKER, //automatically up-to-date for any client "in view" CLIENT_MONTH_BYTES_UPLOADED, //this needs to be requested (=> requestClientVariables) CLIENT_MONTH_BYTES_DOWNLOADED, //this needs to be requested (=> requestClientVariables) CLIENT_TOTAL_BYTES_UPLOADED, //this needs to be requested (=> requestClientVariables) CLIENT_TOTAL_BYTES_DOWNLOADED, //this needs to be requested (=> requestClientVariables) CLIENT_TOTAL_ONLINE_TIME, CLIENT_MONTH_ONLINE_TIME, - CLIENT_IS_PRIORITY_SPEAKER, //automatically up-to-date for any manager "in view" - CLIENT_UNREAD_MESSAGES, //automatically up-to-date for any manager "in view" - CLIENT_NICKNAME_PHONETIC, //automatically up-to-date for any manager "in view" - CLIENT_NEEDED_SERVERQUERY_VIEW_POWER, //automatically up-to-date for any manager "in view" + CLIENT_IS_PRIORITY_SPEAKER, //automatically up-to-date for any client "in view" + CLIENT_UNREAD_MESSAGES, //automatically up-to-date for any client "in view" + CLIENT_NICKNAME_PHONETIC, //automatically up-to-date for any client "in view" + CLIENT_NEEDED_SERVERQUERY_VIEW_POWER, //automatically up-to-date for any client "in view" CLIENT_DEFAULT_TOKEN, //only usable for ourself, the default token we used to connect on our last connection attempt - CLIENT_ICON_ID, //automatically up-to-date for any manager "in view" - CLIENT_IS_CHANNEL_COMMANDER, //automatically up-to-date for any manager "in view" - CLIENT_COUNTRY, //automatically up-to-date for any manager "in view" - CLIENT_CHANNEL_GROUP_INHERITED_CHANNEL_ID, //automatically up-to-date for any manager "in view", only valid with PERMISSION feature, contains channel_id where the channel_group_id is set from - CLIENT_BADGES, //automatically up-to-date for any manager "in view", stores icons for partner badges + CLIENT_ICON_ID, //automatically up-to-date for any client "in view" + CLIENT_IS_CHANNEL_COMMANDER, //automatically up-to-date for any client "in view" + CLIENT_COUNTRY, //automatically up-to-date for any client "in view" + CLIENT_CHANNEL_GROUP_INHERITED_CHANNEL_ID, //automatically up-to-date for any client "in view", only valid with PERMISSION feature, contains channel_id where the channel_group_id is set from + CLIENT_BADGES, //automatically up-to-date for any client "in view", stores icons for partner badges CLIENT_MYTEAMSPEAK_ID, CLIENT_INTEGRATIONS, @@ -374,11 +374,11 @@ namespace ts { CONNECTION_PING = CONNECTION_BEGINMARKER, //average latency for a round trip through and back this connection CONNECTION_PING_DEVIATION, //standard deviation of the above average latency CONNECTION_CONNECTED_TIME, //how long the connection exists already - CONNECTION_IDLE_TIME, //how long since the last action of this manager - CONNECTION_CLIENT_IP, //NEED DB SAVE! //IP of this manager (as seen from the server side) - CONNECTION_CLIENT_PORT, //Port of this manager (as seen from the server side) - CONNECTION_SERVER_IP, //IP of the server (seen from the manager side) - only available on yourself, not for remote clients, not available server side - CONNECTION_SERVER_PORT, //Port of the server (seen from the manager side) - only available on yourself, not for remote clients, not available server side + CONNECTION_IDLE_TIME, //how long since the last action of this client + CONNECTION_CLIENT_IP, //NEED DB SAVE! //IP of this client (as seen from the server side) + CONNECTION_CLIENT_PORT, //Port of this client (as seen from the server side) + CONNECTION_SERVER_IP, //IP of the server (seen from the client side) - only available on yourself, not for remote clients, not available server side + CONNECTION_SERVER_PORT, //Port of the server (seen from the client side) - only available on yourself, not for remote clients, not available server side CONNECTION_PACKETS_SENT_SPEECH, //how many Speech packets were sent through this connection CONNECTION_PACKETS_SENT_KEEPALIVE, CONNECTION_PACKETS_SENT_CONTROL, @@ -399,7 +399,7 @@ namespace ts { CONNECTION_PACKETLOSS_KEEPALIVE, CONNECTION_PACKETLOSS_CONTROL, CONNECTION_PACKETLOSS_TOTAL, //the probability with which a packet round trip failed because a packet was lost - CONNECTION_SERVER2CLIENT_PACKETLOSS_SPEECH, //the probability with which a speech packet failed from the server to the manager + CONNECTION_SERVER2CLIENT_PACKETLOSS_SPEECH, //the probability with which a speech packet failed from the server to the client CONNECTION_SERVER2CLIENT_PACKETLOSS_KEEPALIVE, CONNECTION_SERVER2CLIENT_PACKETLOSS_CONTROL, CONNECTION_SERVER2CLIENT_PACKETLOSS_TOTAL, @@ -858,7 +858,7 @@ namespace ts { Properties(); ~Properties(); Properties(const Properties&) = delete; - Properties(Properties&&) = delete; + Properties(Properties&&) = default; std::vector list_properties(property::flag_type flagMask = (property::flag_type) ~0UL, property::flag_type negatedFlagMask = 0); std::vector all_properties(); diff --git a/src/Variable.h b/src/Variable.h index 218861c..acdf406 100644 --- a/src/Variable.h +++ b/src/Variable.h @@ -58,8 +58,10 @@ class variable { variable& operator=(const variable& ref); variable& operator=(variable&& ref); - std::string key() const { return data->pair.first; } - std::string value() const { return data->pair.second; } + [[nodiscard]] std::string key() const { return this->r_key(); } + void set_key(const std::string_view& key) const { this->r_key() = key; } + + std::string value() const { return this->r_value(); } VariableType type() const { return data->_type; } variable clone(){ return variable(key(), value(), type()); } diff --git a/src/query/Command.cpp b/src/query/Command.cpp index 7b14cde..00353e2 100644 --- a/src/query/Command.cpp +++ b/src/query/Command.cpp @@ -5,7 +5,6 @@ #include #include #include -#include "log/LogUtils.h" #include "escape.h" using namespace std; diff --git a/src/query/command3.cpp b/src/query/command3.cpp index e297fcf..dba1b30 100644 --- a/src/query/command3.cpp +++ b/src/query/command3.cpp @@ -2,11 +2,12 @@ // Created by wolverindev on 25.01.20. // +#include #include "command3.h" using namespace ts; -command_bulk command_parser::empty_bulk{std::string::npos, ""}; +command_bulk command_parser::empty_bulk{std::string::npos, 0, ""}; bool command_parser::parse(bool command) { this->data = this->_command; @@ -31,8 +32,22 @@ bool command_parser::parse(bool command) { if(findex == std::string::npos) findex = this->_command.size(); - this->_bulks.emplace_back(this->_bulks.size() - 1, this->data.substr(index, findex - index)); + this->_bulks.emplace_back(this->_bulks.size() - 1, index, this->data.substr(index, findex - index)); index = findex + 1; } return true; +} + +std::optional command_parser::next_bulk_containing(const std::string_view &key, size_t start) const { + if(start >= this->bulk_count()) return std::nullopt; + + auto index = this->bulk(start).command_character_index(); + auto next = this->data.find(key, index); + if(next == std::string::npos) return std::nullopt; + + size_t upper_bulk{start + 1}; + for(; upper_bulk < this->bulk_count(); upper_bulk++) + if(this->bulk(upper_bulk).command_character_index() > next) + break; + return upper_bulk - 1; } \ No newline at end of file diff --git a/src/query/command3.h b/src/query/command3.h index f6e224a..79f7e49 100644 --- a/src/query/command3.h +++ b/src/query/command3.h @@ -26,13 +26,16 @@ namespace ts { findex += key_size; if(findex < max) { - if(findex < max && data[findex] != '=') { + if(data[findex] == '=') + begin = findex + 1; + else if(data[findex] == ' ') + begin = findex; /* empty value */ + else { index = findex + key_size; continue; } - begin = findex + 1; - if(end) *end = data.find(' ', findex + 1); + if(end) *end = data.find(' ', findex); return true; } else { begin = max; @@ -46,6 +49,11 @@ namespace ts { public: [[nodiscard]] inline bool is_empty() const noexcept { return this->data.empty(); } + [[nodiscard]] inline bool has_key(const std::string_view& key) const { + size_t begin{0}; + return value_raw_impl(this->data, key, begin, nullptr); + } + [[nodiscard]] inline std::string_view value_raw(const std::string_view& key) const { bool tmp; auto result = this->value_raw(key, tmp); @@ -54,7 +62,7 @@ namespace ts { } [[nodiscard]] inline std::string_view value_raw(const std::string_view& key, bool& has_been_found) const noexcept { - size_t begin, end; + size_t begin{0}, end; has_been_found = value_raw_impl(this->data, key, begin, &end); if(!has_been_found) return {}; @@ -68,6 +76,13 @@ namespace ts { return query::unescape(std::string{value}, false); } + [[nodiscard]] inline std::string value(const std::string_view& key, bool& has_been_found) const noexcept { + const auto value = this->value_raw(key, has_been_found); + if(value.empty()) return std::string{}; + + return query::unescape(std::string{value}, false); + } + template [[nodiscard]] inline T value_as(const std::string_view& key) const { static_assert(converter::supported, "Target type isn't supported!"); @@ -76,16 +91,23 @@ namespace ts { return converter::from_string_view(this->value(key)); } + [[nodiscard]] inline size_t command_character_index() const { return this->abs_index; } + [[nodiscard]] inline size_t key_command_character_index(const std::string_view& key) const { + size_t begin{0}; + if(!value_raw_impl(this->data, key, begin, nullptr)) return this->abs_index; + return this->abs_index + begin; + } protected: - command_string_parser(size_t index, std::string_view data) : index{index}, data{std::move(data)} {} + command_string_parser(size_t index, size_t abs_index, std::string_view data) : index{index}, abs_index{abs_index}, data{data} {} + size_t abs_index{}; size_t index{}; std::string_view data{}; }; } struct command_bulk : public impl::command_string_parser { - command_bulk(size_t index, std::string_view data) : command_string_parser{index, std::move(data)} {} + command_bulk(size_t index, size_t abs_index, std::string_view data) : command_string_parser{index, abs_index, data} {} inline bool next_entry(size_t& index, std::string_view& key, std::string& value) const { auto next_key = this->data.find_first_not_of(' ', index); @@ -119,7 +141,7 @@ namespace ts { failed }; - explicit command_parser(std::string command) : _command{std::move(command)}, impl::command_string_parser{std::string::npos, this->_command} { } + explicit command_parser(std::string command) : impl::command_string_parser{std::string::npos, 0, command}, _command{std::move(command)} { } bool parse(bool /* contains identifier */); @@ -135,25 +157,23 @@ namespace ts { [[nodiscard]] inline bool has_switch(const std::string_view& key) const noexcept { size_t index{0}; do { - index = this->_command_view.find(key, index); + index = this->data.find(key, index); index -= 1; if(index > key.size()) return false; - if(this->_command_view[index] == '-') return index == 0 || this->_command_view[index - 1] == ' '; + if(this->data[index] == '-') return index == 0 || this->data[index - 1] == ' '; index += 2; } while(true); } [[nodiscard]] const std::vector& bulks() const { return this->_bulks; } + + [[nodiscard]] std::optional next_bulk_containing(const std::string_view& /* key */, size_t /* bulk offset */) const; private: static command_bulk empty_bulk; const std::string _command{}; - const std::string_view _command_view{}; - std::string_view command_type{}; - std::vector _bulks{}; - std::vector flags{}; }; class command_builder_bulk { diff --git a/src/sql/SqlQuery.h b/src/sql/SqlQuery.h index eceac7c..f78f9c6 100644 --- a/src/sql/SqlQuery.h +++ b/src/sql/SqlQuery.h @@ -53,7 +53,7 @@ namespace sql { return *this; } - std::string fmtStr(){ + std::string fmtStr() const { std::stringstream s; operator<<(s, *this); return s.str(); @@ -285,12 +285,6 @@ namespace sql { return *(SelfType*) this; } - template - SelfType& value(const std::initializer_list& val) { - //this->_data->variables.push_back(val.begin()); - return *(SelfType*) this; - } - template SelfType& value(const std::string& key, T&& value) { this->_data->variables.push_back(variable{key, value}); diff --git a/src/sql/insert.h b/src/sql/insert.h new file mode 100644 index 0000000..d68edea --- /dev/null +++ b/src/sql/insert.h @@ -0,0 +1,153 @@ +#pragma once + +namespace sql { + + template + struct Column { + constexpr explicit Column(const std::string_view& name) : name{name} { } + + const std::string name; + }; + + struct BindingNameGenerator { + public: + [[nodiscard]] static std::string generate(size_t index) { + assert(index > 0); + std::string result{}; + result.resize(std::max(index >> 4U, 1UL)); + for(auto it = result.begin(); index > 0; index >>= 4U) + *(it++) = number_map[index & 0xFU]; + return result; + } + + private: + constexpr static std::array number_map{ + 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', + 'm', 'n', 'o', 'p' + }; + }; + + template + struct InsertQueryGenerator { + struct GenerateOptions { + bool enable_ignore{false}; + sql::SqlType target{sql::SqlType::TYPE_SQLITE}; + }; + + explicit InsertQueryGenerator(std::string target_table, Column... columns) : table_name{std::move(target_table)}, columns{std::move(columns.name)...} { } + + [[nodiscard]] constexpr inline auto column_count() const { return sizeof...(ColTypes); }; + + [[nodiscard]] inline std::string generate_query(size_t entry_count, sql::SqlType target = sql::SqlType::TYPE_SQLITE, bool enable_ignore = false) const { + if(entry_count == 0) return "-- No entries given"; + + std::string result{"INSERT "}; + if(enable_ignore) { + if(target == sql::TYPE_MYSQL) + result += "IGNORE "; + else + result += "OR IGNORE "; + } + + result += this->table_name + " ("; + //"INSERT INTO " + this->table_name + " (" + for(auto it = this->columns.begin();;) { + result += "`" + *it + "`"; + if(++it != this->columns.end()) { + result += ", "; + } else { + break; + } + } + result += ") VALUES ("; + for(size_t index{1}; index <= this->column_count() * entry_count; index++) + result += ":" + BindingNameGenerator::generate(index) + ", "; + result = result.substr(0, result.length() - 2) + ");"; + return result; + } + + const std::string table_name{}; + const std::array columns{}; + }; + + template + struct InsertQuery : public InsertQueryGenerator { + struct ExecuteResult { + std::vector> failed_entries{}; + + [[nodiscard]] inline auto has_succeeded() const { return this->failed_entries.empty(); } + }; + + explicit InsertQuery(const std::string& target_table, Column... columns) : InsertQueryGenerator{target_table, std::move(columns)...} { } + + inline void reserve_entries(size_t amount) { + this->entries.reserve(amount); + } + + inline void add_entry(const ColTypes&... values) { + this->entries.push_back(std::array{variable{"", values}...}); + } + + inline ExecuteResult execute(sql::SqlManager* sql, bool ignore_fails = false) { + if(this->entries.empty()) return ExecuteResult{}; + + ExecuteResult result{}; + + const auto chunk_size = std::min(2UL, this->entries.size()); + sql::model chunk_base{sql, this->generate_query(chunk_size, sql->getType(), ignore_fails)}; + sql::model entry_model{sql, this->generate_query(1, sql->getType(), ignore_fails)}; + + for(size_t chunk{0}; chunk < this->entries.size() / chunk_size; chunk++) { + auto command = chunk_base.command(); + size_t parameter_index{1}; + for(size_t index{chunk * chunk_size}; index < (chunk + 1) * chunk_size; index++) { + for(auto& var : this->entries[index]) { + var.set_key(":" + BindingNameGenerator::generate(parameter_index++)); + command.value(var); + } + } + + auto exec_result = command.execute(); + if(!exec_result) { + /* try every entry 'till we've found the error one */ + for(size_t index{chunk * chunk_size}; index < (chunk + 1) * chunk_size; index++) { + parameter_index = 1; + + auto entry_command = entry_model.command(); + for(auto& var : this->entries[index]) { + var.set_key(":" + BindingNameGenerator::generate(parameter_index++)); + entry_command.value(var); + } + exec_result = entry_command.execute(); + if(!exec_result) { + result.failed_entries.emplace_back(index, std::move(exec_result)); + if(!ignore_fails) + return result; + } + } + } + } + + for(size_t index{(this->entries.size() / chunk_size) * chunk_size}; index < this->entries.size(); index++) { + size_t parameter_index{1}; + auto entry_command = entry_model.command(); + for(auto& var : this->entries[index]) { + var.set_key(":" + BindingNameGenerator::generate(parameter_index++)); + entry_command.value(var); + } + auto exec_result = entry_command.execute(); + if(!exec_result) { + result.failed_entries.emplace_back(index, std::move(exec_result)); + if(!ignore_fails) + return result; + } + } + + return result; + } + + std::vector> entries{}; + }; + +} \ No newline at end of file diff --git a/src/sql/mysql/MySQL.cpp b/src/sql/mysql/MySQL.cpp index 4ded0e6..42a70c2 100644 --- a/src/sql/mysql/MySQL.cpp +++ b/src/sql/mysql/MySQL.cpp @@ -49,7 +49,7 @@ inline result parse_url(const string& url, std::map& c index = idx + 1; } while(index != 0); } - + //TODO: Set CLIENT_MULTI_STATEMENTS //if(!connect_map["hostName"].get() || strcmp(*connect_map["hostName"].get(), "")) connect_map["hostName"] = target_url; logTrace(LOG_GENERAL, "Got mysql property {}. Value: {}", "hostName", target_url); diff --git a/src/sql/sqlite/SqliteSQL.cpp b/src/sql/sqlite/SqliteSQL.cpp index d098ed3..3fac9db 100644 --- a/src/sql/sqlite/SqliteSQL.cpp +++ b/src/sql/sqlite/SqliteSQL.cpp @@ -54,32 +54,30 @@ std::shared_ptr SqliteManager::copyCommandData(std::shared_ptr '" + val.value() + "' in query '" + sqlite3_sql(stmt) + "'" << endl; - return; - } +namespace sql::sqlite { + inline void bindVariable(sqlite3_stmt* stmt, int& valueIndex, const variable& val){ + valueIndex = sqlite3_bind_parameter_index(stmt, val.key().c_str()); + if(valueIndex == 0){ //TODO maybe throw an exception + //cerr << "Cant find variable '" + val.key() + "' -> '" + val.value() + "' in query '" + sqlite3_sql(stmt) + "'" << endl; + return; + } - int resultState = 0; - if(val.type() == VARTYPE_NULL) - resultState = sqlite3_bind_null(stmt, valueIndex); - else if(val.type() == VARTYPE_TEXT) - resultState = sqlite3_bind_text(stmt, valueIndex, val.value().c_str(), val.value().length(), SQLITE_TRANSIENT); - else if(val.type() == VARTYPE_INT || val.type() == VARTYPE_BOOLEAN) - resultState = sqlite3_bind_int(stmt, valueIndex, val.as()); - else if(val.type() == VARTYPE_LONG) - resultState = sqlite3_bind_int64(stmt, valueIndex, val.as()); - else if(val.type() == VARTYPE_DOUBLE || val.type() == VARTYPE_FLOAT) - resultState = sqlite3_bind_double(stmt, valueIndex, val.as()); - else cerr << "Invalid value type!" << endl; //TODO throw exception + int resultState = 0; + if(val.type() == VARTYPE_NULL) + resultState = sqlite3_bind_null(stmt, valueIndex); + else if(val.type() == VARTYPE_TEXT) + resultState = sqlite3_bind_text(stmt, valueIndex, val.value().c_str(), val.value().length(), SQLITE_TRANSIENT); + else if(val.type() == VARTYPE_INT || val.type() == VARTYPE_BOOLEAN) + resultState = sqlite3_bind_int(stmt, valueIndex, val.as()); + else if(val.type() == VARTYPE_LONG) + resultState = sqlite3_bind_int64(stmt, valueIndex, val.as()); + else if(val.type() == VARTYPE_DOUBLE || val.type() == VARTYPE_FLOAT) + resultState = sqlite3_bind_double(stmt, valueIndex, val.as()); + else cerr << "Invalid value type!" << endl; //TODO throw exception - if(resultState != SQLITE_OK){ - cerr << "Invalid bind. " << sqlite3_errmsg(sqlite3_db_handle(stmt)) << " Index: " << valueIndex << endl; //TODO throw exception - } + if(resultState != SQLITE_OK){ + cerr << "Invalid bind. " << sqlite3_errmsg(sqlite3_db_handle(stmt)) << " Index: " << valueIndex << endl; //TODO throw exception } } } diff --git a/test/CommandTest.cpp b/test/CommandTest.cpp index a0b8d83..f425b87 100644 --- a/test/CommandTest.cpp +++ b/test/CommandTest.cpp @@ -132,10 +132,12 @@ int main() { ts::command cmd("notify"); */ - auto command = ts::command_parser{"a a=c a=c2 -z |-? a=2 key_c=c"}; + auto command = ts::command_parser{"a a=x |x b=x | c=x | a=x"}; if(!command.parse(true)) return 1; print_entries(command); + auto next = command.next_bulk_containing("a", 0); + std::cout << (next.has_value() ? *next : -1) << "\n"; return 0; std::cout << "Command v3:\n"; diff --git a/test/SQL2Test.cpp b/test/SQL2Test.cpp new file mode 100644 index 0000000..e7285a4 --- /dev/null +++ b/test/SQL2Test.cpp @@ -0,0 +1,169 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +template +struct Column { + constexpr explicit Column(const std::string_view& name) : name{name} { } + + const std::string name; +}; + +struct BindingNameGenerator { + public: + [[nodiscard]] static std::string generate(size_t index) { + assert(index > 0); + std::string result{}; + result.resize(std::max(index >> 4U, 1UL)); + for(auto it = result.begin(); index > 0; index >>= 4U) + *(it++) = number_map[index & 0xFU]; + return result; + } + + private: + constexpr static std::array number_map{ + 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', + 'm', 'n', 'o', 'p' + }; +}; + +template +struct InsertQueryGenerator { + struct GenerateOptions { + bool enable_ignore{false}; + sql::SqlType target{sql::SqlType::TYPE_SQLITE}; + }; + + explicit InsertQueryGenerator(std::string target_table, Column... columns) : table_name{std::move(target_table)}, columns{std::move(columns.name)...} { } + + [[nodiscard]] constexpr inline auto column_count() const { return sizeof...(ColTypes); }; + + [[nodiscard]] inline std::string generate_query(size_t entry_count, sql::SqlType target = sql::SqlType::TYPE_SQLITE, bool enable_ignore = false) const { + if(entry_count == 0) return "-- No entries given"; + + std::string result{"INSERT "}; + if(enable_ignore) { + if(target == sql::TYPE_MYSQL) + result += "IGNORE "; + else + result += "OR IGNORE "; + } + + result += this->table_name + " ("; + //"INSERT INTO " + this->table_name + " (" + for(auto it = this->columns.begin();;) { + result += "`" + *it + "`"; + if(++it != this->columns.end()) { + result += ", "; + } else { + break; + } + } + result += ") VALUES ("; + for(size_t index{1}; index <= this->column_count() * entry_count; index++) + result += ":" + BindingNameGenerator::generate(index) + ", "; + result = result.substr(0, result.length() - 2) + ");"; + return result; + } + + const std::string table_name{}; + const std::array columns{}; +}; + +template +struct InsertQuery : public InsertQueryGenerator { + struct ExecuteResult { + std::vector> failed_entries{}; + + [[nodiscard]] inline auto has_succeeded() const { return this->failed_entries.empty(); } + }; + + explicit InsertQuery(const std::string& target_table, Column... columns) : InsertQueryGenerator{target_table, std::move(columns)...} { } + + inline void reserve_entries(size_t amount) { + this->entries.reserve(amount); + } + + inline void add_entry(const ColTypes&... values) { + this->entries.push_back(std::array{variable{"", values}...}); + } + + inline ExecuteResult execute(sql::SqlManager* sql, bool ignore_fails = false) { + if(this->entries.empty()) return ExecuteResult{}; + + ExecuteResult result{}; + + const auto chunk_size = std::min(2UL, this->entries.size()); + sql::model chunk_base{sql, this->generate_query(chunk_size, sql->getType(), ignore_fails)}; + sql::model entry_model{sql, this->generate_query(1, sql->getType(), ignore_fails)}; + + for(size_t chunk{0}; chunk < this->entries.size() / chunk_size; chunk++) { + auto command = chunk_base.command(); + size_t parameter_index{1}; + for(size_t index{chunk * chunk_size}; index < (chunk + 1) * chunk_size; index++) { + for(auto& var : this->entries[index]) { + var.set_key(":" + BindingNameGenerator::generate(parameter_index++)); + command.value(var); + } + } + + auto exec_result = command.execute(); + if(!exec_result) { + /* try every entry 'till we've found the error one */ + for(size_t index{chunk * chunk_size}; index < (chunk + 1) * chunk_size; index++) { + parameter_index = 1; + + auto entry_command = entry_model.command(); + for(auto& var : this->entries[index]) { + var.set_key(":" + BindingNameGenerator::generate(parameter_index++)); + entry_command.value(var); + } + exec_result = entry_command.execute(); + if(!exec_result) { + result.failed_entries.emplace_back(index, std::move(exec_result)); + if(!ignore_fails) + return result; + } + } + } + } + + for(size_t index{(this->entries.size() / chunk_size) * chunk_size}; index < this->entries.size(); index++) { + size_t parameter_index{1}; + auto entry_command = entry_model.command(); + for(auto& var : this->entries[index]) { + var.set_key(":" + BindingNameGenerator::generate(parameter_index++)); + entry_command.value(var); + } + auto exec_result = entry_command.execute(); + if(!exec_result) { + result.failed_entries.emplace_back(index, std::move(exec_result)); + if(!ignore_fails) + return result; + } + } + + return result; + } + + std::vector> entries{}; +}; + +int main() { + InsertQuery insert{"hello", Column{"column_a"}, Column{"column_ab"}}; + + for(int i{0}; i < 100; i++) + insert.add_entry(i, "X"); + + std::cout << "Result: " << insert.generate_query(2) << "\n"; + + std::string value{}; + insert.add_entry(2, value); + insert.execute(nullptr); +} \ No newline at end of file