diff --git a/.appveyor.yml b/.appveyor.yml index 6bbe22d5..7b5dacf3 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -1,16 +1,14 @@ os: - - Visual Studio 2015 + - Visual Studio 2017 configuration: - Debug platform: - - Win32 + - Win64 environment: MSVC_DEFAULT_OPTIONS: ON - BOOST_ROOT_DIR: "C:\\Libraries\\boost_1_63_0" - BOOST_LIB_DIR: "C:\\Libraries\\boost_1_63_0\\lib32-msvc-14.0" MYSQL_PWD: "Password12!" services: @@ -22,7 +20,7 @@ before_build: - git submodule update --init - mkdir build - cd build - - cmake .. -G "Visual Studio 14 2015" + - cmake .. -G "Visual Studio 15 2017 Win64" - cmake --build . --target ALL_BUILD --config Debug build_script: diff --git a/.gitmodules b/.gitmodules index 14f0cb00..69f7a3a6 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,6 @@ -[submodule "src/libraries"] - path = src/libraries - url = https://github.com/SapphireMordred/SapphireLibs.git - ignore = dirty +[submodule "deps/asio"] + path = deps/asio + url = https://github.com/chriskohlhoff/asio.git +[submodule "deps/spdlog"] + path = deps/spdlog + url = https://github.com/gabime/spdlog.git diff --git a/.travis.yml b/.travis.yml index 017917dd..3df10328 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,7 +18,7 @@ matrix: - clang-6.0 - g++-7 env: - - MATRIX_EVAL="CC=clang-6.0 && CXX=clang++-6.0" + - MATRIX_EVAL="CC=gcc-7 && CXX=g++-7" # Setup cache cache: @@ -32,7 +32,6 @@ before_install: - gem install --no-ri --no-rdoc mtime_cache - sudo add-apt-repository -y ppa:rexut/recoil - sudo apt-get update - - sudo apt-get install -y libboost1.63-dev libboost1.63-all-dev - sudo apt-get install -y libmysqlclient-dev # Build steps @@ -41,6 +40,6 @@ script: - mtime_cache src/**/*.{%{cpp}} -c .mtime_cache/cache.json - mkdir -p build - cd build - - cmake .. -DSAPPHIRE_BOOST_VER="1.63.0" && make -j 3 + - cmake .. && make -j 3 - cd .. - bash sql_import.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index b9b359a5..4e82357f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,33 +1,17 @@ -cmake_policy(SET CMP0014 NEW) -cmake_minimum_required(VERSION 3.0.2) -project (Sapphire) +cmake_policy( SET CMP0014 NEW ) +cmake_minimum_required( VERSION 3.0.2 ) +project( Sapphire ) -set(CMAKE_BINARY_DIR ${CMAKE_SOURCE_DIR}/bin) +set( CMAKE_BINARY_DIR ${CMAKE_SOURCE_DIR}/bin ) -set(EXECUTABLE_OUTPUT_PATH ${CMAKE_SOURCE_DIR}/bin) -set(LIBRARY_OUTPUT_PATH ${CMAKE_BINARY_DIR}/bin) -set(EXECUTABLE_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin) +set( EXECUTABLE_OUTPUT_PATH ${CMAKE_SOURCE_DIR}/bin ) +set( LIBRARY_OUTPUT_PATH ${CMAKE_BINARY_DIR}/bin ) +set( EXECUTABLE_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin ) -set(PROJECT_INCLUDE_DIR ${PROJECT_SOURCE_DIR}/include) -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) - -########################################################################## -# Boost stuff - -# set(Boost_DEBUG 1) - -if( NOT SAPPHIRE_BOOST_VER ) - set( SAPPHIRE_BOOST_VER 1.63.0 ) -endif() -set( SAPPHIRE_BOOST_FOLDER_NAME boost_1_63_0 ) - -########################################################################## -# Common and library path -set( LIBRARY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/src/libraries" ) +set( CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake ) ########################################################################## # Dependencies and compiler settings -include( "cmake/boost.cmake" ) include( "cmake/mysql.cmake" ) include( "cmake/compiler.cmake" ) include( "cmake/cotire.cmake" ) @@ -40,12 +24,20 @@ git_describe( VERSION --all --dirty=-d ) configure_file( "${CMAKE_CURRENT_SOURCE_DIR}/src/common/Version.cpp.in" "${CMAKE_CURRENT_SOURCE_DIR}/src/common/Version.cpp" @ONLY ) +############################## +# Mysql # +############################## +find_package( MySQL ) + ########################################################################## -add_subdirectory( "src/libraries/sapphire/datReader" ) -add_subdirectory( "src/libraries/sapphire/mysqlConnector" ) +add_subdirectory( "deps/zlib" ) +add_subdirectory( "deps/MySQL" ) +add_subdirectory( "deps/datReader" ) +add_subdirectory( "deps/mysqlConnector" ) add_subdirectory( "src/common" ) add_subdirectory( "src/servers" ) +#add_subdirectory( "src/dbm" ) add_subdirectory( "src/tools/exd_common_gen" ) add_subdirectory( "src/tools/exd_struct_gen" ) @@ -53,5 +45,5 @@ add_subdirectory( "src/tools/exd_struct_test" ) add_subdirectory( "src/tools/quest_parser" ) add_subdirectory( "src/tools/discovery_parser" ) add_subdirectory( "src/tools/mob_parse" ) -#add_subdirectory("src/tools/pcb_reader") -#add_subdirectory("src/tools/event_object_parser") +add_subdirectory( "src/tools/pcb_reader" ) +add_subdirectory( "src/tools/event_object_parser" ) diff --git a/CMakeSettings.json b/CMakeSettings.json index 90e895cd..c623adf2 100644 --- a/CMakeSettings.json +++ b/CMakeSettings.json @@ -1,75 +1,13 @@ { // See https://go.microsoft.com//fwlink//?linkid=834763 for more information about this file. "configurations": [ - { - "name": "x86-Debug", - "generator": "Visual Studio 14 2015", - "configurationType": "Debug", - "buildRoot": "${env.USERPROFILE}\\CMakeBuild\\${workspaceHash}\\build\\${name}", - "cmakeCommandArgs": "-DCMAKE_BUILD_TYPE=\"Debug\"", - "buildCommandArgs": "-m -v:minimal", - "variables": [ - { - "name": "Boost_COMPILER", - "value": "-vc140" - } - ] - }, - { - "name": "x86-Release", - "generator": "Visual Studio 14 2015", - "configurationType": "Release", - "buildRoot": "${env.USERPROFILE}\\CMakeBuild\\${workspaceHash}\\build\\${name}", - "cmakeCommandArgs": "", - "buildCommandArgs": "-m -v:minimal", - "variables": [ - { - "name": "Boost_COMPILER", - "value": "-vc140" - } - ] - }, - { - "name": "x64-Debug", - "generator": "Visual Studio 14 2015 Win64", - "configurationType": "Debug", - "buildRoot": "${env.USERPROFILE}\\CMakeBuild\\${workspaceHash}\\build\\${name}", - "cmakeCommandArgs": "-DCMAKE_BUILD_TYPE=\"Debug\"", - "buildCommandArgs": "-m -v:minimal", - "variables": [ - { - "name": "Boost_COMPILER", - "value": "-vc140" - } - ] - }, - { - "name": "x64-Release", - "generator": "Visual Studio 14 2015 Win64", - "configurationType": "Release", - "buildRoot": "${env.USERPROFILE}\\CMakeBuild\\${workspaceHash}\\build\\${name}", - "cmakeCommandArgs": "", - "buildCommandArgs": "-m -v:minimal", - "variables": [ - { - "name": "Boost_COMPILER", - "value": "-vc140" - } - ] - }, { "name": "x86-Debug", "generator": "Visual Studio 15 2017", "configurationType": "Debug", "buildRoot": "${env.USERPROFILE}\\CMakeBuild\\${workspaceHash}\\build\\${name}", "cmakeCommandArgs": "-DCMAKE_BUILD_TYPE=\"Debug\"", - "buildCommandArgs": "-m -v:minimal", - "variables": [ - { - "name": "Boost_COMPILER", - "value": "-vc141" - } - ] + "buildCommandArgs": "-m -v:minimal" }, { "name": "x86-Release", @@ -77,13 +15,7 @@ "configurationType": "Release", "buildRoot": "${env.USERPROFILE}\\CMakeBuild\\${workspaceHash}\\build\\${name}", "cmakeCommandArgs": "", - "buildCommandArgs": "-m -v:minimal", - "variables": [ - { - "name": "Boost_COMPILER", - "value": "-vc141" - } - ] + "buildCommandArgs": "-m -v:minimal" }, { "name": "x64-Debug", @@ -91,13 +23,7 @@ "configurationType": "Debug", "buildRoot": "${env.USERPROFILE}\\CMakeBuild\\${workspaceHash}\\build\\${name}", "cmakeCommandArgs": "-DCMAKE_BUILD_TYPE=\"Debug\"", - "buildCommandArgs": "-m -v:minimal", - "variables": [ - { - "name": "Boost_COMPILER", - "value": "-vc141" - } - ] + "buildCommandArgs": "-m -v:minimal" }, { "name": "x64-Release", @@ -105,13 +31,7 @@ "configurationType": "Release", "buildRoot": "${env.USERPROFILE}\\CMakeBuild\\${workspaceHash}\\build\\${name}", "cmakeCommandArgs": "", - "buildCommandArgs": "-m -v:minimal", - "variables": [ - { - "name": "Boost_COMPILER", - "value": "-vc141" - } - ] + "buildCommandArgs": "-m -v:minimal" } ] } diff --git a/README.md b/README.md index 2b7b3878..0626bdf5 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,13 @@ # Sapphire - FINAL FANTASY XIV Server Emulator + +

+ FFXIV Sapphire +

+ [![Discord Server](https://img.shields.io/badge/discord-Sapphire-7289DA.svg)](https://discord.gg/xxcdCER) [![Linux Build Status](https://travis-ci.org/SapphireMordred/Sapphire.svg?branch=master)](https://travis-ci.org/SapphireMordred/Sapphire) [![Windows Build Status](https://ci.appveyor.com/api/projects/status/lil7lxa3ty165emm?svg=true)](https://ci.appveyor.com/project/SapphireMordred/Sapphire) -![FFXIV Sapphire](http://i.imgur.com/I4bj1tR.png) + Sapphire is a FINAL FANTASY XIV 4.0+ Server Emulator currently in development. @@ -13,8 +18,7 @@ Sapphire requires the following software: | *Name* | *Windows* | *Linux* | | ------ | --------- | ------- | -| CMake 3.0.2+ and C++14 capable compiler | [Visual Studio 2017](https://www.visualstudio.com/) | `gcc 4.9` and `g++ 4.9` or newer | -| Boost 1.63.0 | [Win32 precompiled binaries](https://sourceforge.net/projects/boost/files/boost-binaries/1.63.0/boost_1_63_0-msvc-14.0-32.exe/download) | Boost libraries from your distribution's package manager | +| CMake 3.0.2+ and C++17 capable compiler | [Visual Studio 2017](https://www.visualstudio.com/) | `gcc 7` and `g++ 7` or newer | | MySQL Server 5.7 | [Official Site](https://dev.mysql.com/downloads/mysql/) | MySQL server from your distribution's package manager | Please check the [wiki](https://github.com/SapphireMordred/Sapphire/wiki) for detailed installation/build instructions for your OS. diff --git a/bin/config/config.ini.default b/bin/config/config.ini.default new file mode 100644 index 00000000..4c11b849 --- /dev/null +++ b/bin/config/config.ini.default @@ -0,0 +1,58 @@ +[Database] +Host = 127.0.0.1 +Port = 3306 +Database = sapphire +Username = sapphire +Password = +SyncThreads = 2 +AsyncThreads = 2 + +[GlobalParameters] +ServerSecret = default +DataPath = C:\\SquareEnix\\FINAL FANTASY XIV - A Realm Reborn\\game\\sqpack + +[GlobalNetwork] +; Values definining how Users and other servers will access - these have to be set to your public IP when running a public server +ZoneHost = 127.0.0.1 +ZonePort = 54992 + +LobbyHost = 127.0.0.1 +LobbyPort = 54994 + +RestHost = 127.0.0.1 +RestPort = 80 + +[Lobby] +WorldID = 67 +AllowNoSessionConnect = false +WorldName = Sapphire + +[LobbyNetwork] +ListenIp = 0.0.0.0 +ListenPort = 54994 + +[CharacterCreation] +DefaultGMRank = 255 + +[RestNetwork] +ListenIp = 0.0.0.0 +ListenPort = 80 + +[Scripts] +; where compiled script modules are located +Path = ./compiledscripts/ +; relative to Path, where we copy and load modules from +CachePath = ./cache/ +; whether we should detect changes to script modules and reload them +HotSwap = true + +[Network] +DisconnectTimeout = 20 + +[ZoneNetwork] +ListenIp = 0.0.0.0 +ListenPort = 54992 + +[General] +; Sent on login - each line must be shorter than 307 characters, split lines with ';' +MotD = Welcome to Sapphire!;This is a very good server;You can change these messages by editing General.MotD in config/zone.ini \ No newline at end of file diff --git a/bin/config/global.ini.default b/bin/config/global.ini.default deleted file mode 100644 index 072d3f7c..00000000 --- a/bin/config/global.ini.default +++ /dev/null @@ -1,23 +0,0 @@ -[Database] -Host = 127.0.0.1 -Port = 3306 -Database = sapphire -Username = root -Password = -SyncThreads = 2 -AsyncThreads = 2 - -[GlobalParameters] -ServerSecret = default -DataPath = C:\\SquareEnix\\FINAL FANTASY XIV - A Realm Reborn\\game\\sqpack - -[GlobalNetwork] -; Values definining how Users and other servers will access - these have to be set to your public IP when running a public server -ZoneHost = 127.0.0.1 -ZonePort = 54992 - -LobbyHost = 127.0.0.1 -LobbyPort = 54994 - -RestHost = 127.0.0.1 -RestPort = 80 \ No newline at end of file diff --git a/bin/config/lobby.ini.default b/bin/config/lobby.ini.default deleted file mode 100644 index 077e947c..00000000 --- a/bin/config/lobby.ini.default +++ /dev/null @@ -1,8 +0,0 @@ -[Lobby] -WorldID = 67 -AllowNoSessionConnect = false -WorldName = Sapphire - -[LobbyNetwork] -ListenIp = 0.0.0.0 -ListenPort = 54994 \ No newline at end of file diff --git a/bin/config/rest.ini.default b/bin/config/rest.ini.default deleted file mode 100644 index d51b4967..00000000 --- a/bin/config/rest.ini.default +++ /dev/null @@ -1,6 +0,0 @@ -[CharacterCreation] -DefaultGMRank = 255 - -[RestNetwork] -ListenIp = 0.0.0.0 -ListenPort = 80 \ No newline at end of file diff --git a/bin/config/zone.ini.default b/bin/config/zone.ini.default deleted file mode 100644 index 7631e9eb..00000000 --- a/bin/config/zone.ini.default +++ /dev/null @@ -1,18 +0,0 @@ -[Scripts] -; where compiled script modules are located -Path = ./compiledscripts/ -; relative to Path, where we copy and load modules from -CachePath = ./cache/ -; whether we should detect changes to script modules and reload them -HotSwap = true - -[Network] -DisconnectTimeout = 20 - -[ZoneNetwork] -ListenIp = 0.0.0.0 -ListenPort = 54992 - -[General] -; Sent on login - each line must be shorter than 307 characters, split lines with ';' -MotD = Welcome to Sapphire!;This is a very good server;You can change these messages by editing General.MotD in config/zone.ini \ No newline at end of file diff --git a/bin/zlib1.dll b/bin/zlib1.dll deleted file mode 100644 index ea747b42..00000000 Binary files a/bin/zlib1.dll and /dev/null differ diff --git a/cmake/FindMySQL.cmake b/cmake/FindMySQL.cmake new file mode 100644 index 00000000..5fd19dae --- /dev/null +++ b/cmake/FindMySQL.cmake @@ -0,0 +1,291 @@ +# +# Find the MySQL client includes and library +# + +# This module defines +# MYSQL_INCLUDE_DIR, where to find mysql.h +# MYSQL_LIBRARIES, the libraries to link against to connect to MySQL +# MYSQL_EXECUTABLE, the MySQL executable. +# MYSQL_FOUND, if false, you cannot build anything that requires MySQL. + +# also defined, but not for general use are +# MYSQL_LIBRARY, where to find the MySQL library. + +set( MYSQL_FOUND 0 ) + +if( UNIX ) + set(MYSQL_CONFIG_PREFER_PATH "$ENV{MYSQL_HOME}/bin" CACHE FILEPATH + "preferred path to MySQL (mysql_config)" + ) + + find_program(MYSQL_CONFIG mysql_config + ${MYSQL_CONFIG_PREFER_PATH} + /usr/local/mysql/bin/ + /usr/local/bin/ + /usr/bin/ + ) + + if( MYSQL_CONFIG ) + message(STATUS "Using mysql-config: ${MYSQL_CONFIG}") + # set INCLUDE_DIR + exec_program(${MYSQL_CONFIG} + ARGS --include + OUTPUT_VARIABLE MY_TMP + ) + + string(REGEX REPLACE "-I([^ ]*)( .*)?" "\\1" MY_TMP "${MY_TMP}") + set(MYSQL_ADD_INCLUDE_PATH ${MY_TMP} CACHE FILEPATH INTERNAL) + #message("[DEBUG] MYSQL ADD_INCLUDE_PATH : ${MYSQL_ADD_INCLUDE_PATH}") + # set LIBRARY_DIR + exec_program(${MYSQL_CONFIG} + ARGS --libs_r + OUTPUT_VARIABLE MY_TMP + ) + set(MYSQL_ADD_LIBRARIES "") + string(REGEX MATCHALL "-l[^ ]*" MYSQL_LIB_LIST "${MY_TMP}") + foreach(LIB ${MYSQL_LIB_LIST}) + string(REGEX REPLACE "[ ]*-l([^ ]*)" "\\1" LIB "${LIB}") + list(APPEND MYSQL_ADD_LIBRARIES "${LIB}") + #message("[DEBUG] MYSQL ADD_LIBRARIES : ${MYSQL_ADD_LIBRARIES}") + endforeach(LIB ${MYSQL_LIB_LIST}) + + set(MYSQL_ADD_LIBRARIES_PATH "") + string(REGEX MATCHALL "-L[^ ]*" MYSQL_LIBDIR_LIST "${MY_TMP}") + foreach(LIB ${MYSQL_LIBDIR_LIST}) + string(REGEX REPLACE "[ ]*-L([^ ]*)" "\\1" LIB "${LIB}") + list(APPEND MYSQL_ADD_LIBRARIES_PATH "${LIB}") + #message("[DEBUG] MYSQL ADD_LIBRARIES_PATH : ${MYSQL_ADD_LIBRARIES_PATH}") + endforeach(LIB ${MYSQL_LIBS}) + + else( MYSQL_CONFIG ) + set(MYSQL_ADD_LIBRARIES "") + list(APPEND MYSQL_ADD_LIBRARIES "mysqlclient_r") + endif( MYSQL_CONFIG ) +endif( UNIX ) + +if( WIN32 ) + # read environment variables and change \ to / + SET(PROGRAM_FILES_32 $ENV{ProgramFiles}) + if (${PROGRAM_FILES_32}) + STRING(REPLACE "\\\\" "/" PROGRAM_FILES_32 ${PROGRAM_FILES_32}) + endif(${PROGRAM_FILES_32}) + + SET(PROGRAM_FILES_64 $ENV{ProgramW6432}) + if (${PROGRAM_FILES_64}) + STRING(REPLACE "\\\\" "/" PROGRAM_FILES_64 ${PROGRAM_FILES_64}) + endif(${PROGRAM_FILES_64}) +endif ( WIN32 ) + +find_path(MYSQL_INCLUDE_DIR + NAMES + mysql.h + PATHS + ${MYSQL_ADD_INCLUDE_PATH} + /usr/include + /usr/include/mysql + /usr/local/include + /usr/local/include/mysql + /usr/local/mysql/include + "${PROGRAM_FILES_32}/MySQL/MySQL Server 5.7/include" + "${PROGRAM_FILES_32}/MySQL/MySQL Server 5.6/include" + "${PROGRAM_FILES_32}/MySQL/MySQL Server 5.5/include" + "${PROGRAM_FILES_32}/MySQL/MySQL Server 5.1/include" + "${PROGRAM_FILES_32}/MySQL/include" + "${PROGRAM_FILES_64}/MySQL/MySQL Server 5.7/include" + "${PROGRAM_FILES_64}/MySQL/MySQL Server 5.6/include" + "${PROGRAM_FILES_64}/MySQL/MySQL Server 5.5/include" + "${PROGRAM_FILES_64}/MySQL/MySQL Server 5.1/include" + "${PROGRAM_FILES_64}/MySQL/include" + "C:/MySQL/include" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\MySQL AB\\MySQL Server 5.7;Location]/include" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\MySQL AB\\MySQL Server 5.6;Location]/include" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\MySQL AB\\MySQL Server 5.5;Location]/include" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\MySQL AB\\MySQL Server 5.1;Location]/include" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\MySQL AB\\MySQL Server 5.7;Location]/include" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\MySQL AB\\MySQL Server 5.6;Location]/include" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\MySQL AB\\MySQL Server 5.5;Location]/include" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\MySQL AB\\MySQL Server 5.1;Location]/include" + "$ENV{ProgramFiles}/MySQL/MySQL Server 5.7/include" + "$ENV{ProgramFiles}/MySQL/MySQL Server 5.6/include" + "$ENV{ProgramFiles}/MySQL/MySQL Server 5.5/include" + "$ENV{ProgramFiles}/MySQL/MySQL Server 5.1/include" + "$ENV{SystemDrive}/MySQL/MySQL Server 5.7/include" + "$ENV{SystemDrive}/MySQL/MySQL Server 5.6/include" + "$ENV{SystemDrive}/MySQL/MySQL Server 5.5/include" + "$ENV{SystemDrive}/MySQL/MySQL Server 5.1/include" + "c:/msys/local/include" + "$ENV{MYSQL_ROOT}/include" + DOC + "Specify the directory containing mysql.h." +) + +if( UNIX ) + foreach(LIB ${MYSQL_ADD_LIBRARIES}) + find_library( MYSQL_LIBRARY + NAMES + mysql libmysql ${LIB} + PATHS + ${MYSQL_ADD_LIBRARIES_PATH} + /usr/lib + /usr/lib/mysql + /usr/local/lib + /usr/local/lib/mysql + /usr/local/mysql/lib + DOC "Specify the location of the mysql library here." + ) + endforeach(LIB ${MYSQL_ADD_LIBRARY}) +endif( UNIX ) + +if( WIN32 ) + find_library( MYSQL_LIBRARY + NAMES + libmysql + PATHS + ${MYSQL_ADD_LIBRARIES_PATH} + "${PROGRAM_FILES_32}/MySQL/MySQL Server 5.7/lib" + "${PROGRAM_FILES_32}/MySQL/MySQL Server 5.6/lib" + "${PROGRAM_FILES_32}/MySQL/MySQL Server 5.5/lib" + "${PROGRAM_FILES_32}/MySQL/MySQL Server 5.1/lib" + "${PROGRAM_FILES_32}/MySQL/MySQL Server 5.7/lib/opt" + "${PROGRAM_FILES_32}/MySQL/MySQL Server 5.6/lib/opt" + "${PROGRAM_FILES_32}/MySQL/MySQL Server 5.5/lib/opt" + "${PROGRAM_FILES_32}/MySQL/MySQL Server 5.1/lib/opt" + "${PROGRAM_FILES_32}/MySQL/lib" + "${PROGRAM_FILES_64}/MySQL/MySQL Server 5.7/lib" + "${PROGRAM_FILES_64}/MySQL/MySQL Server 5.6/lib" + "${PROGRAM_FILES_64}/MySQL/MySQL Server 5.5/lib" + "${PROGRAM_FILES_64}/MySQL/MySQL Server 5.1/lib" + "${PROGRAM_FILES_64}/MySQL/MySQL Server 5.7/lib/opt" + "${PROGRAM_FILES_64}/MySQL/MySQL Server 5.6/lib/opt" + "${PROGRAM_FILES_64}/MySQL/MySQL Server 5.5/lib/opt" + "${PROGRAM_FILES_64}/MySQL/MySQL Server 5.1/lib/opt" + "${PROGRAM_FILES_64}/MySQL/lib" + "C:/MySQL/lib/debug" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\MySQL AB\\MySQL Server 5.7;Location]/lib" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\MySQL AB\\MySQL Server 5.6;Location]/lib" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\MySQL AB\\MySQL Server 5.5;Location]/lib" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\MySQL AB\\MySQL Server 5.1;Location]/lib" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\MySQL AB\\MySQL Server 5.7;Location]/lib/opt" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\MySQL AB\\MySQL Server 5.6;Location]/lib/opt" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\MySQL AB\\MySQL Server 5.5;Location]/lib/opt" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\MySQL AB\\MySQL Server 5.1;Location]/lib/opt" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\MySQL AB\\MySQL Server 5.7;Location]/lib" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\MySQL AB\\MySQL Server 5.6;Location]/lib" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\MySQL AB\\MySQL Server 5.5;Location]/lib" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\MySQL AB\\MySQL Server 5.1;Location]/lib" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\MySQL AB\\MySQL Server 5.7;Location]/lib/opt" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\MySQL AB\\MySQL Server 5.6;Location]/lib/opt" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\MySQL AB\\MySQL Server 5.5;Location]/lib/opt" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\MySQL AB\\MySQL Server 5.1;Location]/lib/opt" + "$ENV{ProgramFiles}/MySQL/MySQL Server 5.7/lib/opt" + "$ENV{ProgramFiles}/MySQL/MySQL Server 5.6/lib/opt" + "$ENV{ProgramFiles}/MySQL/MySQL Server 5.5/lib/opt" + "$ENV{ProgramFiles}/MySQL/MySQL Server 5.1/lib/opt" + "$ENV{SystemDrive}/MySQL/MySQL Server 5.7/lib/opt" + "$ENV{SystemDrive}/MySQL/MySQL Server 5.6/lib/opt" + "$ENV{SystemDrive}/MySQL/MySQL Server 5.5/lib/opt" + "$ENV{SystemDrive}/MySQL/MySQL Server 5.1/lib/opt" + "c:/msys/local/include" + "$ENV{MYSQL_ROOT}/lib" + DOC "Specify the location of the mysql library here." + ) +endif( WIN32 ) + +# On Windows you typically don't need to include any extra libraries +# to build MYSQL stuff. + +if( NOT WIN32 ) + find_library( MYSQL_EXTRA_LIBRARIES + NAMES + z zlib + PATHS + /usr/lib + /usr/local/lib + DOC + "if more libraries are necessary to link in a MySQL client (typically zlib), specify them here." + ) +else( NOT WIN32 ) + set( MYSQL_EXTRA_LIBRARIES "" ) +endif( NOT WIN32 ) + +if( UNIX ) + find_program(MYSQL_EXECUTABLE mysql + PATHS + ${MYSQL_CONFIG_PREFER_PATH} + /usr/local/mysql/bin/ + /usr/local/bin/ + /usr/bin/ + DOC + "path to your mysql binary." + ) +endif( UNIX ) + +if( WIN32 ) + find_program(MYSQL_EXECUTABLE mysql + PATHS + "${PROGRAM_FILES_32}/MySQL/MySQL Server 5.7/bin" + "${PROGRAM_FILES_32}/MySQL/MySQL Server 5.6/bin" + "${PROGRAM_FILES_32}/MySQL/MySQL Server 5.5/bin" + "${PROGRAM_FILES_32}/MySQL/MySQL Server 5.1/bin" + "${PROGRAM_FILES_32}/MySQL/MySQL Server 5.7/bin/opt" + "${PROGRAM_FILES_32}/MySQL/MySQL Server 5.6/bin/opt" + "${PROGRAM_FILES_32}/MySQL/MySQL Server 5.5/bin/opt" + "${PROGRAM_FILES_32}/MySQL/MySQL Server 5.1/bin/opt" + "${PROGRAM_FILES_32}/MySQL/bin" + "${PROGRAM_FILES_64}/MySQL/MySQL Server 5.7/bin" + "${PROGRAM_FILES_64}/MySQL/MySQL Server 5.6/bin" + "${PROGRAM_FILES_64}/MySQL/MySQL Server 5.5/bin" + "${PROGRAM_FILES_64}/MySQL/MySQL Server 5.1/bin" + "${PROGRAM_FILES_64}/MySQL/MySQL Server 5.7/bin/opt" + "${PROGRAM_FILES_64}/MySQL/MySQL Server 5.6/bin/opt" + "${PROGRAM_FILES_64}/MySQL/MySQL Server 5.5/bin/opt" + "${PROGRAM_FILES_64}/MySQL/MySQL Server 5.1/bin/opt" + "${PROGRAM_FILES_64}/MySQL/bin" + "C:/MySQL/bin/debug" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\MySQL AB\\MySQL Server 5.7;Location]/bin" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\MySQL AB\\MySQL Server 5.6;Location]/bin" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\MySQL AB\\MySQL Server 5.5;Location]/bin" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\MySQL AB\\MySQL Server 5.1;Location]/bin" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\MySQL AB\\MySQL Server 5.7;Location]/bin/opt" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\MySQL AB\\MySQL Server 5.6;Location]/bin/opt" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\MySQL AB\\MySQL Server 5.5;Location]/bin/opt" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\MySQL AB\\MySQL Server 5.1;Location]/bin/opt" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\MySQL AB\\MySQL Server 5.7;Location]/bin" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\MySQL AB\\MySQL Server 5.6;Location]/bin" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\MySQL AB\\MySQL Server 5.5;Location]/bin" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\MySQL AB\\MySQL Server 5.1;Location]/bin" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\MySQL AB\\MySQL Server 5.7;Location]/bin/opt" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\MySQL AB\\MySQL Server 5.6;Location]/bin/opt" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\MySQL AB\\MySQL Server 5.5;Location]/bin/opt" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\MySQL AB\\MySQL Server 5.1;Location]/bin/opt" + "$ENV{ProgramFiles}/MySQL/MySQL Server 5.7/bin/opt" + "$ENV{ProgramFiles}/MySQL/MySQL Server 5.6/bin/opt" + "$ENV{ProgramFiles}/MySQL/MySQL Server 5.5/bin/opt" + "$ENV{ProgramFiles}/MySQL/MySQL Server 5.1/bin/opt" + "$ENV{SystemDrive}/MySQL/MySQL Server 5.7/bin/opt" + "$ENV{SystemDrive}/MySQL/MySQL Server 5.6/bin/opt" + "$ENV{SystemDrive}/MySQL/MySQL Server 5.5/bin/opt" + "$ENV{SystemDrive}/MySQL/MySQL Server 5.1/bin/opt" + "c:/msys/local/include" + "$ENV{MYSQL_ROOT}/bin" + DOC + "path to your mysql binary." + ) +endif( WIN32 ) + +if( MYSQL_LIBRARY ) + if( MYSQL_INCLUDE_DIR ) + set( MYSQL_FOUND 1 ) + message(STATUS "Found MySQL library: ${MYSQL_LIBRARY}") + message(STATUS "Found MySQL headers: ${MYSQL_INCLUDE_DIR}") + else( MYSQL_INCLUDE_DIR ) + message(FATAL_ERROR "Could not find MySQL headers! Please install the development libraries and headers") + endif( MYSQL_INCLUDE_DIR ) + if( MYSQL_EXECUTABLE ) + message(STATUS "Found MySQL executable: ${MYSQL_EXECUTABLE}") + endif( MYSQL_EXECUTABLE ) + mark_as_advanced( MYSQL_FOUND MYSQL_LIBRARY MYSQL_EXTRA_LIBRARIES MYSQL_INCLUDE_DIR MYSQL_EXECUTABLE) +else( MYSQL_LIBRARY ) + message(FATAL_ERROR "Could not find the MySQL libraries! Please install the development libraries and headers") +endif( MYSQL_LIBRARY ) + diff --git a/cmake/boost.cmake b/cmake/boost.cmake deleted file mode 100644 index 3e1c37ef..00000000 --- a/cmake/boost.cmake +++ /dev/null @@ -1,39 +0,0 @@ - -if(UNIX) - - find_package(Boost ${SAPPHIRE_BOOST_VER} COMPONENTS system) - if(Boost_FOUND) - set(BOOST_LIBRARY_DIR ${Boost_LIBRARY_DIR}) - else() - if (EXISTS /opt/build_libs/${SAPPHIRE_BOOST_FOLDER_NAME}) - set(Boost_INCLUDE_DIR /opt/build_libs/${SAPPHIRE_BOOST_FOLDER_NAME}) - set(BOOST_LIBRARYDIR /opt/build_libs/${SAPPHIRE_BOOST_FOLDER_NAME}/stage/lib) - else() - message(FATAL_ERROR "Unable to find boost ${SAPPHIRE_BOOST_VER} package!") - endif() - endif() -else() - - if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/src/libraries/external/${SAPPHIRE_BOOST_FOLDER_NAME}) - message(STATUS "Using boost in /libraries/external") - set(Boost_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src/libraries/external/${SAPPHIRE_BOOST_FOLDER_NAME}) - set(BOOST_LIBRARYDIR ${CMAKE_CURRENT_SOURCE_DIR}/src/libraries/external/${SAPPHIRE_BOOST_FOLDER_NAME}/lib32-msvc-14.0) - else() - find_package(Boost ${SAPPHIRE_BOOST_VER} COMPONENTS system) - if(Boost_FOUND) - set(BOOST_LIBRARY_DIR ${Boost_LIBRARY_DIR}) - elseif ((EXISTS $ENV{BOOST_ROOT_DIR}) AND (EXISTS $ENV{BOOST_LIB_DIR})) - set(Boost_INCLUDE_DIR $ENV{BOOST_ROOT_DIR}) - set(BOOST_LIBRARYDIR $ENV{BOOST_LIB_DIR}) - else() - message(FATAL_ERROR "SapphireError: Unable to find boost ${SAPPHIRE_BOOST_VER} package and environment variables BOOST_ROOT_DIR and BOOST_LIB_DIR not set!") - endif() - endif() -endif() - -set(Boost_USE_STATIC_LIBS ON) - -find_package(Boost ${SAPPHIRE_BOOST_VER} COMPONENTS log log_setup thread date_time filesystem system) -include_directories(${Boost_INCLUDE_DIR}) -link_directories(${BOOST_LIBRARYDIR}) - diff --git a/cmake/compiler.cmake b/cmake/compiler.cmake index 20bab671..9ef1ee51 100644 --- a/cmake/compiler.cmake +++ b/cmake/compiler.cmake @@ -1,10 +1,11 @@ if(UNIX) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17") # set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -m32") else() add_definitions(-D_WIN32_WINNT=0x601) add_definitions(-D_CRT_SECURE_NO_WARNINGS) + add_definitions(-DNOMINMAX) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHc") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /bigobj") @@ -16,7 +17,7 @@ else() # edit and continue message(STATUS "Enabling Edit and Continue..") - add_definitions(/ZI) + add_definitions(/Zi) # incremental linking message(STATUS "Enabling Incremental Linking..") @@ -28,4 +29,5 @@ else() endif() endif() - +# force standalone asio +add_definitions( -DASIO_STANDALONE ) diff --git a/deps/MySQL/CMakeLists.txt b/deps/MySQL/CMakeLists.txt new file mode 100644 index 00000000..bed5a1e8 --- /dev/null +++ b/deps/MySQL/CMakeLists.txt @@ -0,0 +1,22 @@ +# Copyright (C) 2008-2018 TrinityCore +# +# This file is free software; as a special exception the author gives +# unlimited permission to copy and/or distribute it, with or without +# modifications, as long as this notice is preserved. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +if (NOT MYSQL_FOUND) + message(FATAL_ERROR "MySQL wasn't found on your system but it's required to build the servers!") +endif() + +add_library(mysql STATIC IMPORTED GLOBAL) + +set_target_properties(mysql + PROPERTIES + IMPORTED_LOCATION + "${MYSQL_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES +"${MYSQL_INCLUDE_DIR}") diff --git a/deps/asio b/deps/asio new file mode 160000 index 00000000..28d9b8d6 --- /dev/null +++ b/deps/asio @@ -0,0 +1 @@ +Subproject commit 28d9b8d6df708024af5227c551673fdb2519f5bf diff --git a/deps/datReader/CMakeLists.txt b/deps/datReader/CMakeLists.txt new file mode 100644 index 00000000..a8c7ec6c --- /dev/null +++ b/deps/datReader/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.0.2) +project(Sapphire) + +include_directories( "../" ) + +file( GLOB UTILS_PUBLIC_INCLUDE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/*" ) +file( GLOB UTILS_SOURCE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/src/*" ) + +set( CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ) + +add_library( xivdat ${UTILS_PUBLIC_INCLUDE_FILES} ${UTILS_SOURCE_FILES} ) + +set_target_properties( xivdat PROPERTIES + CXX_STANDARD 17 + CXX_STANDARD_REQUIRED ON + CXX_EXTENSIONS ON + ) + +if (UNIX) + target_link_libraries( xivdat PUBLIC dl ) + target_link_libraries( xivdat PUBLIC z ) +else() + target_link_libraries( xivdat PUBLIC zlib ) +endif() +target_include_directories( xivdat PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ) +#cotire( xivdat ) diff --git a/deps/datReader/Dat.cpp b/deps/datReader/Dat.cpp new file mode 100644 index 00000000..94812a09 --- /dev/null +++ b/deps/datReader/Dat.cpp @@ -0,0 +1,311 @@ +#include "Dat.h" + +#include "zlib.h" + +#include "File.h" + +namespace +{ + const uint32_t model_section_count = 0xB; +} + +namespace xiv +{ +namespace dat +{ + struct DatFileHeader + { + uint32_t size; + FileType entry_type; + uint32_t total_uncompressed_size; + uint32_t unknown[0x2]; + }; + + struct DatBlockRecord + { + uint32_t offset; + uint32_t size; + uint32_t unknown[0x4]; + SqPackBlockHash block_hash; + }; + + struct DatBlockHeader + { + uint32_t size; + uint32_t unknown1; + uint32_t compressed_size; + uint32_t uncompressed_size; + }; + + struct DatStdFileBlockInfos + { + uint32_t offset; + uint16_t size; + uint16_t uncompressed_size; + }; + + struct DatMdlFileBlockInfos + { + uint32_t unknown1; + uint32_t uncompressed_sizes[::model_section_count]; + uint32_t compressed_sizes[::model_section_count]; + uint32_t offsets[::model_section_count]; + uint16_t block_ids[::model_section_count]; + uint16_t block_counts[::model_section_count]; + uint32_t unknown2[0x2]; + }; + + struct DatTexFileBlockInfos + { + uint32_t offset; + uint32_t size; + uint32_t uncompressed_size; + uint32_t block_id; + uint32_t block_count; + }; + +} +} + +namespace xiv +{ +namespace utils +{ +namespace bparse +{ + template <> + inline void reorder(xiv::dat::DatFileHeader& i_struct) + { + xiv::utils::bparse::reorder(i_struct.size); + xiv::utils::bparse::reorder(i_struct.entry_type); + xiv::utils::bparse::reorder(i_struct.total_uncompressed_size); + for (int32_t i = 0; i < 0x2; ++i) { xiv::utils::bparse::reorder(i_struct.unknown[i]); } + } + + template <> + inline void reorder(xiv::dat::DatBlockRecord& i_struct) + { + xiv::utils::bparse::reorder(i_struct.offset); + xiv::utils::bparse::reorder(i_struct.size); + for (int32_t i = 0; i < 0x4; ++i) { xiv::utils::bparse::reorder(i_struct.unknown[i]); } + xiv::utils::bparse::reorder(i_struct.block_hash); + } + + template <> + inline void reorder(xiv::dat::DatBlockHeader& i_struct) + { + xiv::utils::bparse::reorder(i_struct.size); + xiv::utils::bparse::reorder(i_struct.unknown1); + xiv::utils::bparse::reorder(i_struct.compressed_size); + xiv::utils::bparse::reorder(i_struct.uncompressed_size); + } + + template <> + inline void reorder(xiv::dat::DatStdFileBlockInfos& i_struct) + { + xiv::utils::bparse::reorder(i_struct.offset); + xiv::utils::bparse::reorder(i_struct.size); + xiv::utils::bparse::reorder(i_struct.uncompressed_size); + } + + template <> + inline void reorder(xiv::dat::DatMdlFileBlockInfos& i_struct) + { + xiv::utils::bparse::reorder(i_struct.unknown1); + for (auto i = 0; i < ::model_section_count; ++i) { xiv::utils::bparse::reorder(i_struct.uncompressed_sizes[i]); } + for (auto i = 0; i < ::model_section_count; ++i) { xiv::utils::bparse::reorder(i_struct.compressed_sizes[i]); } + for (auto i = 0; i < ::model_section_count; ++i) { xiv::utils::bparse::reorder(i_struct.offsets[i]); } + for (auto i = 0; i < ::model_section_count; ++i) { xiv::utils::bparse::reorder(i_struct.block_ids[i]); } + for (auto i = 0; i < ::model_section_count; ++i) { xiv::utils::bparse::reorder(i_struct.block_counts[i]); } + for (auto i = 0; i < 0x2; ++i) { xiv::utils::bparse::reorder(i_struct.unknown2[i]); } + } + + template <> + inline void reorder(xiv::dat::DatTexFileBlockInfos& i_struct) + { + xiv::utils::bparse::reorder(i_struct.offset); + xiv::utils::bparse::reorder(i_struct.size); + xiv::utils::bparse::reorder(i_struct.uncompressed_size); + xiv::utils::bparse::reorder(i_struct.block_id); + xiv::utils::bparse::reorder(i_struct.block_count); + } +} +} +}; + +using xiv::utils::bparse::extract; + +namespace xiv +{ +namespace dat +{ + +Dat::Dat( const std::experimental::filesystem::path& i_path, uint32_t i_nb ) : + SqPack( i_path ), + m_num( i_nb ) +{ + auto block_record = extract(m_handle); + block_record.offset *= 0x80; + isBlockValid(block_record.offset, block_record.size, block_record.block_hash); +} + +Dat::~Dat() +{ +} + +std::unique_ptr Dat::getFile( uint32_t i_offset ) +{ + std::unique_ptr outputFile(new File()); + { + // Lock in this scope + std::lock_guard lock(m_fileMutex); + + // Seek to the start of the header of the file record and extract it + m_handle.seekg(i_offset); + auto file_header = extract(m_handle); + + switch (file_header.entry_type) + { + case FileType::empty: + throw std::runtime_error("File is empty"); + + case FileType::standard: + { + outputFile->_type = FileType::standard; + + uint32_t number_of_blocks = extract(m_handle, "number_of_blocks"); + + // Just extract offset infos for the blocks to extract + std::vector std_file_block_infos; + extract( m_handle, number_of_blocks, std_file_block_infos ); + + // Pre allocate data vector for the whole file + outputFile->_data_sections.resize(1); + auto& data_section = outputFile->_data_sections.front(); + + data_section.reserve(file_header.total_uncompressed_size); + // Extract each block + for (auto& file_block_info : std_file_block_infos) + { + extractBlock(i_offset + file_header.size + file_block_info.offset, data_section); + } + } + break; + + case FileType::model: + { + outputFile->_type = FileType::model; + + DatMdlFileBlockInfos mdlBlockInfo = extract(m_handle); + + // Getting the block number and read their sizes + const uint32_t block_count = mdlBlockInfo.block_ids[::model_section_count - 1] + + mdlBlockInfo.block_counts[::model_section_count - 1]; + std::vector block_sizes; + extract(m_handle, "block_size", block_count, block_sizes); + + // Preallocate sufficient space + outputFile->_data_sections.resize(::model_section_count); + + for (uint32_t i = 0; i < ::model_section_count; ++i) + { + // Preallocating for section + auto& data_section = outputFile->_data_sections[i]; + data_section.reserve(mdlBlockInfo.uncompressed_sizes[i]); + + uint32_t current_offset = i_offset + file_header.size + mdlBlockInfo.offsets[i]; + for (uint32_t j = 0; j < mdlBlockInfo.block_counts[i]; ++j) + { + extractBlock(current_offset, data_section); + current_offset += block_sizes[mdlBlockInfo.block_ids[i] + j]; + } + } + } + break; + + case FileType::texture: + { + outputFile->_type = FileType::texture; + + // Extracts mipmap entries and the block sizes + uint32_t sectionCount = extract(m_handle, "sections_count"); + + std::vector texBlockInfo; + extract(m_handle, sectionCount, texBlockInfo); + + // Extracting block sizes + uint32_t block_count = texBlockInfo.back().block_id + texBlockInfo.back().block_count; + std::vector block_sizes; + extract(m_handle, "block_size", block_count, block_sizes); + + outputFile->_data_sections.resize(sectionCount + 1); + + // Extracting header in section 0 + const uint32_t header_size = texBlockInfo.front().offset; + auto& header_section = outputFile->_data_sections[0]; + header_section.resize(header_size); + + m_handle.seekg(i_offset + file_header.size); + m_handle.read(header_section.data(), header_size); + + // Extracting other sections + for (uint32_t i = 0; i < sectionCount; ++i) + { + auto& data_section = outputFile->_data_sections[i + 1]; + auto& section_infos = texBlockInfo[i]; + data_section.reserve(section_infos.uncompressed_size); + + uint32_t current_offset = i_offset + file_header.size + section_infos.offset; + for (uint32_t j = 0; j < section_infos.block_count; ++j) + { + extractBlock(current_offset, data_section); + current_offset += block_sizes[section_infos.block_id + j]; + } + } + } + break; + + default: + throw std::runtime_error("Invalid entry_type: " + std::to_string(static_cast(file_header.entry_type))); + } + } + + return outputFile; +} + +void Dat::extractBlock( uint32_t i_offset, std::vector& o_data ) +{ + m_handle.seekg(i_offset); + + DatBlockHeader block_header = extract(m_handle); + + // Resizing the vector to write directly into it + const uint32_t data_size = o_data.size(); + o_data.resize(data_size + block_header.uncompressed_size); + + // 32000 in compressed_size means it is not compressed so take uncompressed_size + if (block_header.compressed_size == 32000) + { + m_handle.read(o_data.data() + data_size, block_header.uncompressed_size); + } + else + { + // If it is compressed use zlib + // Read the data to be decompressed + std::vector temp_buffer(block_header.compressed_size); + m_handle.read(temp_buffer.data(), block_header.compressed_size); + + utils::zlib::no_header_decompress(reinterpret_cast(temp_buffer.data()), + temp_buffer.size(), + reinterpret_cast(o_data.data() + data_size), + block_header.uncompressed_size); + } +} + +uint32_t Dat::getNum() const +{ + return m_num; +} + +} +} diff --git a/deps/datReader/Dat.h b/deps/datReader/Dat.h new file mode 100644 index 00000000..5311ab48 --- /dev/null +++ b/deps/datReader/Dat.h @@ -0,0 +1,45 @@ +#ifndef XIV_DAT_DAT_H +#define XIV_DAT_DAT_H + +#include "SqPack.h" + +#include + +#include + +namespace xiv +{ +namespace dat +{ + +class File; + +class Dat : public SqPack +{ +public: + // Full path to the dat file + Dat( const std::experimental::filesystem::path& i_path, uint32_t i_nb ); + virtual ~Dat(); + + // Retrieves a file given the offset in the dat file + std::unique_ptr getFile( uint32_t i_offset ); + + // Appends to the vector the data of this block, it is assumed to be preallocated + // Is it also assumed that the m_fileMutex is currently locked by this thread before the call + void extractBlock( uint32_t i_offset, std::vector& o_data ); + + // Returns the dat number + uint32_t getNum() const; + +protected: + // File reading mutex to have only one thread reading the file at a time + std::mutex m_fileMutex; + + // Dat nb + uint32_t m_num; +}; + +} +} + +#endif // XIV_DAT_DAT_H diff --git a/deps/datReader/DatCat.cpp b/deps/datReader/DatCat.cpp new file mode 100644 index 00000000..117c2b9b --- /dev/null +++ b/deps/datReader/DatCat.cpp @@ -0,0 +1,86 @@ +#include "DatCat.h" + +#include "Index.h" +#include "Dat.h" +#include "File.h" +#include "GameData.h" + +namespace xiv +{ +namespace dat +{ + +Cat::Cat( const std::experimental::filesystem::path& basePath, uint32_t catNum, const std::string& name ) : + m_name( name ), + m_catNum( catNum ), + m_chunk( -1 ) +{ + // From the category number, compute back the real filename for.index .datXs + std::stringstream ss; + ss << std::setw( 2 ) << std::setfill( '0' ) << std::hex << catNum; + std::string prefix = ss.str() + "0000.win32"; + + // Creates the index: XX0000.win32.index + m_index = std::unique_ptr( new Index( basePath / "//ffxiv" / ( prefix + ".index" ) ) ); + + // For all dat files linked to this index, create it: XX0000.win32.datX + for( uint32_t i = 0; i < getIndex().getDatCount(); ++i ) + { + m_dats.emplace_back( std::unique_ptr( new Dat( basePath / "//ffxiv" / ( prefix + ".dat" + std::to_string( i ) ), i ) ) ); + } +} + +Cat::Cat( const std::experimental::filesystem::path& basePath, uint32_t catNum, const std::string& name, uint32_t exNum, uint32_t chunk ) : + m_name( name ), + m_catNum( catNum ), + m_chunk( chunk ) +{ + // Creates the index: XX0000.win32.index + m_index = std::unique_ptr( new Index( basePath / GameData::buildDatStr( "ex" + std::to_string( exNum ), catNum, exNum, chunk, "win32", "index" ) ) ); + + // For all dat files linked to this index, create it: XX0000.win32.datX + for( uint32_t i = 0; i < getIndex().getDatCount(); ++i ) + { + m_dats.emplace_back( std::unique_ptr( new Dat( basePath / GameData::buildDatStr( "ex" + std::to_string( exNum ), catNum, exNum, chunk, "win32", "dat" + std::to_string( i ) ), i ) ) ); + } +} + +Cat::~Cat() +{ + +} + +const Index& Cat::getIndex() const +{ + return *m_index; +} + +std::unique_ptr Cat::getFile(uint32_t dir_hash, uint32_t filename_hash) const +{ + // Fetch the correct hash_table_entry for these hashes, from that request the file from the right dat file + auto& hash_table_entry = getIndex().getHashTableEntry(dir_hash, filename_hash); + return m_dats[hash_table_entry.datNum]->getFile(hash_table_entry.datOffset); +} + +bool Cat::doesFileExist( uint32_t dir_hash, uint32_t filename_hash ) const +{ + return getIndex().doesFileExist( dir_hash, filename_hash ); +} + +bool Cat::doesDirExist( uint32_t dir_hash ) const +{ + return getIndex().doesDirExist( dir_hash ); +} + +const std::string& Cat::getName() const +{ + return m_name; +} + +uint32_t Cat::getCatNum() const +{ + return m_catNum; +} + +} +} diff --git a/deps/datReader/DatCat.h b/deps/datReader/DatCat.h new file mode 100644 index 00000000..1e1d0759 --- /dev/null +++ b/deps/datReader/DatCat.h @@ -0,0 +1,64 @@ +#ifndef XIV_DAT_CAT_H +#define XIV_DAT_CAT_H + +#include +#include +#include + +namespace xiv { +namespace dat { + +class Index; +class Dat; +class File; + +// A category represents an .index and its associated .datX +class Cat +{ +public: + // basePath: Path to the folder containingthe datfiles + // catNum: The number of the category + // name: The name of the category, empty if not known + Cat( const std::experimental::filesystem::path& basePath, uint32_t catNum, const std::string& name ); + + // basePath: Path to the folder containingthe datfiles + // catNum: The number of the category + // name: The name of the category, empty if not known + // exNum: The number of the expansion to load from + // chunk: The chunk to load from + Cat( const std::experimental::filesystem::path& basePath, uint32_t catNum, const std::string& name, uint32_t exNum, uint32_t chunk ); + ~Cat(); + + // Returns .index of the category + const Index& getIndex() const; + + // Retrieve a file from the category given its hashes + std::unique_ptr getFile( uint32_t dir_hash, uint32_t filename_hash ) const; + + + bool doesFileExist( uint32_t dir_hash, uint32_t filename_hash ) const; + bool doesDirExist( uint32_t dir_hash ) const; + + + // Returns thename of the category + const std::string& getName() const; + + // Returns the number of the category + uint32_t getCatNum() const; + +protected: + const std::string m_name; + const uint32_t m_catNum; + const uint32_t m_chunk; + + // The .index + std::unique_ptr m_index; + + // The .datXs such as dat nb X => m_dats[X] + std::vector> m_dats; +}; + +} +} + +#endif // XIV_DAT_CAT_H diff --git a/deps/datReader/Exd.cpp b/deps/datReader/Exd.cpp new file mode 100644 index 00000000..b4513130 --- /dev/null +++ b/deps/datReader/Exd.cpp @@ -0,0 +1,278 @@ +#include "Exd.h" + +#include "bparse.h" +#include "stream.h" + +#include "Exh.h" + +using xiv::utils::bparse::extract; + + +namespace xiv +{ + namespace exd + { + + struct ExdHeader + { + char magic[0x4]; + uint16_t unknown; + uint16_t unknown2; + uint32_t index_size; + }; + + struct ExdRecordIndex + { + uint32_t id; + uint32_t offset; + }; + + } +} + +namespace xiv +{ + namespace utils + { + namespace bparse + { + template <> inline void reorder( xiv::exd::ExdHeader& i_struct ) { for( int32_t i = 0; i < 0x4; ++i ) { xiv::utils::bparse::reorder( i_struct.magic[i] ); } i_struct.unknown = xiv::utils::bparse::byteswap( i_struct.unknown ); xiv::utils::bparse::reorder( i_struct.unknown ); i_struct.unknown2 = xiv::utils::bparse::byteswap( i_struct.unknown2 ); xiv::utils::bparse::reorder( i_struct.unknown2 ); i_struct.index_size = xiv::utils::bparse::byteswap( i_struct.index_size ); xiv::utils::bparse::reorder( i_struct.index_size ); } + template <> inline void reorder( xiv::exd::ExdRecordIndex& i_struct ) { i_struct.id = xiv::utils::bparse::byteswap( i_struct.id ); xiv::utils::bparse::reorder( i_struct.id ); i_struct.offset = xiv::utils::bparse::byteswap( i_struct.offset ); xiv::utils::bparse::reorder( i_struct.offset ); } + } + } +}; + +namespace xiv +{ + namespace exd + { + + Exd::Exd( std::shared_ptr i_exh, const std::vector>& i_files ) + { + _exh = i_exh; + _files = i_files; + + + // Iterates over all the files + const uint32_t member_count = _exh->get_members().size(); + for ( auto &file_ptr : _files ) + { + // Get a stream + std::vector< char > dataCpy = file_ptr->get_data_sections().front(); + std::istringstream iss( std::string( dataCpy.begin(), dataCpy.end() ) ); + + // Extract the header and skip to the record indices + auto exd_header = extract< ExdHeader >( iss ); + iss.seekg( 0x20 ); + + // Preallocate and extract the record_indices + const uint32_t record_count = exd_header.index_size / sizeof( ExdRecordIndex ); + std::vector< ExdRecordIndex > record_indices; + record_indices.reserve( record_count ); + for ( uint32_t i = 0; i < record_count; ++i ) + { + auto recordIndex = extract< ExdRecordIndex >( iss ); + _idCache[recordIndex.id] = ExdCacheEntry{file_ptr, recordIndex.offset}; + } + } + } + + Exd::~Exd() + { + } + + const std::vector Exd::get_row( uint32_t id ) + { + + auto cacheEntryIt = _idCache.find( id ); + if( cacheEntryIt == _idCache.end() ) + throw std::runtime_error( "Id not found: " + std::to_string( id ) ); + + // Iterates over all the files + const uint32_t member_count = _exh->get_members().size(); + auto& file_ptr = cacheEntryIt->second.file; + + std::vector< char > dataCpy = file_ptr->get_data_sections().front(); + std::istringstream iss( std::string( dataCpy.begin(), dataCpy.end() ) ); + + // Get the vector fields for the given record and preallocate it + auto fields = _data[id]; + fields.reserve( member_count ); + + for( auto& member_entry : _exh->get_exh_members() ) + { + // Seek to the position of the member to extract. + // 6 is because we have uint32_t/uint16_t at the start of each record + iss.seekg( cacheEntryIt->second.offset + 6 + member_entry.offset ); + + // Switch depending on the type to extract + switch( member_entry.type ) + { + case DataType::string: + // Extract the offset to the actual string + // Seek to it then extract the actual string + { + auto string_offset = extract( iss, "string_offset", false ); + iss.seekg( cacheEntryIt->second.offset + 6 + _exh->get_header().data_offset + string_offset ); + fields.emplace_back( utils::bparse::extract_cstring( iss, "string" ) ); + } + break; + + case DataType::boolean: + fields.emplace_back( extract( iss, "bool" ) ); + break; + + case DataType::int8: + fields.emplace_back( extract( iss, "int8_t" ) ); + break; + + case DataType::uint8: + fields.emplace_back( extract( iss, "uint8_t" ) ); + break; + + case DataType::int16: + fields.emplace_back( extract( iss, "int16_t", false ) ); + break; + + case DataType::uint16: + fields.emplace_back( extract( iss, "uint16_t", false ) ); + break; + + case DataType::int32: + fields.emplace_back( extract( iss, "int32_t", false ) ); + break; + + case DataType::uint32: + fields.emplace_back( extract( iss, "uint32_t", false ) ); + break; + + case DataType::float32: + fields.emplace_back( extract( iss, "float", false ) ); + break; + + case DataType::uint64: + fields.emplace_back( extract( iss, "uint64_t", false ) ); + break; + + default: + auto type = static_cast< uint16_t >( member_entry.type ); + if( type < 0x19 || type > 0x20 ) + throw std::runtime_error("Unknown DataType: " + std::to_string( type )); + uint64_t val = extract< uint64_t >( iss, "bool" ); + int32_t shift = type - 0x19; + int32_t i = 1 << shift; + val &= i; + fields.emplace_back( ( val & i ) == i ); + break; + } + } + return fields; + + } + + // Get all rows + const std::map>& Exd::get_rows() + { + // Iterates over all the files + const uint32_t member_count = _exh->get_members().size(); + for( auto& file_ptr : _files ) + { + // Get a stream + std::vector< char > dataCpy = file_ptr->get_data_sections().front(); + std::istringstream iss( std::string( dataCpy.begin(), dataCpy.end() ) ); + + // Extract the header and skip to the record indices + auto exd_header = extract( iss ); + iss.seekg( 0x20 ); + + // Preallocate and extract the record_indices + const uint32_t record_count = exd_header.index_size / sizeof( ExdRecordIndex ); + std::vector record_indices; + record_indices.reserve( record_count ); + for( uint32_t i = 0; i < record_count; ++i ) + { + record_indices.emplace_back( extract( iss ) ); + } + + for( auto& record_index : record_indices ) + { + // Get the vector fields for the given record and preallocate it + auto& fields = _data[record_index.id]; + fields.reserve( member_count ); + + for( auto& member_entry : _exh->get_exh_members() ) + //for( auto& member_entry : _exh->get_members() ) + { + // Seek to the position of the member to extract. + // 6 is because we have uint32_t/uint16_t at the start of each record + iss.seekg( record_index.offset + 6 + member_entry.offset ); + + // Switch depending on the type to extract + switch( member_entry.type ) + { + case DataType::string: + // Extract the offset to the actual string + // Seek to it then extract the actual string + { + auto string_offset = extract( iss, "string_offset", false ); + iss.seekg( record_index.offset + 6 + _exh->get_header().data_offset + string_offset ); + fields.emplace_back( utils::bparse::extract_cstring( iss, "string" ) ); + } + break; + + case DataType::boolean: + fields.emplace_back( extract( iss, "bool" ) ); + break; + + case DataType::int8: + fields.emplace_back( extract( iss, "int8_t" ) ); + break; + + case DataType::uint8: + fields.emplace_back( extract( iss, "uint8_t" ) ); + break; + + case DataType::int16: + fields.emplace_back( extract( iss, "int16_t", false ) ); + break; + + case DataType::uint16: + fields.emplace_back( extract( iss, "uint16_t", false ) ); + break; + + case DataType::int32: + fields.emplace_back( extract( iss, "int32_t", false ) ); + break; + + case DataType::uint32: + fields.emplace_back( extract( iss, "uint32_t", false ) ); + break; + + case DataType::float32: + fields.emplace_back( extract( iss, "float", false ) ); + break; + + case DataType::uint64: + fields.emplace_back( extract( iss, "uint64_t", false ) ); + break; + + default: + auto type = static_cast< uint16_t >( member_entry.type ); + if( type < 0x19 || type > 0x20 ) + throw std::runtime_error("Unknown DataType: " + std::to_string( type )); + uint64_t val = extract< uint64_t >( iss, "bool" ); + int32_t shift = type - 0x19; + int32_t i = 1 << shift; + val &= i; + fields.emplace_back( ( val & i ) == i ); + break; + } + } + } + } + return _data; + } + + } +} + diff --git a/deps/datReader/Exd.h b/deps/datReader/Exd.h new file mode 100644 index 00000000..0bafa5e4 --- /dev/null +++ b/deps/datReader/Exd.h @@ -0,0 +1,64 @@ +#ifndef XIV_EXD_EXD_H +#define XIV_EXD_EXD_H + +#include +#include + +#include + +#include "File.h" + +namespace xiv +{ +namespace exd +{ + +class Exh; + +// Field type containing all the possible types in the data files +using Field = std::variant< + std::string, + bool, + int8_t, + uint8_t, + int16_t, + uint16_t, + int32_t, + uint32_t, + float, + uint64_t >; + +struct ExdCacheEntry +{ + std::shared_ptr file; + uint32_t offset; +}; + +// Data for a given language +class Exd +{ +public: + // i_exh: the header + // i_files: the multiple exd files + Exd() {} + Exd(std::shared_ptr i_exh, const std::vector>& i_files); + ~Exd(); + + // Get a row by its id + const std::vector get_row(uint32_t id); + + // Get all rows + const std::map>& get_rows(); + +protected: + // Data indexed by the ID of the row, the vector is field with the same order as exh.members + std::map> _data; + std::vector> _files; + std::shared_ptr _exh; + std::map< uint32_t, ExdCacheEntry > _idCache; +}; + +} +} + +#endif // XIV_EXD_EXD_H diff --git a/deps/datReader/ExdCat.cpp b/deps/datReader/ExdCat.cpp new file mode 100644 index 00000000..ddab260e --- /dev/null +++ b/deps/datReader/ExdCat.cpp @@ -0,0 +1,82 @@ +#include "ExdCat.h" + +#include + +#include "GameData.h" + +#include "Exh.h" +#include "Exd.h" + +namespace +{ + // Suffix of the filenames given a language + std::map language_map = + {{xiv::exd::Language::none, ""}, + {xiv::exd::Language::ja, "_ja"}, + {xiv::exd::Language::en, "_en"}, + {xiv::exd::Language::de, "_de"}, + {xiv::exd::Language::fr, "_fr"}, + {xiv::exd::Language::chs, "_chs"}}; +} + +namespace xiv +{ + namespace exd + { + + Cat::Cat(dat::GameData& i_game_data, const std::string& i_name) : + _name(i_name) + { + //XIV_INFO(xiv_exd_logger, "Initializing Cat with name: " << i_name); + // creates the header .exh + { + auto header_file = i_game_data.getFile("exd/" + i_name + ".exh"); + _header = std::shared_ptr< Exh >( new Exh( *header_file ) ); + + } + + for(auto language: _header->get_languages()) + { + // chs not yet in data files + if (language == Language::en || language == Language::none) + { + // Get all the files for a given category/language, in case of multiple range of IDs in separate files (like Quest) + std::vector> files; + for(auto& exd_def: _header->get_exd_defs()) + { + files.emplace_back( i_game_data.getFile("exd/" + i_name + "_" + std::to_string(exd_def.start_id) + language_map.at(language) + ".exd") ); + } + // Instantiate the data for this language + _data[language] = std::unique_ptr(new Exd(_header, files)); + } + } + } + + Cat::~Cat() + { + + } + + const std::string& Cat::get_name() const + { + return _name; + } + + const Exh& Cat::get_header() const + { + return *_header; + } + + const Exd& Cat::get_data_ln(Language i_language) const + { + auto ln_it = _data.find(i_language); + if (ln_it == _data.end()) + { + throw std::runtime_error("No data for language: " + std::to_string(uint16_t(i_language))); + } + + return *(ln_it->second); + } + + } +} diff --git a/deps/datReader/ExdCat.h b/deps/datReader/ExdCat.h new file mode 100644 index 00000000..af1090d8 --- /dev/null +++ b/deps/datReader/ExdCat.h @@ -0,0 +1,66 @@ +#ifndef XIV_EXD_CAT_H +#define XIV_EXD_CAT_H + +#include +#include + +#include + +#include "bparse.h" +#include "Exd.h" + + +namespace xiv +{ + namespace dat + { + class GameData; + } + namespace exd + { + + // Language in the exd files - note: chs/chinese is present in the languages array but not in the data files + enum Language : uint16_t + { + none = 0, + ja = 1, + en = 2, + de = 3, + fr = 4, + chs = 5, + }; + + class Exh; + class Exd; + + // A category repesent a several data sheets in the dats all under the same category + class Cat + { + public: + // i_name: name of the category + // i_game_data: used to fetch the files needed + Cat( dat::GameData& i_game_data, const std::string& i_name ); + ~Cat(); + + // Returns the name of the category + const std::string& get_name() const; + + // Returns the header + const Exh& get_header() const; + + // Returns data for a specific language + const Exd& get_data_ln( Language i_language = Language::none ) const; + protected: + const std::string _name; + + // The header file of the category *.exh + std::shared_ptr _header; + // The data files of the category, indexed by language *.exd + // Note that if we have multiple files for different range of IDs, they are merged here + std::map> _data; + }; + + } +} + +#endif // XIV_EXD_CAT_H diff --git a/deps/datReader/ExdData.cpp b/deps/datReader/ExdData.cpp new file mode 100644 index 00000000..3c4af47a --- /dev/null +++ b/deps/datReader/ExdData.cpp @@ -0,0 +1,100 @@ +#include "ExdData.h" + +#include "stream.h" + +#include "GameData.h" +#include "File.h" + +#include "ExdCat.h" + +namespace xiv +{ +namespace exd +{ + +ExdData::ExdData(dat::GameData& i_game_data) try : + _game_data(i_game_data) +{ + //XIV_INFO(xiv_exd_logger, "Initializing ExdData"); + + // Fetch the root.exl and get a stream from it + auto root_exl = i_game_data.getFile("exd/root.exl"); + std::vector< char > dataCpy = root_exl->get_data_sections().front(); + xiv::utils::stream::vectorwrapbuf databuf(dataCpy); + std::istream stream(&databuf); + + // Iterates over the lines while skipping the first one + std::string line; + std::getline(stream, line); // extract first line EXLT,2 + std::getline(stream, line); + + // Until the EOF + while (!line.empty()) + { + // Format is cat_name,XX + // XX being an internal identifier + // Get only the cat_name + auto sep = line.find(','); + auto category = line.substr(0, sep); + + // Add to the list of category name + // creates the empty category in the cats map + // instantiate the creation mutex for this category + _cat_names.push_back(category); + _cats[category] = std::unique_ptr(); + _cat_creation_mutexes[category] = std::unique_ptr(new std::mutex()); + + std::getline(stream, line); + } +} +catch(std::exception& e) +{ + // In case of failure here, client is supposed to catch the exception because it is not recoverable on our side + throw std::runtime_error( "ExdData initialization failed: " + std::string( e.what() ) ); +} + +ExdData::~ExdData() +{ + +} + +const std::vector& ExdData::get_cat_names() const +{ + return _cat_names; +} + +const Cat& ExdData::get_category(const std::string& i_cat_name) +{ + // Get the category from its name + auto cat_it = _cats.find(i_cat_name); + if (cat_it == _cats.end()) + { + throw std::runtime_error("Category not found: " + i_cat_name); + } + + if (cat_it->second) + { + // If valid return it + return *(cat_it->second); + } + else + { + // If not, create it and return it + create_category(i_cat_name); + return *(_cats[i_cat_name]); + } +} + +void ExdData::create_category(const std::string& i_cat_name) +{ + // Lock mutex in this scope + std::lock_guard lock(*(_cat_creation_mutexes[i_cat_name])); + // Maybe after unlocking it has already been created, so check (most likely if it blocked) + if (!_cats[i_cat_name]) + { + _cats[i_cat_name] = std::unique_ptr(new Cat(_game_data, i_cat_name)); + } +} + +} +} diff --git a/deps/datReader/ExdData.h b/deps/datReader/ExdData.h new file mode 100644 index 00000000..c6ca6170 --- /dev/null +++ b/deps/datReader/ExdData.h @@ -0,0 +1,57 @@ +#ifndef XIV_EXD_EXDDATA_H +#define XIV_EXD_EXDDATA_H + +#include +#include +#include + +#include + +namespace xiv +{ + namespace dat + { + class GameData; + } + namespace exd + { + + class Cat; + + // Interface for retrieval of exd data - Main entry point + // the game_data object should outlive the exd_data object + class ExdData + { + public: + // Need an initialized dat::GameData to retrieve the files from the dat + ExdData(dat::GameData& i_game_data); + ~ExdData(); + + // Get the list of thenames of the categories + const std::vector& get_cat_names() const; + + // Get a category by its name + const Cat& get_category(const std::string& i_cat_name); + + // Export in csv in base flder i_ouput_path + void export_as_csvs(const std::experimental::filesystem::path& i_output_path); + + protected: + // Lazy instantiation of category + void create_category(const std::string& i_cat_name); + + // Reference to the game_data object + dat::GameData& _game_data; + + // Categories, indexed by their name + std::unordered_map> _cats; + // List of category names = m_cats.keys() + std::vector _cat_names; + // Mutexes used to avoid race condition when lazy instantiating a category + std::unordered_map> _cat_creation_mutexes; + }; + + } +} + +#endif // XIV_EXD_EXDDATA_H diff --git a/deps/datReader/Exh.cpp b/deps/datReader/Exh.cpp new file mode 100644 index 00000000..0a829bec --- /dev/null +++ b/deps/datReader/Exh.cpp @@ -0,0 +1,77 @@ +#include "Exh.h" + +#include "stream.h" + +#include "File.h" +#include + +using xiv::utils::bparse::extract; + +namespace xiv +{ +namespace exd +{ +Exh::Exh(const dat::File& i_file) +{ + // Get a stream from the file + std::vector< char > dataCpy = i_file.get_data_sections().front(); + std::istringstream iss( std::string( dataCpy.begin(), dataCpy.end() ) ); + + // Extract header and skip to member definitions + _header = extract(iss); + iss.seekg(0x20); + + // Extract all the members and feed the _members map + for (auto i = 0; i < _header.field_count; ++i) + { + auto member = extract(iss); + _members[member.offset] = member; + _exh_defs.push_back( member ); + } + + // Extract all the exd_defs + _exd_defs.reserve(_header.exd_count); + for (auto i = 0; i < _header.exd_count; ++i) + { + _exd_defs.emplace_back(extract(iss)); + } + + // Extract all the languages + _languages.reserve(_header.language_count); + for (auto i = 0; i < _header.language_count; ++i) + { + _languages.emplace_back(Language(extract(iss, "language"))); + } +} + +Exh::~Exh() +{ +} + +const ExhHeader& Exh::get_header() const +{ + return _header; +} + +const std::vector& Exh::get_exd_defs() const +{ + return _exd_defs; +} + +const std::vector& Exh::get_languages() const +{ + return _languages; +} + +const std::map& Exh::get_members() const +{ + return _members; +} + +const std::vector& Exh::get_exh_members() const +{ + return _exh_defs; +} + +} +} diff --git a/deps/datReader/Exh.h b/deps/datReader/Exh.h new file mode 100644 index 00000000..2a7cf630 --- /dev/null +++ b/deps/datReader/Exh.h @@ -0,0 +1,100 @@ +#ifndef XIV_EXD_EXH_H +#define XIV_EXD_EXH_H + +#include + +#include "bparse.h" + +namespace xiv +{ + namespace exd + { + enum class DataType : uint16_t + { + string = 0, + boolean = 1, + int8 = 2, + uint8 = 3, + int16 = 4, + uint16 = 5, + int32 = 6, + uint32 = 7, + float32 = 9, + uint64 = 11, + }; + + struct ExhHeader + { + char magic[0x4]; + uint16_t unknown; + uint16_t data_offset; + uint16_t field_count; + uint16_t exd_count; + uint16_t language_count; + }; + + struct ExhMember + { + DataType type; + uint16_t offset; + }; + + struct ExhExdDef + { + uint32_t start_id; + uint32_t count_id; + }; + } +}; + +namespace xiv +{ + namespace utils + { + namespace bparse + { + template <> inline void reorder( xiv::exd::ExhHeader& i_struct ) { for( int32_t i = 0; i < 0x4; ++i ) { xiv::utils::bparse::reorder( i_struct.magic[i] ); } i_struct.unknown = xiv::utils::bparse::byteswap( i_struct.unknown ); xiv::utils::bparse::reorder( i_struct.unknown ); i_struct.data_offset = xiv::utils::bparse::byteswap( i_struct.data_offset ); xiv::utils::bparse::reorder( i_struct.data_offset ); i_struct.field_count = xiv::utils::bparse::byteswap( i_struct.field_count ); xiv::utils::bparse::reorder( i_struct.field_count ); i_struct.exd_count = xiv::utils::bparse::byteswap( i_struct.exd_count ); xiv::utils::bparse::reorder( i_struct.exd_count ); i_struct.language_count = xiv::utils::bparse::byteswap( i_struct.language_count ); xiv::utils::bparse::reorder( i_struct.language_count ); } + template <> inline void reorder( xiv::exd::ExhMember& i_struct ) { i_struct.type = xiv::utils::bparse::byteswap( i_struct.type ); xiv::utils::bparse::reorder( i_struct.type ); i_struct.offset = xiv::utils::bparse::byteswap( i_struct.offset ); xiv::utils::bparse::reorder( i_struct.offset ); } + template <> inline void reorder( xiv::exd::ExhExdDef& i_struct ) { i_struct.start_id = xiv::utils::bparse::byteswap( i_struct.start_id ); xiv::utils::bparse::reorder( i_struct.start_id ); i_struct.count_id = xiv::utils::bparse::byteswap( i_struct.count_id ); xiv::utils::bparse::reorder( i_struct.count_id ); } + } + } +}; + +namespace xiv +{ + namespace dat + { + class File; + } + namespace exd + { + + enum Language : uint16_t; + + // Header file for exd data + class Exh + { + public: + // The header file + Exh( const dat::File& i_file ); + ~Exh(); + + const ExhHeader& get_header() const; + const std::vector& get_exd_defs() const; + const std::vector& get_languages() const; + const std::map& get_members() const; + const std::vector& get_exh_members() const; + + protected: + ExhHeader _header; + // Members of the datastruct ordered(indexed) by offset + std::map _members; + std::vector _exh_defs; + std::vector _exd_defs; + std::vector _languages; + }; + + } +} + +#endif // XIV_EXD_EXH_H diff --git a/deps/datReader/File.cpp b/deps/datReader/File.cpp new file mode 100644 index 00000000..9559159d --- /dev/null +++ b/deps/datReader/File.cpp @@ -0,0 +1,45 @@ +#include "File.h" + +#include + +namespace xiv +{ +namespace dat +{ + +File::File() : + _type(FileType::empty) +{ +} + +File::~File() +{ +} + +FileType File::get_type() const +{ + return _type; +} + +const std::vector>& File::get_data_sections() const +{ + return _data_sections; +} + +std::vector>& File::access_data_sections() +{ + return _data_sections; +} + +void File::exportToFile(const std::experimental::filesystem::path& i_path) const +{ + std::ofstream ofs( i_path.string(), std::ios_base::binary | std::ios_base::out ); + for( auto& data_section : _data_sections ) + { + ofs.write( data_section.data(), data_section.size() ); + } + ofs.close(); +} + +} +} diff --git a/deps/datReader/File.h b/deps/datReader/File.h new file mode 100644 index 00000000..1d09e78e --- /dev/null +++ b/deps/datReader/File.h @@ -0,0 +1,56 @@ +#ifndef XIV_DAT_FILE_H +#define XIV_DAT_FILE_H + +#include + +#include +#include +#include "bparse.h" + + +namespace xiv +{ + namespace dat + { + enum class FileType : uint32_t + { + empty = 1, + standard = 2, + model = 3, + texture = 4, + }; + } +}; + +namespace xiv +{ + namespace dat + { + + class Dat; + + // Basic file from the dats + class File + { + friend class Dat; + public: + File(); + ~File(); + + FileType get_type() const; + + // Getters functions for the data in the file + const std::vector>& get_data_sections() const; + std::vector>& access_data_sections(); + + void exportToFile( const std::experimental::filesystem::path& i_path ) const; + + protected: + FileType _type; + std::vector> _data_sections; + }; + + } +} + +#endif // XIV_DAT_FILE_H diff --git a/deps/datReader/GameData.cpp b/deps/datReader/GameData.cpp new file mode 100644 index 00000000..9d19644a --- /dev/null +++ b/deps/datReader/GameData.cpp @@ -0,0 +1,315 @@ +#include "GameData.h" + +#include +#include +#include + +#include +#include + +#include "bparse.h" +#include "DatCat.h" +#include "File.h" + +namespace +{ + // Relation between category number and category name + // These names are taken straight from the exe, it helps resolve dispatching when getting files by path + + std::unordered_map< std::string, uint32_t > categoryNameToIdMap = + {{"common", 0x00}, + {"bgcommon", 0x01}, + {"bg", 0x02}, + {"cut", 0x03}, + {"chara", 0x04}, + {"shader", 0x05}, + {"ui", 0x06}, + {"sound", 0x07}, + {"vfx", 0x08}, + {"ui_script", 0x09}, + {"exd", 0x0A}, + {"game_script", 0x0B}, + {"music", 0x0C} + }; + + std::unordered_map< uint32_t, std::string > categoryIdToNameMap = + {{0x00, "common"}, + {0x01, "bgcommon"}, + {0x02, "bg"}, + {0x03, "cut"}, + {0x04, "chara"}, + {0x05, "shader"}, + {0x06, "ui"}, + {0x07, "sound"}, + {0x08, "vfx"}, + {0x09, "ui_script"}, + {0x0A, "exd"}, + {0x0B, "game_script"}, + {0x0C, "music"}}; +} + +namespace xiv +{ +namespace dat +{ + +GameData::GameData(const std::experimental::filesystem::path& path) try : + m_path(path) +{ + int maxExLevel = 0; + + // Determine which expansions are available + while( std::experimental::filesystem::exists( std::experimental::filesystem::path( m_path.string() + "\\ex" + std::to_string( maxExLevel + 1) + "\\ex" + std::to_string( maxExLevel + 1) + ".ver" ) ) ) + { + maxExLevel++; + } + + + // Iterate over the files in path + for( auto it = std::experimental::filesystem::directory_iterator( m_path.string() + "//ffxiv" ); it != std::experimental::filesystem::directory_iterator(); ++it ) + { + // Get the filename of the current element + auto filename = it->path().filename().string(); + + // If it contains ".win32.index" this is most likely a hit for a category + if( filename.find( ".win32.index" ) != std::string::npos && filename.find( ".win32.index2" ) == std::string::npos ) + { + // Format of indexes is XX0000.win32.index, so fetch the hex number for category number + std::istringstream iss( filename.substr( 0, 2 ) ); + uint32_t cat_nb; + iss >> std::hex >> cat_nb; + + + // Add to the list of category number + // creates the empty category in the cats map + // instantiate the creation mutex for this category + m_catNums.push_back( cat_nb ); + m_cats[cat_nb] = std::unique_ptr(); + m_catCreationMutexes[cat_nb] = std::unique_ptr( new std::mutex() ); + + // Check for expansion + for( int exNum = 1; exNum <= maxExLevel; exNum++ ) + { + const std::string path = m_path.string() + "\\" + buildDatStr( "ex" + std::to_string( exNum ), cat_nb, exNum, 0, "win32", "index" ); + + if( std::experimental::filesystem::exists( std::experimental::filesystem::path( path ) ) ) + { + + int chunkCount = 0; + + for(int chunkTest = 0; chunkTest < 256; chunkTest++ ) + { + if( std::experimental::filesystem::exists( m_path.string() + "\\" + buildDatStr( "ex" + std::to_string( exNum ), cat_nb, exNum, chunkTest, "win32", "index" ) ) ) + { + m_exCats[cat_nb].exNumToChunkMap[exNum].chunkToCatMap[chunkTest] = std::unique_ptr(); + chunkCount++; + } + } + + } + } + } + } + +} +catch( std::exception& e ) +{ + // In case of failure here, client is supposed to catch the exception because it is not recoverable on our side + throw std::runtime_error( "GameData initialization failed: " + std::string( e.what() ) ); +} + +GameData::~GameData() +{ + +} + +const std::string GameData::buildDatStr( const std::string folder, const int cat, const int exNum, const int chunk, const std::string platform, const std::string type ) +{ + char dat[1024]; + sprintf( dat, "%s/%02x%02x%02x.%s.%s", folder.c_str(), cat, exNum, chunk, platform.c_str(), type.c_str() ); + return std::string( dat ); +} + +const std::vector& GameData::getCatNumbers() const +{ + return m_catNums; +} + +std::unique_ptr GameData::getFile(const std::string& path) +{ + // Get the hashes, the category from the path then call the getFile of the category + uint32_t dirHash; + uint32_t filenameHash; + getHashes( path, dirHash, filenameHash ); + + return getCategoryFromPath( path ).getFile( dirHash, filenameHash ); +} + +bool GameData::doesFileExist(const std::string& path) +{ + uint32_t dirHash; + uint32_t filenameHash; + getHashes( path, dirHash, filenameHash ); + + return getCategoryFromPath( path ).doesFileExist( dirHash, filenameHash ); +} + +bool GameData::doesDirExist(const std::string& i_path) +{ + uint32_t dirHash; + uint32_t filenameHash; + getHashes( i_path, dirHash, filenameHash ); + + return getCategoryFromPath( i_path ).doesDirExist( dirHash ); +} + +const Cat& GameData::getCategory(uint32_t catNum) +{ + // Check that the category number exists + auto catIt = m_cats.find( catNum ); + if( catIt == m_cats.end() ) + { + throw std::runtime_error( "Category not found: " + std::to_string( catNum ) ); + } + + // If it exists and already instantiated return it + if( catIt->second ) + { + return *( catIt->second ); + } + else + { + // Else create it and return it + createCategory( catNum ); + return *( m_cats[catNum] ); + } +} + +const Cat& GameData::getCategory(const std::string& catName) +{ + // Find the category number from the name + auto categoryNameToIdMapIt = ::categoryNameToIdMap.find( catName ); + if( categoryNameToIdMapIt == ::categoryNameToIdMap.end() ) + { + throw std::runtime_error( "Category not found: " + catName ); + } + + // From the category number return the category + return getCategory( categoryNameToIdMapIt->second ); +} + +const Cat& GameData::getExCategory( const std::string& catName, uint32_t exNum, const std::string& path ) +{ + // Find the category number from the name + auto categoryMapIt = ::categoryNameToIdMap.find( catName ); + if( categoryMapIt == ::categoryNameToIdMap.end() ) + { + throw std::runtime_error( "Category not found: " + catName ); + } + + uint32_t dirHash; + uint32_t filenameHash; + getHashes( path, dirHash, filenameHash ); + + for( auto const& chunk : m_exCats[categoryMapIt->second].exNumToChunkMap[exNum].chunkToCatMap ) + { + if( !chunk.second ) + createExCategory( categoryMapIt->second ); + + if( chunk.second->doesFileExist( dirHash, filenameHash ) ) + { + return *( chunk.second ); + } + } + + throw std::runtime_error( "Chunk not found for path: " + path ); +} + +const Cat& GameData::getCategoryFromPath(const std::string& path) +{ + // Find the first / in the string, paths are in the format CAT_NAME/..../.../../.... + auto firstSlashPos = path.find( '/' ); + if( firstSlashPos == std::string::npos ) + { + throw std::runtime_error( "Path does not have a / char: " + path ); + } + + if( path.substr( firstSlashPos + 1, 2) == "ex" ) + { + return getExCategory( path.substr( 0, firstSlashPos ), std::stoi( path.substr( firstSlashPos + 3, 1 ) ), path ); + } + else + { + // From the sub string found beforethe first / get the category + return getCategory( path.substr( 0, firstSlashPos ) ); + } +} + +void GameData::getHashes(const std::string& path, uint32_t& dirHash, uint32_t& filenameHash) const +{ + // Convert the path to lowercase before getting the hashes + std::string pathLower; + pathLower.resize( path.size() ); + std::transform( path.begin(), path.end(), pathLower.begin(), ::tolower ); + + // Find last / to separate dir from filename + auto lastSlashPos = pathLower.rfind( '/' ); + if( lastSlashPos == std::string::npos ) + { + throw std::runtime_error( "Path does not have a / char: " + path ); + } + + std::string dirPart = pathLower.substr( 0, lastSlashPos ); + std::string filenamePart = pathLower.substr( lastSlashPos + 1 ); + + // Get the crc32 values from zlib, to compensate the final XOR 0xFFFFFFFF that isnot done in the exe we just reXOR + dirHash = crc32( 0, reinterpret_cast( dirPart.data() ), dirPart.size() ) ^ 0xFFFFFFFF; + filenameHash = crc32( 0, reinterpret_cast( filenamePart.data() ), filenamePart.size() ) ^ 0xFFFFFFFF; +} + +void GameData::createCategory(uint32_t catNum) +{ + // Lock mutex in this scope + std::lock_guard lock( *( m_catCreationMutexes[catNum] ) ); + // Maybe after unlocking it has already been created, so check (most likely if it blocked) + if( !m_cats[catNum] ) + { + // Get the category name if we have it + std::string catName; + auto categoryMapIt = ::categoryIdToNameMap.find( catNum ); + if( categoryMapIt != ::categoryIdToNameMap.end() ) + { + catName = categoryMapIt->second; + } + + // Actually creates the category + m_cats[catNum] = std::unique_ptr( new Cat( m_path, catNum, catName ) ); + } +} + +void GameData::createExCategory( uint32_t catNum ) +{ + // Maybe after unlocking it has already been created, so check (most likely if it blocked) + if( !m_exCats[catNum].exNumToChunkMap[1].chunkToCatMap[0] ) + { + // Get the category name if we have it + std::string catName; + auto categoryMapIt = ::categoryIdToNameMap.find( catNum ); + if( categoryMapIt != ::categoryIdToNameMap.end() ) + { + catName = categoryMapIt->second; + } + + for( auto const& ex : m_exCats[catNum].exNumToChunkMap ) + { + for( auto const& chunk : m_exCats[catNum].exNumToChunkMap[ex.first].chunkToCatMap ) + { + // Actually creates the category + m_exCats[catNum].exNumToChunkMap[ex.first].chunkToCatMap[chunk.first] = std::unique_ptr( new Cat( m_path, catNum, catName, ex.first, chunk.first ) ); + } + } + } +} + +} +} diff --git a/deps/datReader/GameData.h b/deps/datReader/GameData.h new file mode 100644 index 00000000..2b64fafd --- /dev/null +++ b/deps/datReader/GameData.h @@ -0,0 +1,84 @@ +#ifndef XIV_DAT_GAMEDATA_H +#define XIV_DAT_GAMEDATA_H + +#include +#include +#include + +#include + +namespace xiv +{ +namespace dat +{ + +class Cat; +class File; + +// Interface to all the datfiles - Main entry point +// All the paths to files/dirs inside the dats are case-insensitive +class GameData +{ +public: + // This should be the path in which the .index/.datX files are located + GameData( const std::experimental::filesystem::path& path ); + ~GameData(); + + static const std::string buildDatStr( const std::string folder, const int cat, const int exNum, const int chunk, const std::string platform, const std::string type ); + + // Returns all the scanned category number available in the path + const std::vector& getCatNumbers() const; + + // Return a specific category by its number (see getCatNumbers() for loops) + const Cat& getCategory( uint32_t catNum ); + // Return a specific category by it's name (e.g.: "exd"/"game_script"/ etc...) + const Cat& getCategory( const std::string& catName ); + + const Cat& getExCategory( const std::string& catName, uint32_t exNum, const std::string& path ); + + // Retrieve a file from the dats given its filename + std::unique_ptr getFile( const std::string& path ); + + // Checks that a file exists + bool doesFileExist( const std::string& path ); + + // Checks that a dir exists, there must be a trailing / in the path + // Note that it won't work for dirs that don't contain any file + // e.g.: - "ui/icon/" will return False + // - "ui/icon/000000/" will return True + bool doesDirExist( const std::string& path ); + +protected: + // Return a specific category given a path (calls const Cat& getCategory(const std::string& catName)) + const Cat& getCategoryFromPath( const std::string& path ); + + // From a full path, returns the dirHash and the filenameHash + void getHashes( const std::string& path, uint32_t& dirHash, uint32_t& filenameHash ) const; + + // Lazy instantiation of category + void createCategory( uint32_t catNum ); + + void createExCategory( uint32_t catNum ); + + // Path given to constructor, pointing to the folder with the .index/.datX files + const std::experimental::filesystem::path m_path; + + // Stored categories, indexed by their number, categories are instantiated and parsed individually when they are needed + std::unordered_map> m_cats; + + // List of all the categories numbers, is equal to m_cats.keys() + std::vector m_catNums; + + // Map of all EX categories and their chunks, "CatNum - (ExNum - (ChunkNum - Cat))" + // Map of all EX categories and their chunks, "CatNum - (ExNum - (ChunkNum - Cat))" + using ChunkToCatMap = struct { std::unordered_map< uint32_t, std::unique_ptr< Cat > > chunkToCatMap; }; + using ExNumToChunkMap = struct { std::unordered_map< uint32_t, ChunkToCatMap > exNumToChunkMap; }; + using CatNumToExNumMap = std::unordered_map< uint32_t, ExNumToChunkMap >; + CatNumToExNumMap m_exCats; + std::unordered_map> m_catCreationMutexes; +}; + +} +} + +#endif // XIV_DAT_GAMEDATA_H diff --git a/deps/datReader/Index.cpp b/deps/datReader/Index.cpp new file mode 100644 index 00000000..92dfb5da --- /dev/null +++ b/deps/datReader/Index.cpp @@ -0,0 +1,167 @@ +#include "Index.h" + +#include "bparse.h" + +namespace xiv +{ +namespace dat +{ + struct IndexBlockRecord + { + uint32_t offset; + uint32_t size; + SqPackBlockHash blockHash; + }; + + struct IndexHashTableEntry + { + uint32_t filenameHash; + uint32_t dirHash; + uint32_t datOffset; + uint32_t padding; + }; +} +} + +namespace xiv +{ +namespace utils +{ +namespace bparse +{ + template <> + inline void reorder(xiv::dat::IndexBlockRecord& i_struct) + { + xiv::utils::bparse::reorder(i_struct.offset); + xiv::utils::bparse::reorder(i_struct.size); + xiv::utils::bparse::reorder(i_struct.blockHash); + } + + template <> + inline void reorder(xiv::dat::IndexHashTableEntry& i_struct) + { + xiv::utils::bparse::reorder(i_struct.filenameHash); + xiv::utils::bparse::reorder(i_struct.dirHash); + xiv::utils::bparse::reorder(i_struct.datOffset); + xiv::utils::bparse::reorder(i_struct.padding); + } +} +} +}; + +using xiv::utils::bparse::extract; + +namespace xiv +{ +namespace dat +{ + +Index::Index(const std::experimental::filesystem::path& path) : + SqPack( path ) +{ + if( !m_handle ) + throw new std::runtime_error( "Failed to load Index at " + path.string() ); + + // Hash Table record + auto hashTableBlockRecord = extract( m_handle ); + isIndexBlockValid( hashTableBlockRecord ); + + // Save the posin the stream to go back to it later on + auto pos = m_handle.tellg(); + + // Seek to the pos of the hash table in the file + m_handle.seekg( hashTableBlockRecord.offset ); + + // Preallocate and extract the index_hash_table_entries + std::vector indexHashTableEntries; + extract( m_handle, hashTableBlockRecord.size / sizeof( IndexHashTableEntry ), + indexHashTableEntries ); + + // Feed the correct entry in the HashTable for each index_hash_table_entry + for( auto& indexHashTableEntry : indexHashTableEntries ) + { + auto& hashTableEntry = m_hashTable[indexHashTableEntry.dirHash][indexHashTableEntry.filenameHash]; + // The dat number is found in the offset, last four bits + hashTableEntry.datNum = ( indexHashTableEntry.datOffset & 0xF ) / 0x2; + // The offset in the dat file, needs to strip the dat number indicator + hashTableEntry.datOffset = ( indexHashTableEntry.datOffset & 0xFFFFFFF0 ) * 0x08; + hashTableEntry.dirHash = indexHashTableEntry.dirHash; + hashTableEntry.filenameHash = indexHashTableEntry.filenameHash; + } + + // Come back to where we were before reading the HashTable + m_handle.seekg( pos ); + + // Dat Count + m_datCount = extract( m_handle, "dat_count" ); + + // Free List + isIndexBlockValid( extract( m_handle ) ); + + // Dir Hash Table + isIndexBlockValid( extract( m_handle ) ); +} + +Index::~Index() +{ +} + +uint32_t Index::getDatCount() const +{ + return m_datCount; +} + +const Index::HashTable& Index::getHashTable() const +{ + return m_hashTable; +} + +bool Index::doesFileExist( uint32_t dir_hash, uint32_t filename_hash ) const +{ + auto dir_it = getHashTable().find( dir_hash ); + if( dir_it != getHashTable().end() ) + { + return ( dir_it->second.find( filename_hash ) != dir_it->second.end() ); + } + return false; +} + +bool Index::doesDirExist( uint32_t dir_hash ) const +{ + return ( getHashTable().find( dir_hash ) != getHashTable().end() ); +} + +const Index::DirHashTable& Index::getDirHashTable( uint32_t dir_hash ) const +{ + auto dir_it = getHashTable().find( dir_hash ); + if( dir_it == getHashTable().end() ) + { + throw std::runtime_error( "dirHash not found" ); + } + else + { + return dir_it->second; + } +} + +const Index::HashTableEntry& Index::getHashTableEntry( uint32_t dir_hash, uint32_t filename_hash ) const +{ + auto& dirHashTable = getDirHashTable( dir_hash ); + auto file_it = dirHashTable.find( filename_hash ); + if( file_it == dirHashTable.end() ) + { + throw std::runtime_error( "filenameHash not found" ); + } + else + { + return file_it->second; + } +} + +void Index::isIndexBlockValid( const IndexBlockRecord& i_index_block_record ) +{ + isBlockValid( i_index_block_record.offset, i_index_block_record.size, i_index_block_record.blockHash ); +} + +} +} diff --git a/deps/datReader/Index.h b/deps/datReader/Index.h new file mode 100644 index 00000000..0dc565b7 --- /dev/null +++ b/deps/datReader/Index.h @@ -0,0 +1,59 @@ +#ifndef XIV_DAT_INDEX_H +#define XIV_DAT_INDEX_H + +#include "SqPack.h" + +#include + +#include + +namespace xiv { +namespace dat { + +struct IndexBlockRecord; + +class Index : public SqPack +{ +public: + // Full path to the index file + Index( const std::experimental::filesystem::path& i_path ); + virtual ~Index(); + + // An entry in the hash table, representing a file in a given dat + struct HashTableEntry + { + uint32_t datNum; + uint32_t dirHash; + uint32_t filenameHash; + uint32_t datOffset; + }; + + // HashTable has dir hashes -> filename hashes -> HashTableEntry + using DirHashTable = std::unordered_map< uint32_t, HashTableEntry >; + using HashTable = std::unordered_map< uint32_t, DirHashTable >; + + // Get the number of dat files the index is linked to + uint32_t getDatCount() const; + + bool doesFileExist( uint32_t dir_hash, uint32_t filename_hash ) const; + bool doesDirExist( uint32_t dir_hash ) const; + + // Returns the whole HashTable + const HashTable& getHashTable() const; + // Returns the hash table for a specific dir + const DirHashTable& getDirHashTable( uint32_t dir_hash ) const; + // Returns the HashTableEntry for a given file given its hashes + const HashTableEntry& getHashTableEntry( uint32_t dir_hash, uint32_t filename_hash ) const; + +protected: + // Checks that the block is valid with regards to its hash + void isIndexBlockValid( const IndexBlockRecord& i_index_block_record ); + + uint32_t m_datCount; + HashTable m_hashTable; +}; + +} +} + +#endif // XIV_DAT_INDEX_H diff --git a/deps/datReader/SqPack.cpp b/deps/datReader/SqPack.cpp new file mode 100644 index 00000000..ede929aa --- /dev/null +++ b/deps/datReader/SqPack.cpp @@ -0,0 +1,76 @@ +#include "SqPack.h" + +namespace xiv { +namespace dat { + struct SqPackHeader + { + char magic[0x8]; + uint32_t zero; + uint32_t size; + uint32_t version; + uint32_t type; + }; + + struct SqPackIndexHeader + { + uint32_t size; + uint32_t type; + }; +} +} +namespace xiv { +namespace utils { +namespace bparse { + template <> + inline void reorder(xiv::dat::SqPackHeader& i_struct) + { + for (int32_t i = 0; i < 0x8; ++i) + { + xiv::utils::bparse::reorder(i_struct.magic[i]); + } + xiv::utils::bparse::reorder(i_struct.zero); + xiv::utils::bparse::reorder(i_struct.size); + xiv::utils::bparse::reorder(i_struct.version); + xiv::utils::bparse::reorder(i_struct.type); + } + + template <> + inline void reorder(xiv::dat::SqPackIndexHeader& i_struct) + { + xiv::utils::bparse::reorder(i_struct.size); + xiv::utils::bparse::reorder(i_struct.type); + } +} +} +}; + +using xiv::utils::bparse::extract; + +namespace xiv +{ +namespace dat +{ + + SqPack::SqPack( const std::experimental::filesystem::path& path ) : + // Open the file + m_handle( path.string(), std::ios_base::in | std::ios_base::binary ) + { + // Extract the header + extract( m_handle ); + + // Skip until the IndexHeader the extract it + m_handle.seekg( 0x400 ); + extract( m_handle ); + } + + SqPack::~SqPack() + { + } + + void SqPack::isBlockValid( uint32_t i_offset, uint32_t i_size, const SqPackBlockHash& i_block_hash ) + { + // TODO + } + +} +} diff --git a/deps/datReader/SqPack.h b/deps/datReader/SqPack.h new file mode 100644 index 00000000..cc7feab3 --- /dev/null +++ b/deps/datReader/SqPack.h @@ -0,0 +1,66 @@ +#ifndef XIV_DAT_SQPACK_H +#define XIV_DAT_SQPACK_H + +#include + +#include + +#include "bparse.h" + + +namespace xiv +{ + namespace dat + { + + struct SqPackBlockHash + { + uint8_t hash[0x14]; + uint32_t padding[0xB]; + }; + + } +} +namespace xiv { + namespace utils { + namespace bparse { + template <> inline void reorder( xiv::dat::SqPackBlockHash& i_struct ) + { + for( auto i = 0; i < 0x14; ++i ) + { + xiv::utils::bparse::reorder( i_struct.hash[i] ); + } + for( auto i = 0; i < 0xB; ++i ) + { + xiv::utils::bparse::reorder( i_struct.padding[i] ); + } + } + } + } +}; + +namespace xiv +{ +namespace dat +{ + +class SqPack +{ + +public: + // Full path to the sqpack file + SqPack( const std::experimental::filesystem::path& i_path ); + virtual ~SqPack(); + +protected: + // Checks that a given block is valid iven its hash + void isBlockValid( uint32_t i_offset, uint32_t i_size, const SqPackBlockHash& i_block_hash ); + + // File handle + std::ifstream m_handle; + }; + +} +} + +#endif // XIV_DAT_SQPACK_H diff --git a/deps/datReader/bparse.cpp b/deps/datReader/bparse.cpp new file mode 100644 index 00000000..dc36a7d6 --- /dev/null +++ b/deps/datReader/bparse.cpp @@ -0,0 +1,8 @@ +#include "bparse.h" + +std::string xiv::utils::bparse::extract_cstring( std::istream& i_stream, const std::string& i_name ) +{ + std::string temp_str; + std::getline( i_stream, temp_str, '\0' ); + return temp_str; +} \ No newline at end of file diff --git a/deps/datReader/bparse.h b/deps/datReader/bparse.h new file mode 100644 index 00000000..f26d2faa --- /dev/null +++ b/deps/datReader/bparse.h @@ -0,0 +1,103 @@ +#ifndef XIV_UTILS_BPARSE_H +#define XIV_UTILS_BPARSE_H + +#include +#include +#include +#include + +namespace xiv +{ +namespace utils +{ +namespace bparse +{ + +// Internal macro for byteswapping +template +void byteswap_impl(char (&bytes)[N]) +{ + for( auto p = std::begin( bytes ), end = std::end( bytes ) - 1; p < end; ++p, --end ) + { + std::swap( *p, *end ); + } +} + +// byteswapping any type (no pointers to array) +template +T byteswap(T value) +{ + byteswap_impl(*reinterpret_cast(&value)); + return value; +} + +// Read a struct from a stream +template +void read(std::istream& i_stream, StructType& i_struct) +{ + static_assert( std::is_pod::value, "StructType must be a POD to be able to use read." ); + i_stream.read( reinterpret_cast( &i_struct ), sizeof( StructType ) ); +} + +// By default a type does not need reordering +template void reorder(StructType& i_struct) {} + +// "Overload" for passed struct as arg +template +void extract(std::istream& i_stream, StructType& o_struct) +{ + read( i_stream, o_struct ); + reorder( o_struct ); +} + +// This should not copy because of RVO +// Extract a struct from a stream and log it +template +StructType extract( std::istream& i_stream ) +{ + StructType temp_struct; + extract( i_stream, temp_struct ); + return temp_struct; +} + +template +void extract(std::istream& i_stream, uint32_t i_size, std::vector& o_structs ) +{ + o_structs.reserve( i_size ); + for( uint32_t i = 0; i < i_size; ++i ) + { + o_structs.emplace_back( extract( i_stream ) ); + } +} + +// For simple (integral) types just provide name and endianness directly +template +StructType extract(std::istream& i_stream, const std::string& i_name, bool i_is_le = true) +{ + StructType temp_struct; + read( i_stream, temp_struct ); + if( !i_is_le ) + { + temp_struct = byteswap( temp_struct ); + } + return temp_struct; +} + +template +void extract(std::istream& i_stream, const std::string& i_name, uint32_t i_size, std::vector& o_structs, bool i_is_le = true) +{ + o_structs.reserve( i_size ); + for( uint32_t i = 0; i < i_size; ++i ) + { + o_structs.emplace_back( extract( i_stream, i_name ) ); + } +} + +// For cstrings +std::string extract_cstring( std::istream& i_stream, const std::string& i_name ); + +} +} +} + +#endif // XIV_UTILS_BPARSE_H diff --git a/deps/datReader/conv.cpp b/deps/datReader/conv.cpp new file mode 100644 index 00000000..40b94080 --- /dev/null +++ b/deps/datReader/conv.cpp @@ -0,0 +1,35 @@ +#include "conv.h" + +namespace xiv { +namespace utils { +namespace conv { + float half2float( const uint16_t i_value ) + { + uint32_t t1; + uint32_t t2; + uint32_t t3; + + t1 = i_value & 0x7fff; // Non-sign bits + t2 = i_value & 0x8000; // Sign bit + t3 = i_value & 0x7c00; // Exponent + t1 <<= 13; // Align mantissa on MSB + t2 <<= 16; // Shift sign bit into position + + t1 += 0x38000000; // Adjust bias + + t1 = ( t3 == 0 ? 0 : t1 ); // Denormals-as-zero + + t1 |= t2; // Re-insert sign bit + + return *reinterpret_cast< float* >( &t1 ); + } + + float ubyte2float( const uint8_t i_value ) + { + return i_value / 255.0f; + } + +} +} +} + diff --git a/deps/datReader/conv.h b/deps/datReader/conv.h new file mode 100644 index 00000000..c0a4529b --- /dev/null +++ b/deps/datReader/conv.h @@ -0,0 +1,17 @@ +#ifndef XIV_UTILS_CONV_H +#define XIV_UTILS_CONV_H + +#include +#include +#include + +namespace xiv { +namespace utils { +namespace conv { + float half2float( const uint16_t i_value ); + float ubyte2float( const uint8_t i_value ); +} +} +} + +#endif // XIV_UTILS_CONV_H diff --git a/deps/datReader/crc32.cpp b/deps/datReader/crc32.cpp new file mode 100644 index 00000000..7b9490d9 --- /dev/null +++ b/deps/datReader/crc32.cpp @@ -0,0 +1,180 @@ +#include "crc32.h" + +#include +#include + +#include + +namespace internal +{ + // Mutex to prevent two threads from concurrently trying to build the crc tables atthe same time + std::mutex crc_creation_mutex; + + typedef std::vector CrcTable; + + // Our crc/rev_crc tables + CrcTable crc_table; + CrcTable rev_crc_table; + + bool crc_tables_created = false; + + void build_crc_tables() + { + std::lock_guard lock(crc_creation_mutex); + if (!crc_tables_created) + { + crc_table.resize(0x100); + rev_crc_table.resize(0x100); + for (auto i = 0; i < 0x100; ++i) + { + uint32_t crc = i; + for (auto j = 0; j < 8; ++j) + { + if (crc & 1) + { + crc = 0xEDB88320 ^ (crc >> 1); + } + else + { + crc = crc >> 1; + } + } + crc_table[i] = crc; + rev_crc_table[crc >> 24] = i + ((crc & 0xFFFFFF) << 8); + } + } + crc_tables_created = true; + } + + const CrcTable& get_crc_table() + { + if (!crc_tables_created) + { + build_crc_tables(); + } + return crc_table; + } + + const CrcTable& get_rev_crc_table() + { + if (!crc_tables_created) + { + build_crc_tables(); + } + return rev_crc_table; + } +} + +namespace xiv +{ +namespace utils +{ +namespace crc32 +{ + +uint32_t compute(const std::string& i_input, uint32_t init_crc) +{ + // Classical crc stuff + auto& crc_table = internal::get_crc_table(); + auto crc = init_crc; + for(std::size_t i = 0; i < i_input.size(); ++i) + { + crc = crc_table[(crc ^ i_input[i]) & 0xFF] ^ (crc >> 8); + } + return crc; +} + +uint32_t rev_compute(const std::string& i_input, uint32_t init_crc) +{ + auto& rev_crc_table = internal::get_rev_crc_table(); + auto crc = init_crc; + const auto input_size = i_input.size(); + // Reverse crc + for(auto i = input_size; i > 0; --i) + { + crc = rev_crc_table[crc >> 24] ^ ((crc << 8) & 0xFFFFFF00) ^ i_input[input_size - i - 1]; + } + // Compute the 4 bytes needed for this init_crc + for (auto i = 0; i < 4; ++i) + { + crc = rev_crc_table[crc >> 24] ^ ((crc << 8) & 0xFFFFFF00); + } + return crc; +} + +void generate_hashes_1(std::string& i_format, const uint32_t i_first_index, std::vector& o_hashes) +{ + char* str = const_cast(i_format.data()); + const uint32_t str_size = i_format.size(); + + o_hashes.resize(10000); + + uint32_t i = 0; + for (char a = '0'; a <= '9'; ++a) + { + str[i_first_index] = a; + for (char b = '0'; b <= '9'; ++b) + { + str[i_first_index + 1] = b; + for (char c = '0'; c <= '9'; ++c) + { + str[i_first_index + 2] = c; + for (char d = '0'; d <= '9'; ++d) + { + str[i_first_index + 3] = d; + o_hashes[i] = ::crc32(0, reinterpret_cast(&(str[0])), str_size) ^ 0xFFFFFFFF; + ++i; + } + } + } + } +} + +void generate_hashes_2(std::string& i_format, const uint32_t i_first_index, const uint32_t i_second_index, std::vector& o_hashes) +{ + char* str = const_cast(i_format.data()); + const uint32_t str_size = i_format.size(); + + o_hashes.resize(100000000); + + uint32_t i = 0; + for (char a = '0'; a <= '9'; ++a) + { + str[i_first_index] = a; + for (char b = '0'; b <= '9'; ++b) + { + str[i_first_index + 1] = b; + for (char c = '0'; c <= '9'; ++c) + { + str[i_first_index + 2] = c; + for (char d = '0'; d <= '9'; ++d) + { + str[i_first_index + 3] = d; + for (char e = '0'; e <= '9'; ++e) + { + str[i_second_index] = e; + for (char f = '0'; f <= '9'; ++f) + { + str[i_second_index + 1] = f; + for (char g = '0'; g <= '9'; ++g) + { + str[i_second_index + 2] = g; + for (char h = '0'; h <= '9'; ++h) + { + str[i_second_index + 3] = h; + o_hashes[i] = ::crc32(0, reinterpret_cast(&(str[0])), str_size) ^ 0xFFFFFFFF; + ++i; + } + } + } + } + } + } + } + } +} + + +} +} +} diff --git a/deps/datReader/crc32.h b/deps/datReader/crc32.h new file mode 100644 index 00000000..e8c59286 --- /dev/null +++ b/deps/datReader/crc32.h @@ -0,0 +1,26 @@ +#ifndef XIV_UTILS_CRC32_H +#define XIV_UTILS_CRC32_H + +#include +#include +#include + +namespace xiv { +namespace utils { +namespace crc32 { + + // Normal crc32 computation from a given intial crc value, use zlib.crc32 instead, the final XOR 0xFFFFFFFF is not done + uint32_t compute( const std::string& i_input, uint32_t init_crc = 0xFFFFFFFF ); + + // Computes the 4 missing bytes XXXX such as init_crc = crc32(prefix_string) + // and string_to_find = prefix_string + XXXX + i_input + uint32_t rev_compute( const std::string& i_input, uint32_t init_crc = 0 ); + + void generate_hashes_1( std::string& i_format, const uint32_t i_first_index, std::vector< uint32_t >& o_hashes ); + void generate_hashes_2( std::string& i_format, const uint32_t i_first_index, const uint32_t i_second_index, std::vector< uint32_t >& o_hashes ); + +} +} +} + +#endif // XIV_UTILS_CRC32_H diff --git a/deps/datReader/stream.cpp b/deps/datReader/stream.cpp new file mode 100644 index 00000000..0a38fbfc --- /dev/null +++ b/deps/datReader/stream.cpp @@ -0,0 +1,16 @@ +#include "stream.h" + +#include +#include +#include + +namespace xiv +{ +namespace utils +{ +namespace stream +{ + +} +} +} diff --git a/deps/datReader/stream.h b/deps/datReader/stream.h new file mode 100644 index 00000000..c0b98074 --- /dev/null +++ b/deps/datReader/stream.h @@ -0,0 +1,27 @@ +#ifndef XIV_UTILS_STREAM_H +#define XIV_UTILS_STREAM_H + +#include +#include +#include + +namespace xiv +{ +namespace utils +{ +namespace stream +{ +template > +class vectorwrapbuf : public std::basic_streambuf +{ +public: + vectorwrapbuf(std::vector &vec) + { + this->setg(vec.data(), vec.data(), vec.data() + vec.size()); + } +}; +} +} +} + +#endif // XIV_UTILS_STREAM_H diff --git a/deps/datReader/zlib.cpp b/deps/datReader/zlib.cpp new file mode 100644 index 00000000..9f75d929 --- /dev/null +++ b/deps/datReader/zlib.cpp @@ -0,0 +1,65 @@ +#include "zlib.h" +#include +#include +#include +#include + +namespace xiv +{ +namespace utils +{ +namespace zlib +{ + +void compress(const std::vector& in, std::vector& out) +{ + // Fetching upper bound for out size + auto out_size = compressBound(in.size()); + out.resize(out_size); + + auto ret = compress2(reinterpret_cast(out.data()), &out_size, + reinterpret_cast(in.data()), in.size(), Z_BEST_COMPRESSION); + + if (ret != Z_OK) + { + throw std::runtime_error("Error at zlib uncompress: " + std::to_string(ret)); + } + + out.resize(out_size); +} + +void no_header_decompress(uint8_t* in, uint32_t in_size, uint8_t* out, uint32_t out_size) +{ + z_stream strm; + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.avail_in = in_size; + strm.next_in = Z_NULL; + + // Init with -15 because we do not have header in this compressed data + auto ret = inflateInit2(&strm, -15); + if (ret != Z_OK) + { + throw std::runtime_error("Error at zlib init: " + std::to_string(ret)); + } + + // Set pointers to the right addresses + strm.next_in = in; + strm.avail_out = out_size; + strm.next_out = out; + + // Effectively decompress data + ret = inflate(&strm, Z_NO_FLUSH); + if (ret != Z_STREAM_END) + { + throw std::runtime_error("Error at zlib inflate: " + std::to_string(ret)); + } + + // Clean up + inflateEnd(&strm); +} + +} +} +} diff --git a/deps/datReader/zlib.h b/deps/datReader/zlib.h new file mode 100644 index 00000000..942efaf9 --- /dev/null +++ b/deps/datReader/zlib.h @@ -0,0 +1,21 @@ +#ifndef XIV_UTILS_ZLIB_H +#define XIV_UTILS_ZLIB_H + +#include +#include + +namespace xiv +{ +namespace utils +{ +namespace zlib +{ + +void compress(const std::vector& in, std::vector& out); +void no_header_decompress(uint8_t* in, uint32_t in_size, uint8_t* out, uint32_t out_size); + +} +} +} + +#endif // XIV_UTILS_ZLIB_H diff --git a/deps/inih/INIReader.h b/deps/inih/INIReader.h new file mode 100644 index 00000000..fbb5bc2c --- /dev/null +++ b/deps/inih/INIReader.h @@ -0,0 +1,441 @@ +// Read an INI file into easy-to-access name/value pairs. + +// inih and INIReader are released under the New BSD license (see LICENSE.txt). +// Go to the project home page for more info: +// +// https://github.com/benhoyt/inih +/* inih -- simple .INI file parser + +inih is released under the New BSD license (see LICENSE.txt). Go to the project +home page for more info: + +https://github.com/benhoyt/inih + +*/ + +#ifndef __INI_H__ +#define __INI_H__ + +/* Make this header file easier to include in C++ code */ +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/* Typedef for prototype of handler function. */ +typedef int (*ini_handler)(void* user, const char* section, + const char* name, const char* value); + +/* Typedef for prototype of fgets-style reader function. */ +typedef char* (*ini_reader)(char* str, int num, void* stream); + +/* Parse given INI-style file. May have [section]s, name=value pairs + (whitespace stripped), and comments starting with ';' (semicolon). Section + is "" if name=value pair parsed before any section heading. name:value + pairs are also supported as a concession to Python's configparser. + + For each name=value pair parsed, call handler function with given user + pointer as well as section, name, and value (data only valid for duration + of handler call). Handler should return nonzero on success, zero on error. + + Returns 0 on success, line number of first error on parse error (doesn't + stop on first error), -1 on file open error, or -2 on memory allocation + error (only when INI_USE_STACK is zero). +*/ +int ini_parse(const char* filename, ini_handler handler, void* user); + +/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't + close the file when it's finished -- the caller must do that. */ +int ini_parse_file(FILE* file, ini_handler handler, void* user); + +/* Same as ini_parse(), but takes an ini_reader function pointer instead of + filename. Used for implementing custom or string-based I/O. */ +int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, + void* user); + +/* Nonzero to allow multi-line value parsing, in the style of Python's + configparser. If allowed, ini_parse() will call the handler with the same + name for each subsequent line parsed. */ +#ifndef INI_ALLOW_MULTILINE +#define INI_ALLOW_MULTILINE 1 +#endif + +/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of + the file. See http://code.google.com/p/inih/issues/detail?id=21 */ +#ifndef INI_ALLOW_BOM +#define INI_ALLOW_BOM 1 +#endif + +/* Nonzero to allow inline comments (with valid inline comment characters + specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match + Python 3.2+ configparser behaviour. */ +#ifndef INI_ALLOW_INLINE_COMMENTS +#define INI_ALLOW_INLINE_COMMENTS 1 +#endif +#ifndef INI_INLINE_COMMENT_PREFIXES +#define INI_INLINE_COMMENT_PREFIXES ";" +#endif + +/* Nonzero to use stack, zero to use heap (malloc/free). */ +#ifndef INI_USE_STACK +#define INI_USE_STACK 1 +#endif + +/* Stop parsing on first error (default is to keep parsing). */ +#ifndef INI_STOP_ON_FIRST_ERROR +#define INI_STOP_ON_FIRST_ERROR 0 +#endif + +/* Maximum line length for any line in INI file. */ +#ifndef INI_MAX_LINE +#define INI_MAX_LINE 200 +#endif + +#ifdef __cplusplus +} +#endif + +/* inih -- simple .INI file parser + +inih is released under the New BSD license (see LICENSE.txt). Go to the project +home page for more info: + +https://github.com/benhoyt/inih + +*/ + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include +#include +#include + +#if !INI_USE_STACK +#include +#endif + +#define MAX_SECTION 50 +#define MAX_NAME 50 + +/* Strip whitespace chars off end of given string, in place. Return s. */ +inline static char* rstrip(char* s) +{ + char* p = s + strlen(s); + while (p > s && isspace((unsigned char)(*--p))) + *p = '\0'; + return s; +} + +/* Return pointer to first non-whitespace char in given string. */ +inline static char* lskip(const char* s) +{ + while (*s && isspace((unsigned char)(*s))) + s++; + return (char*)s; +} + +/* Return pointer to first char (of chars) or inline comment in given string, + or pointer to null at end of string if neither found. Inline comment must + be prefixed by a whitespace character to register as a comment. */ +inline static char* find_chars_or_comment(const char* s, const char* chars) +{ +#if INI_ALLOW_INLINE_COMMENTS + int was_space = 0; + while (*s && (!chars || !strchr(chars, *s)) && + !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) { + was_space = isspace((unsigned char)(*s)); + s++; + } +#else + while (*s && (!chars || !strchr(chars, *s))) { + s++; + } +#endif + return (char*)s; +} + +/* Version of strncpy that ensures dest (size bytes) is null-terminated. */ +inline static char* strncpy0(char* dest, const char* src, size_t size) +{ + strncpy(dest, src, size); + dest[size - 1] = '\0'; + return dest; +} + +/* See documentation in header file. */ +inline int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, + void* user) +{ + /* Uses a fair bit of stack (use heap instead if you need to) */ +#if INI_USE_STACK + char line[INI_MAX_LINE]; +#else + char* line; +#endif + char section[MAX_SECTION] = ""; + char prev_name[MAX_NAME] = ""; + + char* start; + char* end; + char* name; + char* value; + int lineno = 0; + int error = 0; + +#if !INI_USE_STACK + line = (char*)malloc(INI_MAX_LINE); + if (!line) { + return -2; + } +#endif + + /* Scan through stream line by line */ + while (reader(line, INI_MAX_LINE, stream) != NULL) { + lineno++; + + start = line; +#if INI_ALLOW_BOM + if (lineno == 1 && (unsigned char)start[0] == 0xEF && + (unsigned char)start[1] == 0xBB && + (unsigned char)start[2] == 0xBF) { + start += 3; + } +#endif + start = lskip(rstrip(start)); + + if (*start == ';' || *start == '#') { + /* Per Python configparser, allow both ; and # comments at the + start of a line */ + } +#if INI_ALLOW_MULTILINE + else if (*prev_name && *start && start > line) { + +#if INI_ALLOW_INLINE_COMMENTS + end = find_chars_or_comment(start, NULL); + if (*end) + *end = '\0'; + rstrip(start); +#endif + + /* Non-blank line with leading whitespace, treat as continuation + of previous name's value (as per Python configparser). */ + if (!handler(user, section, prev_name, start) && !error) + error = lineno; + } +#endif + else if (*start == '[') { + /* A "[section]" line */ + end = find_chars_or_comment(start + 1, "]"); + if (*end == ']') { + *end = '\0'; + strncpy0(section, start + 1, sizeof(section)); + *prev_name = '\0'; + } + else if (!error) { + /* No ']' found on section line */ + error = lineno; + } + } + else if (*start) { + /* Not a comment, must be a name[=:]value pair */ + end = find_chars_or_comment(start, "=:"); + if (*end == '=' || *end == ':') { + *end = '\0'; + name = rstrip(start); + value = lskip(end + 1); +#if INI_ALLOW_INLINE_COMMENTS + end = find_chars_or_comment(value, NULL); + if (*end) + *end = '\0'; +#endif + rstrip(value); + + /* Valid name[=:]value pair found, call handler */ + strncpy0(prev_name, name, sizeof(prev_name)); + if (!handler(user, section, name, value) && !error) + error = lineno; + } + else if (!error) { + /* No '=' or ':' found on name[=:]value line */ + error = lineno; + } + } + +#if INI_STOP_ON_FIRST_ERROR + if (error) + break; +#endif + } + +#if !INI_USE_STACK + free(line); +#endif + + return error; +} + +/* See documentation in header file. */ +inline int ini_parse_file(FILE* file, ini_handler handler, void* user) +{ + return ini_parse_stream((ini_reader)fgets, file, handler, user); +} + +/* See documentation in header file. */ +inline int ini_parse(const char* filename, ini_handler handler, void* user) +{ + FILE* file; + int error; + + file = fopen(filename, "r"); + if (!file) + return -1; + error = ini_parse_file(file, handler, user); + fclose(file); + return error; +} + +#endif /* __INI_H__ */ + + +#ifndef __INIREADER_H__ +#define __INIREADER_H__ + +#include +#include +#include + +// Read an INI file into easy-to-access name/value pairs. (Note that I've gone +// for simplicity here rather than speed, but it should be pretty decent.) +class INIReader +{ +public: + // Empty Constructor + INIReader() {}; + + // Construct INIReader and parse given filename. See ini.h for more info + // about the parsing. + INIReader(std::string filename); + + // Return the result of ini_parse(), i.e., 0 on success, line number of + // first error on parse error, or -1 on file open error. + int ParseError() const; + + // Return the list of sections found in ini file + const std::set& Sections() const; + + // Get a string value from INI file, returning default_value if not found. + std::string Get(std::string section, std::string name, + std::string default_value) const; + + // Get an integer (long) value from INI file, returning default_value if + // not found or not a valid integer (decimal "1234", "-1234", or hex "0x4d2"). + long GetInteger(std::string section, std::string name, long default_value) const; + + // Get a real (floating point double) value from INI file, returning + // default_value if not found or not a valid floating point value + // according to strtod(). + double GetReal(std::string section, std::string name, double default_value) const; + + // Get a boolean value from INI file, returning default_value if not found or if + // not a valid true/false value. Valid true values are "true", "yes", "on", "1", + // and valid false values are "false", "no", "off", "0" (not case sensitive). + bool GetBoolean(std::string section, std::string name, bool default_value) const; + +protected: + int _error; + std::map _values; + std::set _sections; + static std::string MakeKey(std::string section, std::string name); + static int ValueHandler(void* user, const char* section, const char* name, + const char* value); +}; + +#endif // __INIREADER_H__ + + +#ifndef __INIREADER__ +#define __INIREADER__ + +#include +#include +#include + +using std::string; + +inline INIReader::INIReader(string filename) +{ + _error = ini_parse(filename.c_str(), ValueHandler, this); +} + +inline int INIReader::ParseError() const +{ + return _error; +} + +inline const std::set& INIReader::Sections() const +{ + return _sections; +} + +inline string INIReader::Get(string section, string name, string default_value) const +{ + string key = MakeKey(section, name); + return _values.count(key) ? _values.at(key) : default_value; +} + +inline long INIReader::GetInteger(string section, string name, long default_value) const +{ + string valstr = Get(section, name, ""); + const char* value = valstr.c_str(); + char* end; + // This parses "1234" (decimal) and also "0x4D2" (hex) + long n = strtol(value, &end, 0); + return end > value ? n : default_value; +} + +inline double INIReader::GetReal(string section, string name, double default_value) const +{ + string valstr = Get(section, name, ""); + const char* value = valstr.c_str(); + char* end; + double n = strtod(value, &end); + return end > value ? n : default_value; +} + +inline bool INIReader::GetBoolean(string section, string name, bool default_value) const +{ + string valstr = Get(section, name, ""); + // Convert to lower case to make string comparisons case-insensitive + std::transform(valstr.begin(), valstr.end(), valstr.begin(), ::tolower); + if (valstr == "true" || valstr == "yes" || valstr == "on" || valstr == "1") + return true; + else if (valstr == "false" || valstr == "no" || valstr == "off" || valstr == "0") + return false; + else + return default_value; +} + +inline string INIReader::MakeKey(string section, string name) +{ + string key = section + "=" + name; + // Convert to lower case to make section/name lookups case-insensitive + std::transform(key.begin(), key.end(), key.begin(), ::tolower); + return key; +} + +inline int INIReader::ValueHandler(void* user, const char* section, const char* name, + const char* value) +{ + INIReader* reader = (INIReader*)user; + string key = MakeKey(section, name); + if (reader->_values[key].size() > 0) + reader->_values[key] += "\n"; + reader->_values[key] += value; + reader->_sections.insert(section); + return 1; +} + +#endif // __INIREADER__ diff --git a/deps/inih/LICENSE.txt b/deps/inih/LICENSE.txt new file mode 100644 index 00000000..cb7ee2d0 --- /dev/null +++ b/deps/inih/LICENSE.txt @@ -0,0 +1,27 @@ + +The "inih" library is distributed under the New BSD license: + +Copyright (c) 2009, Ben Hoyt +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Ben Hoyt nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY BEN HOYT ''AS IS'' AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BEN HOYT BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/deps/inih/README.md b/deps/inih/README.md new file mode 100644 index 00000000..ab091a86 --- /dev/null +++ b/deps/inih/README.md @@ -0,0 +1,41 @@ +# inih +[![Build Status](https://travis-ci.org/jtilly/inih.svg?branch=master)](https://travis-ci.org/jtilly/inih) + +This is a header only C++ version of [inih](https://github.com/benhoyt/inih). + +**inih (INI Not Invented Here)** is a simple [.INI file](http://en.wikipedia.org/wiki/INI_file) parser written in C. It's only a couple of pages of code, and it was designed to be _small and simple_, so it's good for embedded systems. It's also more or less compatible with Python's [ConfigParser](http://docs.python.org/library/configparser.html) style of .INI files, including RFC 822-style multi-line syntax and `name: value` entries. + +## Usage + +All you need to do is to include `INIReader.h`. Consider the following example (`INIReaderTest.cpp`): + +```cpp +#include +#include "INIReader.h" + +int main() { + + INIReader reader("test.ini"); + + if (reader.ParseError() < 0) { + std::cout << "Can't load 'test.ini'\n"; + return 1; + } + std::cout << "Config loaded from 'test.ini': version=" + << reader.GetInteger("protocol", "version", -1) << ", name=" + << reader.Get("user", "name", "UNKNOWN") << ", email=" + << reader.Get("user", "email", "UNKNOWN") << ", pi=" + << reader.GetReal("user", "pi", -1) << ", active=" + << reader.GetBoolean("user", "active", true) << "\n"; + return 0; + +} +``` + +To compile and run: + +```sh +g++ INIReaderTest.cpp -o INIReaderTest.out +./INIReaderTest.out +# Config loaded from 'test.ini': version=6, name=Bob Smith, email=bob@smith.com, pi=3.14159, active=1 +``` diff --git a/deps/inih/test.ini b/deps/inih/test.ini new file mode 100644 index 00000000..000f58ae --- /dev/null +++ b/deps/inih/test.ini @@ -0,0 +1,12 @@ +; Test config file for ini_example.c and INIReaderTest.cpp + +[protocol] ; Protocol configuration +version=6 ; IPv6 + +[user] +name = Bob Smith ; Spaces around '=' are stripped +email = bob@smith.com ; And comments (like this) ignored +active = true ; Test a boolean +pi = 3.14159 ; Test a floating point number +multi = this is a ; test + multi-line value ; test diff --git a/deps/mysqlConnector/CMakeLists.txt b/deps/mysqlConnector/CMakeLists.txt new file mode 100644 index 00000000..1002f498 --- /dev/null +++ b/deps/mysqlConnector/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.2.0) +project(Sapphire) + + +file( GLOB UTILS_PUBLIC_INCLUDE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/*" ) +file( GLOB UTILS_SOURCE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/src/*" ) + +set( CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ) + +add_library( mysqlConnector ${UTILS_PUBLIC_INCLUDE_FILES} ${UTILS_SOURCE_FILES} ) + +set_target_properties( mysqlConnector PROPERTIES + CXX_STANDARD 17 + CXX_STANDARD_REQUIRED ON + CXX_EXTENSIONS ON + ) + +target_include_directories( mysqlConnector PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ) +if(UNIX) + target_include_directories( mysqlConnector PUBLIC "/usr/include/mysql/" ) + +else() + target_include_directories( mysqlConnector PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../MySQL/" ) +endif() +target_link_libraries( mysqlConnector PUBLIC mysql ) +#cotire(mysqlConnector) diff --git a/deps/mysqlConnector/Connection.cpp b/deps/mysqlConnector/Connection.cpp new file mode 100644 index 00000000..9b317dc9 --- /dev/null +++ b/deps/mysqlConnector/Connection.cpp @@ -0,0 +1,254 @@ +#include "Connection.h" +#include "MySqlBase.h" +#include "Statement.h" +#include "PreparedStatement.h" +#include + +#include + +Mysql::Connection::Connection( std::shared_ptr< MySqlBase > pBase, + const std::string& hostName, + const std::string& userName, + const std::string& password, + uint16_t port ) : + m_pBase( pBase ), + m_bConnected( false ) +{ + m_pRawCon = mysql_init( nullptr ); + if( mysql_real_connect( m_pRawCon, hostName.c_str(), userName.c_str(), password.c_str(), + nullptr, port, nullptr, 0) == nullptr ) + throw std::runtime_error( mysql_error( m_pRawCon ) ); + m_bConnected = true; + +} + +Mysql::Connection::Connection( std::shared_ptr< MySqlBase > pBase, + const std::string& hostName, + const std::string& userName, + const std::string& password, + const optionMap& options, + uint16_t port ) : + m_pBase( pBase ) +{ + m_pRawCon = mysql_init( nullptr ); + // Different mysql versions support different options, for now whatever was unsupporter here was commented out + // but left there. + for( auto entry : options ) + { + switch( entry.first ) + { + // integer based options + case MYSQL_OPT_CONNECT_TIMEOUT: + case MYSQL_OPT_PROTOCOL: + case MYSQL_OPT_READ_TIMEOUT: + // case MYSQL_OPT_SSL_MODE: + // case MYSQL_OPT_RETRY_COUNT: + case MYSQL_OPT_WRITE_TIMEOUT: + // case MYSQL_OPT_MAX_ALLOWED_PACKET: + // case MYSQL_OPT_NET_BUFFER_LENGTH: + { + uint32_t optVal = std::stoi( entry.second, nullptr, 10 ); + setOption( entry.first, optVal ); + } + break; + + // bool based options + // case MYSQL_ENABLE_CLEARTEXT_PLUGIN: + // case MYSQL_OPT_CAN_HANDLE_EXPIRED_PASSWORDS: + case MYSQL_OPT_COMPRESS: + case MYSQL_OPT_GUESS_CONNECTION: + case MYSQL_OPT_LOCAL_INFILE: + case MYSQL_OPT_RECONNECT: + // case MYSQL_OPT_SSL_ENFORCE: + // case MYSQL_OPT_SSL_VERIFY_SERVER_CERT: + case MYSQL_OPT_USE_EMBEDDED_CONNECTION: + case MYSQL_OPT_USE_REMOTE_CONNECTION: + case MYSQL_REPORT_DATA_TRUNCATION: + case MYSQL_SECURE_AUTH: + { + my_bool optVal = entry.second == "0" ? 0 : 1; + setOption( entry.first, &optVal ); + } + break; + + // string based options + // case MYSQL_DEFAULT_AUTH: + // case MYSQL_OPT_BIND: + // case MYSQL_OPT_SSL_CA: + // case MYSQL_OPT_SSL_CAPATH: + // case MYSQL_OPT_SSL_CERT: + // case MYSQL_OPT_SSL_CIPHER: + // case MYSQL_OPT_SSL_CRL: + // case MYSQL_OPT_SSL_CRLPATH: + // case MYSQL_OPT_SSL_KEY: + // case MYSQL_OPT_TLS_VERSION: + // case MYSQL_PLUGIN_DIR: + case MYSQL_READ_DEFAULT_FILE: + case MYSQL_READ_DEFAULT_GROUP: + // case MYSQL_SERVER_PUBLIC_KEY: + case MYSQL_SET_CHARSET_DIR: + case MYSQL_SET_CHARSET_NAME: + case MYSQL_SET_CLIENT_IP: + case MYSQL_SHARED_MEMORY_BASE_NAME: + { + setOption( entry.first, entry.second.c_str() ); + } + break; + + default: + throw std::runtime_error( "Unknown option: " + std::to_string( entry.first ) ); + } + + } + + + if( mysql_real_connect( m_pRawCon, hostName.c_str(), userName.c_str(), password.c_str(), + nullptr, port, nullptr, 0) == nullptr ) + throw std::runtime_error( mysql_error( m_pRawCon ) ); + +} + + +Mysql::Connection::~Connection() +{ +} + +void Mysql::Connection::setOption( enum mysqlOption option, const void *arg ) +{ + + if( mysql_options( m_pRawCon, static_cast< mysql_option>( option ), arg ) != 0 ) + throw std::runtime_error( "Connection::setOption failed!" ); + +} + +void Mysql::Connection::setOption( enum mysqlOption option, uint32_t arg ) +{ + + if( mysql_options( m_pRawCon, static_cast< mysql_option>( option ), &arg ) != 0 ) + throw std::runtime_error( "Connection::setOption failed!" ); + +} + +void Mysql::Connection::setOption( enum mysqlOption option, const std::string& arg ) +{ + + if( mysql_options( m_pRawCon, static_cast< mysql_option>( option ), arg.c_str() ) != 0 ) + throw std::runtime_error( "Connection::setOption failed!" ); + +} + +void Mysql::Connection::close() +{ + mysql_close( m_pRawCon ); + m_bConnected = false; +} + +bool Mysql::Connection::isClosed() const +{ + return !m_bConnected; +} + +std::shared_ptr< Mysql::MySqlBase > Mysql::Connection::getMySqlBase() const +{ + return m_pBase; +} + +void Mysql::Connection::setAutoCommit( bool autoCommit ) +{ + auto b = static_cast< my_bool >( autoCommit == true ? 1 : 0 ); + if( mysql_autocommit( m_pRawCon, b ) != 0 ) + throw std::runtime_error( "Connection::setAutoCommit failed!" ); +} + +bool Mysql::Connection::getAutoCommit() +{ + // TODO: should be replaced with wrapped sql query function once available + std::string query("SELECT @@autocommit"); + auto res = mysql_real_query( m_pRawCon, query.c_str(), query.length() ); + + if( res != 0 ) + throw std::runtime_error( "Query failed!" ); + + auto pRes = mysql_store_result( m_pRawCon ); + auto row = mysql_fetch_row( pRes ); + + uint32_t ac = atoi( row[0] ); + + return ac != 0; +} + +void Mysql::Connection::beginTransaction() +{ + auto stmt = createStatement(); + stmt->execute( "START TRANSACTION;" ); +} + +void Mysql::Connection::commitTransaction() +{ + auto stmt = createStatement(); + stmt->execute( "COMMIT" ); +} + +void Mysql::Connection::rollbackTransaction() +{ + auto stmt = createStatement(); + stmt->execute( "ROLLBACK" ); +} + +std::string Mysql::Connection::escapeString( const std::string &inData ) +{ + std::unique_ptr< char[] > buffer( new char[inData.length() * 2 + 1] ); + if( !buffer.get() ) + return ""; + unsigned long return_len = mysql_real_escape_string( m_pRawCon, buffer.get(), + inData.c_str(), static_cast< unsigned long > ( inData.length() ) ); + return std::string( buffer.get(), return_len ); +} + +void Mysql::Connection::setSchema( const std::string &schema ) +{ + if( mysql_select_db( m_pRawCon, schema.c_str() ) != 0 ) + throw std::runtime_error( "Current database could not be changed to " + schema ); +} + +std::shared_ptr< Mysql::Statement > Mysql::Connection::createStatement() +{ + return std::make_shared< Mysql::Statement >( shared_from_this() ); +} + +MYSQL* Mysql::Connection::getRawCon() +{ + return m_pRawCon; +} + +std::string Mysql::Connection::getError() +{ + auto mysqlError = mysql_error( m_pRawCon ); + if( mysqlError ) + return std::string( mysqlError ); + return ""; +} + +std::shared_ptr< Mysql::PreparedStatement > Mysql::Connection::prepareStatement( const std::string &sql ) +{ + MYSQL_STMT* stmt = mysql_stmt_init( getRawCon() ); + + if( !stmt ) + throw std::runtime_error( "Could not init prepared statement: " + getError() ); + + if( mysql_stmt_prepare( stmt, sql.c_str(), sql.size() ) ) + throw std::runtime_error( "Could not prepare statement: " + getError() ); + + return std::make_shared< PreparedStatement >( stmt, shared_from_this() ); +} + +uint32_t Mysql::Connection::getErrorNo() +{ + return mysql_errno( m_pRawCon ); +} + +bool Mysql::Connection::ping() +{ + return mysql_ping( m_pRawCon ) != 0; +} + diff --git a/deps/mysqlConnector/Connection.h b/deps/mysqlConnector/Connection.h new file mode 100644 index 00000000..1283ead5 --- /dev/null +++ b/deps/mysqlConnector/Connection.h @@ -0,0 +1,89 @@ +#ifndef SAPPHIRE_CONNECTION_H +#define SAPPHIRE_CONNECTION_H + +#include +#include +#include "MysqlCommon.h" + +typedef struct st_mysql MYSQL; + +namespace Mysql +{ + using optionMap = std::map< enum mysqlOption, std::string >; + class MySqlBase; + class Statement; + class PreparedStatement; + + class Connection : public std::enable_shared_from_this< Connection > + { + public: + Connection( std::shared_ptr< MySqlBase > pBase, + const std::string& hostName, + const std::string& userName, + const std::string& password, + uint16_t port = 3306); + + Connection( std::shared_ptr< MySqlBase > pBase, + const std::string& hostName, + const std::string& userName, + const std::string& password, + const optionMap& options, + uint16_t port = 3306 ); + + virtual ~Connection(); + + void close(); + bool isClosed() const; + + bool ping(); + + void setOption( enum mysqlOption option, const void* arg ); + void setOption( enum mysqlOption option, uint32_t arg ); + void setOption( enum mysqlOption option, const std::string& arg ); + + std::shared_ptr< MySqlBase > getMySqlBase() const; + + void setAutoCommit( bool autoCommit ); + bool getAutoCommit(); + + std::string escapeString( const std::string& inData ); + + void setSchema( const std::string& catalog ); + + std::shared_ptr< Statement > createStatement(); + + void beginTransaction(); + void commitTransaction(); + void rollbackTransaction(); + + std::string getSchema(); + + std::string getError(); + uint32_t getErrorNo(); + + bool isReadOnly(); + void setReadOnly( bool readOnly ); + + bool isValid(); + + bool reconnect(); + + std::shared_ptr< Mysql::PreparedStatement > prepareStatement( const std::string& sql ); + + std::string getLastStatementInfo(); + + MYSQL* getRawCon(); + + private: + std::shared_ptr< MySqlBase > m_pBase; + MYSQL* m_pRawCon; + bool m_bConnected; + + Connection( const Connection& ); + void operator=( Connection& ); + }; + + +} + +#endif //SAPPHIRE_CONNECTION_H diff --git a/deps/mysqlConnector/DataType.h b/deps/mysqlConnector/DataType.h new file mode 100644 index 00000000..8738e505 --- /dev/null +++ b/deps/mysqlConnector/DataType.h @@ -0,0 +1,41 @@ +#ifndef SAPPHIRE_DATATYPE_H +#define SAPPHIRE_DATATYPE_H +namespace Mysql +{ + class DataType + { + DataType(); + + public: + enum + { + UNKNOWN = 0, + BIT, + TINYINT, + SMALLINT, + MEDIUMINT, + INTEGER, + BIGINT, + REAL, + DOUBLE, + DECIMAL, + NUMERIC, + CHAR, + BINARY, + VARCHAR, + VARBINARY, + LONGVARCHAR, + LONGVARBINARY, + TIMESTAMP, + DATE, + TIME, + YEAR, + GEOMETRY, + ENUM, + SET, + SQLNULL, + JSON + }; + }; +} +#endif //SAPPHIRE_DATATYPE_H diff --git a/deps/mysqlConnector/MySqlBase.cpp b/deps/mysqlConnector/MySqlBase.cpp new file mode 100644 index 00000000..e653c488 --- /dev/null +++ b/deps/mysqlConnector/MySqlBase.cpp @@ -0,0 +1,31 @@ +#include "MySqlBase.h" +#include "Connection.h" +#include +#include + +Mysql::MySqlBase::MySqlBase() +{ +} + +Mysql::MySqlBase::~MySqlBase() +{ + +} + +std::string Mysql::MySqlBase::getVersionInfo() +{ + return std::string( mysql_get_client_info() ); +} + +std::shared_ptr< Mysql::Connection > Mysql::MySqlBase::connect( const std::string& hostName, const std::string& userName, + const std::string& password, uint16_t port = 3306 ) +{ + return std::make_shared< Mysql::Connection >( shared_from_this(), hostName, userName, password, port ); +} + +std::shared_ptr< Mysql::Connection > Mysql::MySqlBase::connect( const std::string& hostName, const std::string& userName, + const std::string& password, const optionMap& options, + uint16_t port = 3306 ) +{ + return std::shared_ptr< Mysql::Connection >( new Mysql::Connection( shared_from_this(), hostName, userName, password, options, port ) ); +} diff --git a/deps/mysqlConnector/MySqlBase.h b/deps/mysqlConnector/MySqlBase.h new file mode 100644 index 00000000..43a2b38e --- /dev/null +++ b/deps/mysqlConnector/MySqlBase.h @@ -0,0 +1,38 @@ +#ifndef SAPPHIRE_MYSQLBASE_H +#define SAPPHIRE_MYSQLBASE_H + +#include +#include +#include +#include "MysqlCommon.h" + + +namespace Mysql +{ + +using optionMap = std::map< enum mysqlOption, std::string >; + +class Connection; + +class MySqlBase : public std::enable_shared_from_this< MySqlBase > +{ +public: + MySqlBase(); + + ~MySqlBase(); + + std::shared_ptr< Connection > connect( const std::string& hostName, const std::string& userName, + const std::string& password, uint16_t port ); + std::shared_ptr< Connection > connect( const std::string& hostName, const std::string& userName, + const std::string& password, const optionMap& map, uint16_t port ); + + std::string getVersionInfo(); + +private: + MySqlBase( const MySqlBase& ); + void operator=( MySqlBase& ); +}; + +} + +#endif //SAPPHIRE_MYSQLBASE_H diff --git a/deps/mysqlConnector/MySqlConnector.h b/deps/mysqlConnector/MySqlConnector.h new file mode 100644 index 00000000..0f65813d --- /dev/null +++ b/deps/mysqlConnector/MySqlConnector.h @@ -0,0 +1,12 @@ +#include "MySqlBase.h" +#include "Connection.h" +#include "Statement.h" +#include "PreparedStatement.h" +#include "ResultSet.h" +#include "PreparedResultSet.h" +#include + +namespace Mysql +{ + using PreparedResultSetScopedPtr = std::unique_ptr< Mysql::PreparedResultSet >; +} diff --git a/deps/mysqlConnector/MysqlCommon.h b/deps/mysqlConnector/MysqlCommon.h new file mode 100644 index 00000000..0efd8287 --- /dev/null +++ b/deps/mysqlConnector/MysqlCommon.h @@ -0,0 +1,33 @@ +#ifndef MYSQL_COMMON_H +#define MYSQL_COMMON_H + +namespace Mysql { +enum mysqlOption +{ + MYSQL_OPT_CONNECT_TIMEOUT, MYSQL_OPT_COMPRESS, MYSQL_OPT_NAMED_PIPE, + MYSQL_INIT_COMMAND, MYSQL_READ_DEFAULT_FILE, MYSQL_READ_DEFAULT_GROUP, + MYSQL_SET_CHARSET_DIR, MYSQL_SET_CHARSET_NAME, MYSQL_OPT_LOCAL_INFILE, + MYSQL_OPT_PROTOCOL, MYSQL_SHARED_MEMORY_BASE_NAME, MYSQL_OPT_READ_TIMEOUT, + MYSQL_OPT_WRITE_TIMEOUT, MYSQL_OPT_USE_RESULT, + MYSQL_OPT_USE_REMOTE_CONNECTION, MYSQL_OPT_USE_EMBEDDED_CONNECTION, + MYSQL_OPT_GUESS_CONNECTION, MYSQL_SET_CLIENT_IP, MYSQL_SECURE_AUTH, + MYSQL_REPORT_DATA_TRUNCATION, MYSQL_OPT_RECONNECT, + MYSQL_OPT_SSL_VERIFY_SERVER_CERT, MYSQL_PLUGIN_DIR, MYSQL_DEFAULT_AUTH, + MYSQL_OPT_BIND, + MYSQL_OPT_SSL_KEY, MYSQL_OPT_SSL_CERT, + MYSQL_OPT_SSL_CA, MYSQL_OPT_SSL_CAPATH, MYSQL_OPT_SSL_CIPHER, + MYSQL_OPT_SSL_CRL, MYSQL_OPT_SSL_CRLPATH, + MYSQL_OPT_CONNECT_ATTR_RESET, MYSQL_OPT_CONNECT_ATTR_ADD, + MYSQL_OPT_CONNECT_ATTR_DELETE, + MYSQL_SERVER_PUBLIC_KEY, + MYSQL_ENABLE_CLEARTEXT_PLUGIN, + MYSQL_OPT_CAN_HANDLE_EXPIRED_PASSWORDS, + MYSQL_OPT_SSL_ENFORCE, + MYSQL_OPT_MAX_ALLOWED_PACKET, MYSQL_OPT_NET_BUFFER_LENGTH, + MYSQL_OPT_TLS_VERSION, + MYSQL_OPT_SSL_MODE +}; + +} + +#endif diff --git a/deps/mysqlConnector/PreparedResultSet.cpp b/deps/mysqlConnector/PreparedResultSet.cpp new file mode 100644 index 00000000..05cd1b37 --- /dev/null +++ b/deps/mysqlConnector/PreparedResultSet.cpp @@ -0,0 +1,706 @@ +#include "PreparedResultSet.h" +#include "ResultBind.h" +#include "DataType.h" +#include +#include +#include +#include +#include +#include + +namespace +{ + static inline char * my_l_to_a(char * buf, size_t buf_size, int64_t a) + { + snprintf(buf, buf_size, "%lld", (long long) a); + return buf; + } + + static inline char * my_ul_to_a(char * buf, size_t buf_size, uint64_t a) + { + snprintf(buf, buf_size, "%llu", (unsigned long long) a); + return buf; + } + + static inline char * my_f_to_a(char * buf, size_t buf_size, double a) + { + snprintf(buf, buf_size, "%f", a); + return buf; + } +} + +uint32_t Mysql::PreparedResultSet::findColumn( const std::string &columnLabel ) const +{ + std::string searchColumn = columnLabel; + + std::transform( searchColumn.begin(), searchColumn.end(), searchColumn.begin(), + [](unsigned char c){ return std::toupper( c ); } ); + + auto iter = m_fieldNameToIndex.find( searchColumn ); + if( iter == m_fieldNameToIndex.end() ) + return 0; + + return iter->second + 1; +} + +Mysql::PreparedResultSet::PreparedResultSet( std::shared_ptr< ResultBind >& pBind, + std::shared_ptr< Mysql::PreparedStatement > par ) : + ResultSet( nullptr, par ), + m_pResultBind( pBind ), + m_pStmt( par ) +{ + pBind->bindResult(); + + m_numRows = mysql_stmt_num_rows( par->getRawStmt() ); + m_numFields = mysql_stmt_field_count( par->getRawStmt() ); + auto resMeta = mysql_stmt_result_metadata( m_pStmt->getRawStmt() ); + + for( uint32_t i = 0; i < m_numFields; ++i ) + { + + auto field = resMeta->fields[i]; + std::string fieldName( field.name ); + + std::transform( fieldName.begin(), fieldName.end(), fieldName.begin(), + []( unsigned char c ){ return std::toupper(c); } ); + + m_fieldNameToIndex[fieldName] = i; + + } + +} + +bool Mysql::PreparedResultSet::isBeforeFirstOrAfterLast() const +{ + return ( m_rowPosition == 0 ); +} + +Mysql::PreparedResultSet::~PreparedResultSet() +{ + free(); +} + +uint32_t Mysql::PreparedResultSet::getUInt( const uint32_t columnIndex ) const +{ + + if( isBeforeFirstOrAfterLast() ) + throw std::runtime_error( "PreparedResultSet::getUInt: can't fetch because not on result set" ); + + if( columnIndex == 0 || columnIndex > m_numFields ) + throw std::runtime_error( "PreparedResultSet::getUInt: invalid value of 'columnIndex'" ); + + m_lastQueriedColumn = columnIndex; + + if (*m_pResultBind->m_pBind[columnIndex - 1].is_null) { + return 0; + } + return static_cast< uint32_t >( getUInt64_intern( columnIndex, true ) ); +} + +uint16_t Mysql::PreparedResultSet::getUInt16( const uint32_t columnIndex ) const +{ + return static_cast< uint16_t >( getUInt( columnIndex) ); +} + +uint16_t Mysql::PreparedResultSet::getUInt16( const std::string& columnLabel ) const +{ + return static_cast< uint16_t >( getUInt( columnLabel) ); +} + +uint8_t Mysql::PreparedResultSet::getUInt8( const uint32_t columnIndex ) const +{ + return static_cast< uint8_t >( getUInt( columnIndex) ); +} + +uint8_t Mysql::PreparedResultSet::getUInt8( const std::string& columnLabel ) const +{ + return static_cast< uint8_t >( getUInt( columnLabel) ); +} + +int16_t Mysql::PreparedResultSet::getInt16( const uint32_t columnIndex ) const +{ + return static_cast< int16_t >( getInt( columnIndex) ); +} + +int16_t Mysql::PreparedResultSet::getInt16( const std::string& columnLabel ) const +{ + return static_cast< int16_t >( getInt( columnLabel) ); +} + +int8_t Mysql::PreparedResultSet::getInt8( const uint32_t columnIndex ) const +{ + return static_cast< int8_t >( getInt( columnIndex) ); +} + +int8_t Mysql::PreparedResultSet::getInt8( const std::string& columnLabel ) const +{ + return static_cast< int8_t >( getInt( columnLabel) ); +} + +uint32_t Mysql::PreparedResultSet::getUInt( const std::string& columnLabel ) const +{ + return getUInt( findColumn( columnLabel ) ); +} + +int64_t Mysql::PreparedResultSet::getInt64( const uint32_t columnIndex ) const +{ + + if( isBeforeFirstOrAfterLast() ) + throw std::runtime_error( "PreparedResultSet::getInt64: can't fetch because not on result set" ); + + if( columnIndex == 0 || columnIndex > m_numFields ) + throw std::runtime_error( "PreparedResultSet::getInt64: invalid value of 'columnIndex'" ); + + m_lastQueriedColumn = columnIndex; + + if( *m_pResultBind->m_pBind[columnIndex - 1].is_null ) + return 0; + + return getInt64_intern( columnIndex, true ); +} + +int64_t Mysql::PreparedResultSet::getInt64( const std::string& columnLabel ) const +{ + return getInt64( findColumn( columnLabel ) ); +} + +uint64_t Mysql::PreparedResultSet::getUInt64_intern( const uint32_t columnIndex, bool ) const +{ + + MYSQL_RES* res = mysql_stmt_result_metadata( m_pStmt->getRawStmt() ); + MYSQL_FIELD* field = mysql_fetch_field_direct( res, columnIndex - 1 ); + + switch( Mysql::Util::mysql_type_to_datatype( field ) ) + { + case DataType::REAL: + case DataType::DOUBLE: + return static_cast< uint64_t >( getDouble( columnIndex ) ); + case DataType::NUMERIC: + case DataType::DECIMAL: + case DataType::TIMESTAMP: + case DataType::DATE: + case DataType::TIME: + case DataType::CHAR: + case DataType::BINARY: + case DataType::VARCHAR: + case DataType::VARBINARY: + case DataType::LONGVARCHAR: + case DataType::LONGVARBINARY: + case DataType::SET: + case DataType::ENUM: + case DataType::JSON: + return Mysql::Util::strtoull( getString( columnIndex ).c_str(), nullptr ); + case DataType::BIT: + { + uint64_t uval = 0; + /* This length is in bytes, on the contrary to what can be seen in mysql_resultset.cpp where the Meta is used */ + switch( *m_pResultBind->m_pBind[columnIndex - 1].length ) + { + case 8:uval = (uint64_t) bit_uint8korr( m_pResultBind->m_pBind[columnIndex - 1].buffer );break; + case 7:uval = (uint64_t) bit_uint7korr( m_pResultBind->m_pBind[columnIndex - 1].buffer );break; + case 6:uval = (uint64_t) bit_uint6korr( m_pResultBind->m_pBind[columnIndex - 1].buffer );break; + case 5:uval = (uint64_t) bit_uint5korr( m_pResultBind->m_pBind[columnIndex - 1].buffer );break; + case 4:uval = (uint64_t) bit_uint4korr( m_pResultBind->m_pBind[columnIndex - 1].buffer );break; + case 3:uval = (uint64_t) bit_uint3korr( m_pResultBind->m_pBind[columnIndex - 1].buffer );break; + case 2:uval = (uint64_t) bit_uint2korr( m_pResultBind->m_pBind[columnIndex - 1].buffer );break; + case 1:uval = (uint64_t) bit_uint1korr( m_pResultBind->m_pBind[columnIndex - 1].buffer );break; + } + return uval; + } + case DataType::YEAR: + case DataType::TINYINT: + case DataType::SMALLINT: + case DataType::MEDIUMINT: + case DataType::INTEGER: + case DataType::BIGINT: + { + + uint64_t ret; + bool is_it_null = *m_pResultBind->m_pBind[columnIndex - 1].is_null != 0; + bool is_it_unsigned = m_pResultBind->m_pBind[columnIndex - 1].is_unsigned != 0; + + switch( m_pResultBind->m_pBind[columnIndex - 1].buffer_length ) + { + case 1: + if( is_it_unsigned ) + ret = !is_it_null ? *reinterpret_cast< uint8_t* >( m_pResultBind->m_pBind[columnIndex - 1].buffer ) : 0; + else + ret = !is_it_null ? *reinterpret_cast< int8_t* >( m_pResultBind->m_pBind[columnIndex - 1].buffer ) : 0; + break; + case 2: + if(is_it_unsigned) + ret = !is_it_null ? *reinterpret_cast< uint16_t* >( m_pResultBind->m_pBind[columnIndex - 1].buffer ) : 0; + else + ret = !is_it_null ? *reinterpret_cast< int16_t* >( m_pResultBind->m_pBind[columnIndex - 1].buffer ) : 0; + break; + case 4: + if( is_it_unsigned ) + ret = !is_it_null ? *reinterpret_cast< uint32_t* >( m_pResultBind->m_pBind[columnIndex - 1].buffer ) : 0; + else + ret = !is_it_null ? *reinterpret_cast< int32_t* >( m_pResultBind->m_pBind[columnIndex - 1].buffer ) : 0; + break; + case 8: + if( is_it_unsigned ) + ret = !is_it_null ? *reinterpret_cast< uint64_t* >( m_pResultBind->m_pBind[columnIndex - 1].buffer ) : 0; + else + ret = !is_it_null ? *reinterpret_cast< int64_t* >( m_pResultBind->m_pBind[columnIndex - 1].buffer ) : 0; + break; + default: + throw std::runtime_error( "PreparedResultSet::getInt64_intern: invalid type" ); + } + return ret; + } + default: + break; + } + throw std::runtime_error( "PreparedResultSet::getUInt64_intern: unhandled type. Please, report" ); + return 0; +} + +int64_t Mysql::PreparedResultSet::getInt64_intern( const uint32_t columnIndex, bool ) const +{ + + MYSQL_RES* res = mysql_stmt_result_metadata( m_pStmt->getRawStmt() ); + MYSQL_FIELD* field = mysql_fetch_field_direct( res, columnIndex - 1 ); + + switch( Mysql::Util::mysql_type_to_datatype( field ) ) + { + case DataType::REAL: + case DataType::DOUBLE: + return static_cast< int64_t >( getDouble( columnIndex ) ); + case DataType::NUMERIC: + case DataType::DECIMAL: + case DataType::TIMESTAMP: + case DataType::DATE: + case DataType::TIME: + case DataType::CHAR: + case DataType::BINARY: + case DataType::VARCHAR: + case DataType::VARBINARY: + case DataType::LONGVARCHAR: + case DataType::LONGVARBINARY: + case DataType::SET: + case DataType::ENUM: + case DataType::JSON: + return Mysql::Util::strtoll( getString( columnIndex ).c_str(), nullptr ); + case DataType::BIT: + { + int64_t uval = 0; + switch( *m_pResultBind->m_pBind[columnIndex - 1].length ) + { + case 8:uval = ( int64_t ) bit_uint8korr( m_pResultBind->m_pBind[columnIndex - 1].buffer );break; + case 7:uval = ( int64_t ) bit_uint7korr( m_pResultBind->m_pBind[columnIndex - 1].buffer );break; + case 6:uval = ( int64_t ) bit_uint6korr( m_pResultBind->m_pBind[columnIndex - 1].buffer );break; + case 5:uval = ( int64_t ) bit_uint5korr( m_pResultBind->m_pBind[columnIndex - 1].buffer );break; + case 4:uval = ( int64_t ) bit_uint4korr( m_pResultBind->m_pBind[columnIndex - 1].buffer );break; + case 3:uval = ( int64_t ) bit_uint3korr( m_pResultBind->m_pBind[columnIndex - 1].buffer );break; + case 2:uval = ( int64_t ) bit_uint2korr( m_pResultBind->m_pBind[columnIndex - 1].buffer );break; + case 1:uval = ( int64_t ) bit_uint1korr( m_pResultBind->m_pBind[columnIndex - 1].buffer );break; + } + return uval; + } + case DataType::YEAR: + case DataType::TINYINT: + case DataType::SMALLINT: + case DataType::MEDIUMINT: + case DataType::INTEGER: + case DataType::BIGINT: + { + int64_t ret; + bool is_it_null = *m_pResultBind->m_pBind[columnIndex - 1].is_null != 0; + bool is_it_unsigned = m_pResultBind->m_pBind[columnIndex - 1].is_unsigned != 0; + + switch( m_pResultBind->m_pBind[columnIndex - 1].buffer_length ) + { + case 1: + if( is_it_unsigned ) + ret = !is_it_null ? *reinterpret_cast< uint8_t* >( m_pResultBind->m_pBind[columnIndex - 1].buffer ) : 0; + else + ret = !is_it_null ? *reinterpret_cast< int8_t* >( m_pResultBind->m_pBind[columnIndex - 1].buffer ) : 0; + break; + case 2: + if( is_it_unsigned ) + ret = !is_it_null ? *reinterpret_cast< uint16_t* >( m_pResultBind->m_pBind[columnIndex - 1].buffer ) : 0; + else + ret = !is_it_null ? *reinterpret_cast< int16_t* >( m_pResultBind->m_pBind[columnIndex - 1].buffer ) : 0; + break; + case 4: + if( is_it_unsigned ) + ret = !is_it_null ? *reinterpret_cast< uint32_t* >( m_pResultBind->m_pBind[columnIndex - 1].buffer) : 0; + else + ret = !is_it_null ? *reinterpret_cast< int32_t* >( m_pResultBind->m_pBind[columnIndex - 1].buffer) : 0; + break; + case 8: + if( is_it_unsigned ) + ret = !is_it_null ? *reinterpret_cast< uint64_t* >( m_pResultBind->m_pBind[columnIndex - 1].buffer ) : 0; + else + ret = !is_it_null ? *reinterpret_cast< int64_t* >( m_pResultBind->m_pBind[columnIndex - 1].buffer ) : 0; + break; + default: + throw std::runtime_error( "PreparedResultSet::getInt64_intern: invalid type" ); + } + return ret; + } + default: + break; + + } + throw std::runtime_error( "PreparedResultSet::getInt64_intern: unhandled type. Please, report" ); + return 0; +} + +uint64_t Mysql::PreparedResultSet::getUInt64( const uint32_t columnIndex ) const +{ + + if( isBeforeFirstOrAfterLast() ) + throw std::runtime_error( "PreparedResultSet::getUInt64: can't fetch because not on result set" ); + + if( columnIndex == 0 || columnIndex > m_numFields ) + throw std::runtime_error( "PreparedResultSet::getUInt64: invalid value of 'columnIndex'" ); + + m_lastQueriedColumn = columnIndex; + + if( *m_pResultBind->m_pBind[columnIndex - 1].is_null ) + return 0; + return getUInt64_intern( columnIndex, true ); +} + +uint64_t Mysql::PreparedResultSet::getUInt64( const std::string& columnLabel ) const +{ + return getUInt64( findColumn( columnLabel ) ); +} + +std::string Mysql::PreparedResultSet::getString( const uint32_t columnIndex ) const +{ + if( isBeforeFirstOrAfterLast() ) + throw std::runtime_error( "PreparedResultSet::getString: can't fetch because not on result set" ); + + if( columnIndex == 0 || columnIndex > m_numFields ) + throw std::runtime_error( "PreparedResultSet::getString: invalid 'columnIndex'" ); + + m_lastQueriedColumn = columnIndex; + if( *m_pResultBind->m_pBind[columnIndex - 1].is_null ) + return std::string(""); + + MYSQL_RES* res = mysql_stmt_result_metadata( m_pStmt->getRawStmt() ); + MYSQL_FIELD* field = mysql_fetch_field_direct( res, columnIndex - 1 ); + + switch( Mysql::Util::mysql_type_to_datatype( field ) ) + { + case DataType::TIMESTAMP: + { + char buf[28]; + MYSQL_TIME * t = static_cast< MYSQL_TIME* >( m_pResultBind->m_pBind[columnIndex - 1].buffer ); + if( t->second_part ) + snprintf( buf, sizeof(buf) - 1, "%04d-%02d-%02d %02d:%02d:%02d.%06lu", + t->year, t->month, t->day, t->hour, t->minute, t->second, t->second_part ); + else + snprintf( buf, sizeof(buf) - 1, "%04d-%02d-%02d %02d:%02d:%02d", + t->year, t->month, t->day, t->hour, t->minute, t->second ); + + return std::string( buf ); + } + case DataType::DATE: + { + char buf[12]; + MYSQL_TIME* t = static_cast< MYSQL_TIME* >( m_pResultBind->m_pBind[columnIndex - 1].buffer ); + snprintf( buf, sizeof( buf ) - 1, "%02d-%02d-%02d", t->year, t->month, t->day ); + + return std::string( buf ); + } + case DataType::TIME: + { + char buf[18]; + MYSQL_TIME* t = static_cast< MYSQL_TIME* >( m_pResultBind->m_pBind[columnIndex - 1].buffer ); + if( t->second_part ) + snprintf( buf, sizeof( buf ), "%s%02d:%02d:%02d.%06lu", t->neg ? "-" : "", t->hour, t->minute, t->second, t->second_part ); + else + snprintf( buf, sizeof( buf ), "%s%02d:%02d:%02d", t->neg ? "-" : "", t->hour, t->minute, t->second ); + + return std::string( buf ); + } + case DataType::BIT: + case DataType::YEAR: // fetched as a SMALLINT + case DataType::TINYINT: + case DataType::SMALLINT: + case DataType::MEDIUMINT: + case DataType::INTEGER: + case DataType::BIGINT: + { + char buf[30]; + + if( m_pResultBind->m_pBind[columnIndex - 1].is_unsigned ) + my_ul_to_a( buf, sizeof( buf ) - 1, getUInt64_intern( columnIndex, false ) ); + else + my_l_to_a( buf, sizeof( buf ) - 1, getInt64_intern( columnIndex, false ) ); + + return std::string( buf ); + } + case DataType::REAL: + case DataType::DOUBLE: + { + char buf[50]; + my_f_to_a( buf, sizeof( buf ) - 1, getDouble( columnIndex ) ); + return std::string( buf ); + } + case DataType::NUMERIC: + case DataType::DECIMAL: + case DataType::CHAR: + case DataType::BINARY: + case DataType::VARCHAR: + case DataType::VARBINARY: + case DataType::LONGVARCHAR: + case DataType::LONGVARBINARY: + case DataType::SET: + case DataType::ENUM: + case DataType::JSON: + return std::string( static_cast< char* >( m_pResultBind->m_pBind[columnIndex - 1].buffer ), + *m_pResultBind->m_pBind[columnIndex - 1].length ); + default: + break; + } + + throw std::runtime_error( " PreparedResultSet::getString: unhandled type. Please, report" ); + return 0; +} + +std::string Mysql::PreparedResultSet::getString( const std::string& columnLabel) const +{ + return getString( findColumn( columnLabel ) ); +} + +int32_t Mysql::PreparedResultSet::getInt( uint32_t columnIndex ) const +{ + if( isBeforeFirstOrAfterLast() ) + throw std::runtime_error( "PreparedResultSet::getInt: can't fetch because not on result set" ); + + if( columnIndex == 0 || columnIndex > m_numFields ) + throw std::runtime_error( "PreparedResultSet::getInt: invalid value of 'columnIndex'" ); + + m_lastQueriedColumn = columnIndex; + + if( *m_pResultBind->m_pBind[columnIndex - 1].is_null ) + return 0; + + return static_cast< int32_t >( getInt64_intern( columnIndex, true ) ); +} + +int32_t Mysql::PreparedResultSet::getInt( const std::string& columnLabel ) const +{ + return getInt( findColumn( columnLabel ) ); +} + +long double Mysql::PreparedResultSet::getDouble(const uint32_t columnIndex) const +{ + if( isBeforeFirstOrAfterLast() ) + throw std::runtime_error( "PreparedResultSet::getDouble: can't fetch because not on result set" ); + + if( columnIndex == 0 || columnIndex > m_numFields ) + throw std::runtime_error( "PreparedResultSet::getDouble: invalid 'columnIndex'" ); + + + m_lastQueriedColumn = columnIndex; + + if( *m_pResultBind->m_pBind[columnIndex - 1].is_null) + return 0.0; + + MYSQL_RES* res = mysql_stmt_result_metadata( m_pStmt->getRawStmt() ); + MYSQL_FIELD* field = mysql_fetch_field_direct( res, columnIndex - 1); + + switch( Mysql::Util::mysql_type_to_datatype( field ) ) + { + case DataType::BIT: + case DataType::YEAR: + case DataType::TINYINT: + case DataType::SMALLINT: + case DataType::MEDIUMINT: + case DataType::INTEGER: + case DataType::BIGINT: + { + long double ret; + bool is_it_unsigned = m_pResultBind->m_pBind[columnIndex - 1].is_unsigned != 0; + + if( is_it_unsigned ) + { + uint64_t ival = getUInt64_intern( columnIndex, false ); + ret = static_cast< long double >( ival ); + } + else + { + int64_t ival = getInt64_intern( columnIndex, false ); + ret = static_cast< long double >( ival ); + } + return ret; + } + case DataType::NUMERIC: + case DataType::DECIMAL: + case DataType::TIMESTAMP: + case DataType::DATE: + case DataType::TIME: + case DataType::CHAR: + case DataType::BINARY: + case DataType::VARCHAR: + case DataType::VARBINARY: + case DataType::LONGVARCHAR: + case DataType::LONGVARBINARY: + case DataType::SET: + case DataType::ENUM: + case DataType::JSON: + { + long double ret = Mysql::Util::strtonum( getString( columnIndex ).c_str() ); + return ret; + } + case DataType::REAL: + { + long double ret = !*m_pResultBind->m_pBind[columnIndex - 1].is_null ? + *reinterpret_cast< float* >( m_pResultBind->m_pBind[columnIndex - 1].buffer ) : 0.; + return ret; + } + case DataType::DOUBLE: + { + long double ret = !*m_pResultBind->m_pBind[columnIndex - 1].is_null ? + *reinterpret_cast< double* >( m_pResultBind->m_pBind[columnIndex - 1].buffer ) : 0.; + return ret; + } + } + + throw std::runtime_error( "PreparedResultSet::getDouble: unhandled type. Please, report" ); + return .0; +} + +long double Mysql::PreparedResultSet::getDouble( const std::string& columnLabel ) const +{ + return getDouble( findColumn( columnLabel ) ); +} + +float Mysql::PreparedResultSet::getFloat( const uint32_t columnIndex ) const +{ + return static_cast< float >( getDouble( columnIndex ) ); +} + +float Mysql::PreparedResultSet::getFloat( const std::string& columnLabel ) const +{ + return static_cast< float >( getDouble( findColumn( columnLabel ) ) ); +} + +size_t Mysql::PreparedResultSet::getRow() const +{ + return static_cast< size_t >( m_rowPosition ); +} + +size_t Mysql::PreparedResultSet::rowsCount() const +{ + return static_cast< uint32_t >( m_numRows ); +} + +const std::shared_ptr< Mysql::Statement > Mysql::PreparedResultSet::getStatement() const +{ + return m_pStmt; +} + +std::istream* Mysql::PreparedResultSet::getBlob( const uint32_t columnIndex ) const +{ + if( isBeforeFirstOrAfterLast() ) + throw std::runtime_error( "PreparedResultSet::getBlob: can't fetch because not on result set" ); + + return new std::istringstream( getString( columnIndex ) ); +} + +std::istream* Mysql::PreparedResultSet::getBlob( const std::string& columnLabel ) const +{ + return new std::istringstream( getString( columnLabel ) ); +} + +std::vector< char > Mysql::PreparedResultSet::getBlobVector( uint32_t columnIndex ) const +{ + if( columnIndex == 0 || columnIndex > m_numFields ) + throw std::runtime_error( "PreparedResultSet::getBlobVector: invalid value of 'columnIndex'" ); + + std::unique_ptr< std::istream > inStr( getBlob( columnIndex ) ); + char buff[4196]; + std::vector< char > data; + inStr->read( buff, sizeof( buff ) ); + if( inStr->gcount() ) + { + data.resize( static_cast< uint32_t >( inStr->gcount() ) ); + memcpy( data.data(), buff, static_cast< size_t >( inStr->gcount() ) ); + } + return data; +} + +std::vector< char > Mysql::PreparedResultSet::getBlobVector( const std::string& columnLabel ) const +{ + return getBlobVector( findColumn( columnLabel ) ); +} + +bool Mysql::PreparedResultSet::getBoolean( const uint32_t columnIndex ) const +{ + if( isBeforeFirstOrAfterLast() ) + throw std::runtime_error( "PreparedResultSet::getBoolean: can't fetch because not on result set" ); + return getInt(columnIndex ) != 0; +} + +bool Mysql::PreparedResultSet::getBoolean( const std::string& columnLabel ) const +{ + if( isBeforeFirstOrAfterLast() ) + throw std::runtime_error("PreparedResultSet::getBoolean: can't fetch because not on result set"); + return getInt(columnLabel ) != 0; +} + +bool Mysql::PreparedResultSet::isLast() const +{ + return ( m_rowPosition == m_numRows ); +} + +bool Mysql::PreparedResultSet::isFirst() const +{ + return ( m_rowPosition == 1 ); +} + +bool Mysql::PreparedResultSet::isNull( const uint32_t columnIndex ) const +{ + if( columnIndex == 0 || columnIndex > m_numFields ) + throw std::runtime_error( "PreparedResultSet::isNull: invalid value of 'columnIndex'" ); + + if( isBeforeFirstOrAfterLast() ) + throw std::runtime_error( "PreparedResultSet::isNull: can't fetch because not on result set" ); + return *m_pResultBind->m_pBind[columnIndex - 1].is_null != 0; +} + +bool Mysql::PreparedResultSet::isNull( const std::string& columnLabel ) const +{ + uint32_t index = findColumn( columnLabel ); + if( index == 0 ) + throw std::runtime_error( "PreparedResultSet::isNull: invalid value of 'columnLabel'" ); + return isNull( index ); +} + +bool Mysql::PreparedResultSet::next() +{ + bool ret = false; + + // reset last_queried_column + // m_lastQueriedColumn = std::numeric_limits< uint32_t >::max(); + + int result = mysql_stmt_fetch( m_pStmt->getRawStmt() ); + if( !result || result == MYSQL_DATA_TRUNCATED ) + ret = true; + if( result == MYSQL_NO_DATA ) + ret = false; + if( result == 1 ) + throw std::runtime_error( "PreparedResultSet:next: error getting next result" ); + ++m_rowPosition; + + return ret; +} + +void Mysql::PreparedResultSet::clearWarnings() +{ + +} + +void Mysql::PreparedResultSet::free() +{ + if( m_pStmt->getRawStmt() ) + mysql_stmt_free_result( m_pStmt->getRawStmt() ); +} diff --git a/deps/mysqlConnector/PreparedResultSet.h b/deps/mysqlConnector/PreparedResultSet.h new file mode 100644 index 00000000..26b3d796 --- /dev/null +++ b/deps/mysqlConnector/PreparedResultSet.h @@ -0,0 +1,120 @@ +#ifndef SAPPHIRE_PREPAREDRESULTSET_H +#define SAPPHIRE_PREPAREDRESULTSET_H + +#include +#include "ResultSet.h" + +namespace Mysql +{ + class PreparedStatement; + class ResultBind; + + class PreparedResultSet : public ResultSet + { + private: + mutable uint32_t m_lastQueriedColumn; // this is updated by calls to getInt(int), getString(int), etc... + + uint32_t m_numFields; + uint64_t m_numRows; + uint64_t m_rowPosition; + + typedef std::map< std::string, uint32_t > FieldNameIndexMap; + + FieldNameIndexMap m_fieldNameToIndex; + + std::shared_ptr< PreparedStatement > m_pStmt; + + bool is_valid; + + std::shared_ptr< ResultBind > m_pResultBind; + + protected: + void checkValid() const; + void closeIntern(); + bool isBeforeFirstOrAfterLast() const; + void seek(); + + int64_t getInt64_intern( const uint32_t columnIndex, bool cutTooBig ) const; + uint64_t getUInt64_intern( const uint32_t columnIndex, bool cutTooBig ) const; + + public: + PreparedResultSet( std::shared_ptr< ResultBind >& pBind, std::shared_ptr< PreparedStatement > par ); + + ~PreparedResultSet() override; + + void free(); + + void clearWarnings(); + + void close(); + + uint32_t findColumn( const std::string& columnLabel ) const override; + + std::istream * getBlob( uint32_t columnIndex ) const override; + std::istream * getBlob( const std::string& columnLabel ) const override; + + std::vector< char > getBlobVector( uint32_t columnIndex ) const override; + std::vector< char > getBlobVector( const std::string& columnLabel ) const override; + + bool getBoolean( uint32_t columnIndex ) const override; + bool getBoolean( const std::string& columnLabel ) const override; + + long double getDouble( uint32_t columnIndex ) const override; + long double getDouble( const std::string& columnLabel ) const override; + + float getFloat( uint32_t columnIndex ) const override; + float getFloat( const std::string& columnLabel ) const override; + + int32_t getInt( uint32_t columnIndex ) const override; + int32_t getInt( const std::string& columnLabel ) const override; + + int16_t getInt16( uint32_t columnIndex ) const override; + int16_t getInt16( const std::string& columnLabel ) const override; + + int8_t getInt8( uint32_t columnIndex ) const; + int8_t getInt8( const std::string& columnLabel ) const; + + uint32_t getUInt( uint32_t columnIndex ) const override; + uint32_t getUInt( const std::string& columnLabel ) const override; + + uint8_t getUInt8( uint32_t columnIndex ) const; + uint8_t getUInt8( const std::string& columnLabel ) const; + + uint16_t getUInt16( uint32_t columnIndex ) const override; + uint16_t getUInt16( const std::string& columnLabel ) const override; + + int64_t getInt64( uint32_t columnIndex ) const override; + int64_t getInt64( const std::string& columnLabel ) const override; + + uint64_t getUInt64( uint32_t columnIndex ) const override; + uint64_t getUInt64( const std::string& columnLabel ) const override; + + size_t getRow() const override; + + const std::shared_ptr< Statement > getStatement() const override; + + std::string getString( uint32_t columnIndex ) const override; + std::string getString( const std::string& columnLabel ) const override; + + void getWarnings(); + + bool isClosed() const; + + void insertRow(); + + bool isFirst() const override; + + bool isLast() const override; + + bool isNull( uint32_t columnIndex ) const override; + + bool isNull( const std::string& columnLabel ) const override; + + bool next() override; + + size_t rowsCount() const; + + }; +} + +#endif //SAPPHIRE_PREPAREDRESULTSET_H diff --git a/deps/mysqlConnector/PreparedStatement.cpp b/deps/mysqlConnector/PreparedStatement.cpp new file mode 100644 index 00000000..5354fc02 --- /dev/null +++ b/deps/mysqlConnector/PreparedStatement.cpp @@ -0,0 +1,692 @@ +#include "PreparedStatement.h" +#include "PreparedResultSet.h" +#include "Connection.h" +#include "ResultBind.h" +#include +#include +#include +#include + +static const unsigned int MAX_SEND_LONGDATA_BUFFER = 1 << 18; //1<<18=256k (for istream) +static const unsigned int MAX_SEND_LONGDATA_CHUNK = 1 << 18; //1<<19=512k (for string) + +namespace Mysql +{ + +// Visitor class to send long data contained in blob_bind +struct LongDataSender +{ + unsigned position; + MYSQL_STMT* m_pStmt; + LongDataSender() + {} + + + LongDataSender( MYSQL_STMT* pStmt, unsigned int i ) : + position( i ), + m_pStmt( pStmt ) + { + } + + bool operator()( std::istream* my_blob ) const + { + if( my_blob == nullptr ) + return false; + + //char buf[MAX_SEND_LONGDATA_BUFFER]; + std::unique_ptr< char[] > buf( new char[MAX_SEND_LONGDATA_BUFFER] ); + + do + { + if( my_blob->eof() ) + break; + my_blob->read( buf.get(), MAX_SEND_LONGDATA_BUFFER ); + + if( my_blob->bad() ) + throw std::runtime_error( "Error while reading from blob (bad)" ); + else if( my_blob->fail() ) + { + if( !my_blob->eof() ) + throw std::runtime_error( "Error while reading from blob (fail)" ); + } + if( mysql_stmt_send_long_data( m_pStmt, position, buf.get(), static_cast< unsigned long >( my_blob->gcount() ) ) ) + { + switch( mysql_stmt_errno( m_pStmt ) ) + { + case CR_OUT_OF_MEMORY: + throw std::bad_alloc(); + case CR_INVALID_BUFFER_USE: + throw std::runtime_error( "PreparedStatement::setBlob: can't set blob value on that column" ); + case CR_SERVER_GONE_ERROR: + case CR_COMMANDS_OUT_OF_SYNC: + default: + throw std::runtime_error( "PreparedStatement:: Default error" ); + } + } + } while( true ); + + return true; + } + + bool operator()( std::string* str ) const + { + if ( str == nullptr ) + return false; + + uint32_t sent = 0, chunkSize; + + while( sent < str->length() ) + { + chunkSize = ( sent + MAX_SEND_LONGDATA_CHUNK > str->length() + ? str->length() - sent + : MAX_SEND_LONGDATA_CHUNK ); + + if( mysql_stmt_send_long_data( m_pStmt, position, str->c_str() + sent, chunkSize ) ) + { + + switch( mysql_stmt_errno( m_pStmt ) ) + { + case CR_OUT_OF_MEMORY: + throw std::bad_alloc(); + case CR_INVALID_BUFFER_USE: + throw std::runtime_error( "PreparedStatement::setBlob: can't set blob value on that column" ); + case CR_SERVER_GONE_ERROR: + case CR_COMMANDS_OUT_OF_SYNC: + default: + throw std::runtime_error( "PreparedStatement:: Default error" ); + } + } + + sent+= chunkSize; + } + + return true; + } +}; + + +struct BlobBindDeleter +{ + + void operator()( std::string*& str ) const + { + if( str != nullptr ) + { + delete str; + str= nullptr; + } + } + + void operator()( std::istream*& my_blob ) const + { + if( my_blob!= nullptr ) + { + delete my_blob; + my_blob= nullptr; + } + } +}; + +struct BlobIsNull +{ + + bool operator()( std::string*& str ) const + { + return str == nullptr; + } + + bool operator()( std::istream*& my_blob ) const + { + return my_blob == nullptr; + } +}; + + +void resetBlobBind( MYSQL_BIND& param ) +{ + delete [] static_cast( param.buffer ); + + param.buffer_type = MYSQL_TYPE_LONG_BLOB; + param.buffer = nullptr; + param.buffer_length = 0; + param.is_null_value = 0; + + delete param.length; + param.length = new unsigned long( 0 ); +} + + +class ParamBind +{ +public: + + typedef std::variant< std::istream*, std::string* > Blob_t; + +private: + + unsigned int m_paramCount; + std::unique_ptr< MYSQL_BIND[] > bind; + std::unique_ptr< bool[] > value_set; + std::unique_ptr< bool[] > delete_blob_after_execute; + + typedef std::map< uint32_t, Blob_t > Blobs; + + Blobs blob_bind; + +public: + + ParamBind( uint32_t paramCount ) + : m_paramCount( paramCount ), + bind( nullptr ), + value_set( nullptr ), + delete_blob_after_execute( nullptr ) + { + if( m_paramCount ) + { + bind.reset( new MYSQL_BIND[paramCount] ); + memset( bind.get(), 0, sizeof( MYSQL_BIND ) * paramCount ); + + value_set.reset( new bool[paramCount] ); + delete_blob_after_execute.reset( new bool[paramCount] ); + for( uint32_t i = 0; i < paramCount; ++i ) + { + bind[i].is_null_value = 1; + value_set[i] = false; + delete_blob_after_execute[i] = false; + } + } + } + + virtual ~ParamBind() + { + clearParameters(); + + for( Blobs::iterator it = blob_bind.begin(); it != blob_bind.end(); ++it ) + { + if( delete_blob_after_execute[it->first] ) + { + delete_blob_after_execute[it->first] = false; + std::visit( BlobBindDeleter(), it->second ); + } + } + } + + void set( uint32_t position ) + { + value_set[position] = true; + } + + void unset( uint32_t position ) + { + value_set[position] = false; + if( delete_blob_after_execute[position] ) + { + delete_blob_after_execute[position] = false; + std::visit( BlobBindDeleter(), blob_bind[position] ); + blob_bind.erase( position ); + } + } + + + void setBlob( uint32_t position, Blob_t & blob, bool delete_after_execute ) + { + set( position ); + + resetBlobBind( bind[position] ); + + Blobs::iterator it = blob_bind.find( position ); + if( it != blob_bind.end() && delete_blob_after_execute[position] ) + std::visit( BlobBindDeleter(), it->second ); + + if( std::visit( BlobIsNull(), blob ) ) + { + if( it != blob_bind.end() ) + blob_bind.erase( it ); + + delete_blob_after_execute[position] = false; + } + else + { + blob_bind[position] = blob; + delete_blob_after_execute[position] = delete_after_execute; + } + } + + + bool isAllSet() + { + for( uint32_t i = 0; i < m_paramCount; ++i ) + { + if( !value_set[i] ) + return false; + } + return true; + } + + + void clearParameters() + { + for( uint32_t i = 0; i < m_paramCount; ++i ) + { + delete ( char* ) bind[i].length; + bind[i].length = nullptr; + delete[] ( char* ) bind[i].buffer; + bind[i].buffer = nullptr; + if (value_set[i]) + { + Blobs::iterator it = blob_bind.find( i ); + if( it != blob_bind.end() && delete_blob_after_execute[i] ) + { + std::visit( BlobBindDeleter(), it->second ); + blob_bind.erase( it ); + } + blob_bind[i] = Blob_t(); + value_set[i] = false; + } + } + } + + MYSQL_BIND* getBindObject() + { + return bind.get(); + } + + + std::variant< std::istream*, std::string* > getBlobObject( uint32_t position ) + { + Blobs::iterator it = blob_bind.find( position ); + + if( it != blob_bind.end() ) + return it->second; + + return Blob_t(); + } + +}; + +} + +Mysql::PreparedStatement::PreparedStatement( MYSQL_STMT* pStmt, std::shared_ptr< Mysql::Connection > pConn ) + : Statement( pConn ) +{ + m_pStmt = pStmt; + m_pConnection = pConn; + m_paramCount = mysql_stmt_param_count( m_pStmt ); + m_pParamBind.reset( new ParamBind( m_paramCount ) ); + m_pResultBind.reset( new ResultBind( pStmt ) ); +} + +uint32_t Mysql::PreparedStatement::errNo() +{ + return mysql_stmt_errno( m_pStmt ); +} + +std::shared_ptr< Mysql::Connection > Mysql::PreparedStatement::getConnection() +{ + return m_pConnection; +} + +Mysql::PreparedStatement::~PreparedStatement() +{ + if( m_pStmt ) + closeIntern(); +} + +uint32_t Mysql::PreparedStatement::getWarningCount() +{ + return mysql_warning_count( m_pConnection->getRawCon() ); +} + +uint64_t Mysql::PreparedStatement::getUpdateCount() +{ + throw std::runtime_error( "PreparedStatement::getUpdateCount() Not implemented" ); + return 0; +} + +bool Mysql::PreparedStatement::sendLongDataBeforeParamBind() +{ + MYSQL_BIND* bind = m_pParamBind->getBindObject(); + + for( unsigned int i = 0; i < m_paramCount; ++i ) + { + if( bind[i].buffer_type == MYSQL_TYPE_LONG_BLOB ) + { + LongDataSender lv( m_pStmt, i ); + ParamBind::Blob_t dummy( m_pParamBind->getBlobObject( i ) ); + std::visit( lv, dummy ); + } + } + return true; +} + +void Mysql::PreparedStatement::doQuery() +{ + if( m_paramCount && !m_pParamBind->isAllSet() ) + throw std::runtime_error( "Value not set for all parameters" ); + + if( mysql_stmt_bind_param( m_pStmt, m_pParamBind->getBindObject() ) ) + throw std::runtime_error("Couldn't bind : " + std::to_string( errNo() ) ); + + if( !sendLongDataBeforeParamBind() || mysql_stmt_execute( m_pStmt ) ) + throw std::runtime_error( "Couldn't execute : " + std::to_string( errNo() ) + ": " + std::string( mysql_stmt_error( m_pStmt ) ) ); + + warningsCount = getWarningCount(); +} + +void Mysql::PreparedStatement::closeIntern() +{ + if( m_pStmt ) + mysql_stmt_close( m_pStmt ); + clearParameters(); +} + +void Mysql::PreparedStatement::clearParameters() +{ + m_pParamBind->clearParameters(); +} + +bool Mysql::PreparedStatement::execute() +{ + doQuery(); + return mysql_stmt_field_count( m_pStmt ) > 0; +} + +bool Mysql::PreparedStatement::execute( const std::string &sql ) +{ + throw std::runtime_error("PreparedStatement::execute( const std::string &sql ) Not implemented"); + return false; +} + +std::shared_ptr< Mysql::ResultSet > Mysql::PreparedStatement::executeQuery( const std::string &sql ) +{ + // not to be implemented for prepared statements + return nullptr; +} + +std::shared_ptr< Mysql::ResultSet > Mysql::PreparedStatement::executeQuery() +{ + doQuery(); + + my_bool bool_tmp = 1; + mysql_stmt_attr_set( m_pStmt, STMT_ATTR_UPDATE_MAX_LENGTH, &bool_tmp ); + + std::shared_ptr< ResultSet > tmp( new PreparedResultSet( m_pResultBind, std::static_pointer_cast< PreparedStatement >( shared_from_this() ) ) ); + + return tmp; +} + +std::shared_ptr< Mysql::ResultSet > Mysql::PreparedStatement::getResultSet() +{ + my_bool bool_tmp = 1; + mysql_stmt_attr_set( m_pStmt, STMT_ATTR_UPDATE_MAX_LENGTH, &bool_tmp ); + + std::shared_ptr< ResultSet > tmp( new PreparedResultSet( m_pResultBind, std::static_pointer_cast< PreparedStatement >( shared_from_this() ) ) ); + + return tmp; +} + +MYSQL_STMT* Mysql::PreparedStatement::getRawStmt() +{ + return m_pStmt; +} + +typedef std::pair< char*, size_t > BufferSizePair; + +static BufferSizePair allocate_buffer_for_type( enum_field_types t ) +{ + switch( t ) + { + case MYSQL_TYPE_LONG: + return BufferSizePair( new char[4], 4 ); + case MYSQL_TYPE_DOUBLE: + case MYSQL_TYPE_LONGLONG: + return BufferSizePair( new char[8], 8 ); + case MYSQL_TYPE_STRING: + return BufferSizePair( NULLCSTR, 0 ); + case MYSQL_TYPE_NULL: + return BufferSizePair( NULLCSTR, 0 ); + default: + throw std::runtime_error( "allocate_buffer_for_type: invalid result_bind data type" ); + } +} + +void Mysql::PreparedStatement::setInt( uint32_t parameterIndex, int32_t value ) +{ + + if( parameterIndex == 0 || parameterIndex > m_paramCount ) + throw std::runtime_error( "PreparedStatement::setInt: invalid 'parameterIndex'" ); + --parameterIndex; + + { + ParamBind::Blob_t dummy; + m_pParamBind->setBlob( parameterIndex, dummy, false ); + m_pParamBind->unset( parameterIndex ); + } + + enum_field_types t = MYSQL_TYPE_LONG; + + BufferSizePair p = allocate_buffer_for_type(t); + + m_pParamBind->set(parameterIndex); + MYSQL_BIND* param = &m_pParamBind->getBindObject()[parameterIndex]; + + param->buffer_type = t; + delete [] static_cast< char* >( param->buffer ); + param->buffer = p.first; + param->buffer_length = 0; + param->is_null_value = 0; + delete param->length; + param->length = nullptr; + + memcpy( param->buffer, &value, p.second ); + +} + +void Mysql::PreparedStatement::setUInt( uint32_t parameterIndex, uint32_t value ) +{ + if( parameterIndex == 0 || parameterIndex > m_paramCount ) + throw std::runtime_error( "PreparedStatement::setInt: invalid 'parameterIndex'" ); + --parameterIndex; + + { + ParamBind::Blob_t dummy; + m_pParamBind->setBlob( parameterIndex, dummy, false ); + m_pParamBind->unset( parameterIndex ); + } + + enum_field_types t = MYSQL_TYPE_LONG; + + BufferSizePair p = allocate_buffer_for_type(t); + + m_pParamBind->set( parameterIndex ); + MYSQL_BIND* param = &m_pParamBind->getBindObject()[parameterIndex]; + + param->buffer_type = t; + delete [] static_cast< char* >( param->buffer ); + param->buffer = p.first; + param->buffer_length = 0; + param->is_null_value = 0; + param->is_unsigned = 1; + delete param->length; + param->length = nullptr; + + memcpy( param->buffer, &value, p.second ); +} + +void Mysql::PreparedStatement::setInt64( uint32_t parameterIndex, int64_t value ) +{ + if( parameterIndex == 0 || parameterIndex > m_paramCount ) + throw std::runtime_error( "PreparedStatement::setInt64: invalid 'parameterIndex'" ); + --parameterIndex; + + { + ParamBind::Blob_t dummy; + m_pParamBind->setBlob( parameterIndex, dummy, false ); + m_pParamBind->unset( parameterIndex ); + } + + enum_field_types t = MYSQL_TYPE_LONGLONG; + + BufferSizePair p = allocate_buffer_for_type(t); + + m_pParamBind->set( parameterIndex ); + MYSQL_BIND* param = &m_pParamBind->getBindObject()[parameterIndex]; + + param->buffer_type = t; + delete [] static_cast< char* >( param->buffer ); + param->buffer = p.first; + param->buffer_length = 0; + param->is_null_value = 0; + delete param->length; + param->length = nullptr; + + memcpy( param->buffer, &value, p.second ); +} + +void Mysql::PreparedStatement::setUInt64( uint32_t parameterIndex, uint64_t value ) +{ + if( parameterIndex == 0 || parameterIndex > m_paramCount ) + throw std::runtime_error( "PreparedStatement::setInt64: invalid 'parameterIndex'" ); + --parameterIndex; + + { + ParamBind::Blob_t dummy; + m_pParamBind->setBlob( parameterIndex, dummy, false ); + m_pParamBind->unset( parameterIndex ); + } + + enum_field_types t = MYSQL_TYPE_LONGLONG; + + BufferSizePair p = allocate_buffer_for_type(t); + + m_pParamBind->set( parameterIndex ); + MYSQL_BIND* param = &m_pParamBind->getBindObject()[parameterIndex]; + + param->buffer_type = t; + delete [] static_cast< char* >( param->buffer ); + param->buffer = p.first; + param->buffer_length = 0; + param->is_null_value = 0; + param->is_unsigned = 1; + delete param->length; + param->length = nullptr; + + memcpy( param->buffer, &value, p.second ); +} + +void Mysql::PreparedStatement::setNull( uint32_t parameterIndex, int ) +{ + if( parameterIndex == 0 || parameterIndex > m_paramCount ) + throw std::runtime_error( "PreparedStatement::setNull: invalid 'parameterIndex'" ); + --parameterIndex; + + { + ParamBind::Blob_t dummy; + m_pParamBind->setBlob( parameterIndex, dummy, false ); + m_pParamBind->unset( parameterIndex ); + } + + enum_field_types t = MYSQL_TYPE_NULL; + + m_pParamBind->set( parameterIndex ); + MYSQL_BIND* param = &m_pParamBind->getBindObject()[parameterIndex]; + + param->buffer_type = t; + delete [] static_cast< char* >( param->buffer ); + param->buffer = nullptr; + delete param->length; + param->length = nullptr; +} + +void Mysql::PreparedStatement::setString( uint32_t parameterIndex, const std::string& value ) +{ + if( parameterIndex == 0 || parameterIndex > m_paramCount ) + { + throw std::runtime_error( "PreparedStatement::setString: invalid 'parameterIndex'" ); + } + if( value.length() > 256*1024 ) + { + std::string* pvalue = new std::string( value ); + ParamBind::Blob_t dummy( pvalue ); + return m_pParamBind->setBlob( --parameterIndex, dummy, true ); + } + + --parameterIndex; + + { + ParamBind::Blob_t dummy; + m_pParamBind->setBlob( parameterIndex, dummy, false ); + m_pParamBind->unset( parameterIndex ); + } + + enum_field_types t = MYSQL_TYPE_STRING; + + m_pParamBind->set( parameterIndex ); + MYSQL_BIND* param = &m_pParamBind->getBindObject()[parameterIndex]; + + delete [] static_cast< char* >( param->buffer ); + + param->buffer_type = t; + param->buffer = memcpy( new char[value.length() + 1], value.c_str(), value.length() + 1 ); + param->buffer_length = static_cast< unsigned long >( value.length() ) + 1; + param->is_null_value = 0; + + delete param->length; + param->length = new unsigned long( static_cast< unsigned long >( value.length() ) ); +} + +void Mysql::PreparedStatement::setDouble( uint32_t parameterIndex, double value ) +{ + if( parameterIndex == 0 || parameterIndex > m_paramCount ) + { + throw std::runtime_error( "PreparedStatement::setDouble: invalid 'parameterIndex'" ); + } + --parameterIndex; + + { + ParamBind::Blob_t dummy; + m_pParamBind->setBlob( parameterIndex, dummy, false ); + m_pParamBind->unset( parameterIndex ); + } + + enum_field_types t = MYSQL_TYPE_DOUBLE; + + BufferSizePair p = allocate_buffer_for_type(t); + + m_pParamBind->set( parameterIndex ); + MYSQL_BIND* param = &m_pParamBind->getBindObject()[parameterIndex]; + + param->buffer_type = t; + delete [] static_cast< char* >( param->buffer ); + param->buffer = p.first; + param->buffer_length = 0; + param->is_null_value = 0; + delete param->length; + param->length = nullptr; + + memcpy( param->buffer, &value, p.second ); +} + +void Mysql::PreparedStatement::setDateTime( uint32_t parameterIndex, const std::string& value ) +{ + setString( parameterIndex, value ); +} + +void Mysql::PreparedStatement::setBoolean( uint32_t parameterIndex, bool value ) +{ + setInt( parameterIndex, value ); +} + +void Mysql::PreparedStatement::setBigInt( uint32_t parameterIndex, const std::string& value ) +{ + setString( parameterIndex, value ); +} + +void Mysql::PreparedStatement::setBlob( uint32_t parameterIndex, std::istream* blob ) +{ + if( parameterIndex == 0 || parameterIndex > m_paramCount ) + throw std::runtime_error( "PreparedStatement::setBlob: invalid 'parameterIndex'" ); + + ParamBind::Blob_t dummy(blob); + m_pParamBind->setBlob( --parameterIndex, dummy, false ); +} diff --git a/deps/mysqlConnector/PreparedStatement.h b/deps/mysqlConnector/PreparedStatement.h new file mode 100644 index 00000000..894005c4 --- /dev/null +++ b/deps/mysqlConnector/PreparedStatement.h @@ -0,0 +1,88 @@ +#ifndef SAPPHIRE_DB_PREPAREDSTATEMENT_H +#define SAPPHIRE_DB_PREPAREDSTATEMENT_H + +#include +#include "Statement.h" + +typedef struct st_mysql_stmt MYSQL_STMT; + +namespace Mysql +{ + + class ParamBind; + class ResultBind; + + class PreparedStatement : public Statement + { + protected: + MYSQL_STMT * m_pStmt; + std::shared_ptr< Connection > m_pConnection; + std::unique_ptr< ParamBind > m_pParamBind; + uint32_t m_paramCount; + + int32_t resultSetConcurrency; + int32_t resultSetType; + + std::shared_ptr< ResultBind > m_pResultBind; + + unsigned int warningsCount; + + virtual void doQuery(); + virtual void closeIntern(); + + bool sendLongDataBeforeParamBind(); + + public: + PreparedStatement( MYSQL_STMT* pStmt, std::shared_ptr< Connection > pConn ); + virtual ~PreparedStatement(); + + std::shared_ptr< Connection > getConnection() override; + MYSQL_STMT* getRawStmt(); + + uint32_t errNo() override; + + uint32_t getWarningCount() override; + + uint64_t getUpdateCount() override; + + void clearParameters(); + + bool execute(); + bool execute( const std::string& sql ) override; + + std::shared_ptr< ResultSet > executeQuery(); + std::shared_ptr< ResultSet > executeQuery( const std::string& sql ) override; + + bool getMoreResults(); + + std::shared_ptr< ResultSet > getResultSet() override; + + void setBlob( uint32_t parameterIndex, std::istream * blob ); + + void setBoolean( uint32_t parameterIndex, bool value ); + + void setBigInt( uint32_t parameterIndex, const std::string& value ); + + void setDateTime( uint32_t parameterIndex, const std::string& value ); + + void setDouble( uint32_t parameterIndex, double value ); + + void setInt( uint32_t parameterIndex, int32_t value ); + + void setUInt( uint32_t parameterIndex, uint32_t value ); + + void setInt64( uint32_t parameterIndex, int64_t value ); + + void setUInt64( uint32_t parameterIndex, uint64_t value ); + + void setNull( uint32_t parameterIndex, int sqlType ); + + void setString( uint32_t parameterIndex, const std::string& value ); + + private: + PreparedStatement( const PreparedStatement& ); + void operator=( PreparedStatement& ); + }; +} + +#endif //SAPPHIRE_DB_PREPAREDSTATEMENT_H diff --git a/deps/mysqlConnector/ResultBind.cpp b/deps/mysqlConnector/ResultBind.cpp new file mode 100644 index 00000000..0f8f5d44 --- /dev/null +++ b/deps/mysqlConnector/ResultBind.cpp @@ -0,0 +1,146 @@ +#include "ResultBind.h" + +#include "mysql_util.h" +#include "ResultBind.h" + +#include + +namespace Mysql +{ + struct st_buffer_size_type + { + char * buffer; + size_t size; + enum_field_types type; + st_buffer_size_type( char * b, size_t s, enum_field_types t ) + : buffer( b ), + size( s ), + type( t ) + { + + } + }; + + using BufferSizePair = std::pair< char*, size_t >; + static struct st_buffer_size_type + allocate_buffer_for_field( const MYSQL_FIELD * const field ) + { + switch( field->type ) + { + case MYSQL_TYPE_NULL: + return st_buffer_size_type( nullptr, 0, field->type ); + case MYSQL_TYPE_TINY: + return st_buffer_size_type( new char[1], 1, field->type ); + case MYSQL_TYPE_SHORT: + return st_buffer_size_type( new char[2], 2, field->type ); + case MYSQL_TYPE_INT24: + case MYSQL_TYPE_LONG: + case MYSQL_TYPE_FLOAT: + return st_buffer_size_type( new char[4], 4, field->type ); + case MYSQL_TYPE_DOUBLE: + case MYSQL_TYPE_LONGLONG: + return st_buffer_size_type( new char[8], 8, field->type ); + case MYSQL_TYPE_YEAR: + return st_buffer_size_type( new char[2], 2, MYSQL_TYPE_SHORT ); + case MYSQL_TYPE_TIMESTAMP: + case MYSQL_TYPE_DATE: + case MYSQL_TYPE_TIME: + case MYSQL_TYPE_DATETIME: + return st_buffer_size_type( new char[sizeof( MYSQL_TIME )], sizeof( MYSQL_TIME ), field->type ); + case MYSQL_TYPE_TINY_BLOB: + return st_buffer_size_type( new char[256], 256, field->type ); + case MYSQL_TYPE_LONG_BLOB: + return st_buffer_size_type( new char[16777215], 16777215, field->type ); + case MYSQL_TYPE_MEDIUM_BLOB: + case MYSQL_TYPE_BLOB: + case MYSQL_TYPE_VAR_STRING: + case MYSQL_TYPE_STRING: + return st_buffer_size_type( new char[65535], 65535, field->type ); + + case MYSQL_TYPE_DECIMAL: + case MYSQL_TYPE_NEWDECIMAL: + return st_buffer_size_type( new char[64], 64, field->type ); +#if A1 + case MYSQL_TYPE_TIMESTAMP: + case MYSQL_TYPE_YEAR: + return st_buffer_size_type(new char[10], 10, field->type); +#endif +#if A0 + // There two are not sent over the wire + case MYSQL_TYPE_ENUM: + case MYSQL_TYPE_SET: +#endif + case MYSQL_TYPE_BIT: + return st_buffer_size_type( new char[8], 8, MYSQL_TYPE_BIT ); + case MYSQL_TYPE_GEOMETRY: + default: + throw std::runtime_error( "allocate_buffer_for_field: invalid rbind data type" ); + } + } + + ResultBind::ResultBind( MYSQL_STMT* pStmt ) + : m_pStmt( pStmt ), + m_numFields( 0 ), + m_isNull( nullptr ), + m_err( nullptr ), + m_len( nullptr ), + m_pBind( nullptr ) + { + } + + ResultBind::~ResultBind() + { + if( m_pBind.get() ) + { + for( uint32_t i = 0; i < m_numFields; ++i ) + delete[] ( char* ) m_pBind[i].buffer; + } + } + + void ResultBind::bindResult() + { + for( uint32_t i = 0; i < m_numFields; ++i ) + delete[] ( char* ) m_pBind[i].buffer; + + m_pBind.reset( nullptr ); + m_isNull.reset( nullptr ); + m_err.reset( nullptr ); + m_len.reset( nullptr ); + + m_numFields = mysql_stmt_field_count( m_pStmt ); + if( !m_numFields ) + return; + + m_pBind.reset( new MYSQL_BIND[m_numFields] ); + memset( m_pBind.get(), 0, sizeof( MYSQL_BIND ) * m_numFields ); + + m_isNull.reset( new my_bool[m_numFields] ); + memset( m_isNull.get(), 0, sizeof( my_bool ) * m_numFields ); + + m_err.reset( new my_bool[m_numFields] ); + memset( m_err.get(), 0, sizeof( my_bool ) * m_numFields ); + + m_len.reset( new unsigned long[m_numFields] ); + memset( m_len.get(), 0, sizeof( unsigned long ) * m_numFields ); + + + MYSQL_RES* info = mysql_stmt_result_metadata( m_pStmt ); + for( uint32_t i = 0; i < m_numFields; ++i ) + { +// MYSQL_FIELD * field = resultMeta->fetch_field(); + + MYSQL_FIELD * field = mysql_fetch_field_direct( info, i ); + struct st_buffer_size_type p = allocate_buffer_for_field(field); + m_pBind[i].buffer_type = p.type; + m_pBind[i].buffer = p.buffer; + m_pBind[i].buffer_length = static_cast< unsigned long >( p.size ); + m_pBind[i].length = &m_len[i]; + m_pBind[i].is_null = &m_isNull[i]; + m_pBind[i].error = &m_err[i]; + m_pBind[i].is_unsigned = field->flags & UNSIGNED_FLAG; + } + if( mysql_stmt_bind_result( m_pStmt, m_pBind.get() ) ) + throw std::runtime_error( "Couldn't bind : " + std::to_string( mysql_stmt_errno( m_pStmt ) ) ); + } +} + diff --git a/deps/mysqlConnector/ResultBind.h b/deps/mysqlConnector/ResultBind.h new file mode 100644 index 00000000..cc3d9a48 --- /dev/null +++ b/deps/mysqlConnector/ResultBind.h @@ -0,0 +1,33 @@ +#ifndef SAPPHIRE_RESULTBIND_H +#define SAPPHIRE_RESULTBIND_H + +#include +#include "mysql_util.h" +#include "mysql.h" +#include "PreparedStatement.h" + +namespace Mysql +{ + class ResultBind + { + uint32_t m_numFields; + std::unique_ptr< char[] > m_isNull; + std::unique_ptr< char[] > m_err; + std::unique_ptr< unsigned long[] > m_len; + MYSQL_STMT* m_pStmt; + + public: + std::unique_ptr< MYSQL_BIND[] > m_pBind; + + MYSQL_STMT* getStmt() { return m_pStmt; } + + ResultBind( MYSQL_STMT* pStmt ); + + ~ResultBind(); + + void bindResult(); + + }; +} + +#endif //SAPPHIRE_RESULTBIND_H diff --git a/deps/mysqlConnector/ResultSet.cpp b/deps/mysqlConnector/ResultSet.cpp new file mode 100644 index 00000000..95c8375d --- /dev/null +++ b/deps/mysqlConnector/ResultSet.cpp @@ -0,0 +1,329 @@ +#include "ResultSet.h" +#include "Connection.h" +#include "Statement.h" +#include "mysql_util.h" +#include +#include +#include +#include +#include +#include + +Mysql::ResultSet::ResultSet( MYSQL_RES* res, std::shared_ptr< Mysql::Statement > par ) +{ + if( !res ) + return; + m_pRes = res; + m_numRows = mysql_num_rows( res ); + m_numFields = mysql_num_fields( res ); + m_pStmt = par; + m_rowPosition = 1; + + for( uint32_t i = 0; i < m_numFields; ++i ) + { + + std::string fieldName( getFieldMeta(i + 1)->name ); + + std::transform( fieldName.begin(), fieldName.end(), fieldName.begin(), + []( unsigned char c ){ return std::toupper(c); } ); + + m_fieldNameToIndex[fieldName] = i; + } + +} + +Mysql::ResultSet::~ResultSet() +{ +} + +MYSQL_FIELD* Mysql::ResultSet::getFieldMeta( uint32_t columnIndex ) const +{ + return mysql_fetch_field_direct( m_pRes, columnIndex - 1 ); +} + +uint32_t Mysql::ResultSet::findColumn( const std::string &columnLabel ) const +{ + std::string searchColumn = columnLabel; + + std::transform( searchColumn.begin(), searchColumn.end(), searchColumn.begin(), + [](unsigned char c){ return std::toupper(c); } ); + + auto iter = m_fieldNameToIndex.find( searchColumn ); + if( iter == m_fieldNameToIndex.end() ) + return 0; + + return iter->second + 1; +} + +size_t Mysql::ResultSet::getRow() const +{ + return static_cast< size_t >( m_rowPosition ); +} + +bool Mysql::ResultSet::isLast() const +{ + return ( m_rowPosition == m_numRows ); +} + +bool Mysql::ResultSet::isFirst() const +{ + return ( m_rowPosition == 1 ); +} + +bool Mysql::ResultSet::next() +{ + bool ret = false; + + m_lastQueriedColumn = -1; + m_row = mysql_fetch_row( m_pRes ); + if( m_row == nullptr ) + { + if( m_pStmt->errNo() == 2013 || m_pStmt->errNo() == 2000 ) + throw std::runtime_error( "Error fetching next row " + std::to_string( m_pStmt->errNo() ) + + ": " + m_pStmt->getConnection()->getError() ); + } + if( ( ret = ( m_row != nullptr ) ) ) + ++m_rowPosition; + else + m_rowPosition = 0; + + return ret; +} + +size_t Mysql::ResultSet::rowsCount() const +{ + return static_cast< uint32_t >( m_numRows ); +} + +const std::shared_ptr< Mysql::Statement > Mysql::ResultSet::getStatement() const +{ + return m_pStmt; +} + +int64_t Mysql::ResultSet::getInt64( uint32_t columnIndex ) const +{ + if( columnIndex == 0 || columnIndex > m_numFields ) + throw std::runtime_error( "ResultSet::getInt64: invalid value of 'columnIndex'" ); + + m_lastQueriedColumn = columnIndex; + + if( m_row[columnIndex - 1] == nullptr ) + { + m_wasNull = true; + return 0; + } + + m_wasNull = false; + if( getFieldMeta(columnIndex)->type == MYSQL_TYPE_BIT && + getFieldMeta(columnIndex)->flags != ( BINARY_FLAG | UNSIGNED_FLAG ) ) + { + uint64_t uval = 0; + std::div_t length = std::div( getFieldMeta( columnIndex )->length, 8 ); + if( length.rem != 0 ) + ++length.quot; + + switch( length.quot ) + { + case 8:uval = (uint64_t) bit_uint8korr( m_row[columnIndex - 1] );break; + case 7:uval = (uint64_t) bit_uint7korr( m_row[columnIndex - 1] );break; + case 6:uval = (uint64_t) bit_uint6korr( m_row[columnIndex - 1] );break; + case 5:uval = (uint64_t) bit_uint5korr( m_row[columnIndex - 1] );break; + case 4:uval = (uint64_t) bit_uint4korr( m_row[columnIndex - 1] );break; + case 3:uval = (uint64_t) bit_uint3korr( m_row[columnIndex - 1] );break; + case 2:uval = (uint64_t) bit_uint2korr( m_row[columnIndex - 1] );break; + case 1:uval = (uint64_t) bit_uint1korr( m_row[columnIndex - 1] );break; + } + return uval; + } + + if( getFieldMeta( columnIndex)->flags & UNSIGNED_FLAG ) + return std::stoll( m_row[columnIndex - 1] ); + return std::stoll( m_row[columnIndex - 1] ); +} + +int64_t Mysql::ResultSet::getInt64( const std::string &columnLabel ) const +{ + return getInt64( findColumn( columnLabel ) ); +} + +uint64_t Mysql::ResultSet::getUInt64( uint32_t columnIndex ) const +{ + return static_cast< uint64_t >( getInt64( columnIndex ) ); +} + +uint64_t Mysql::ResultSet::getUInt64( const std::string &columnLabel ) const +{ + return getUInt64( findColumn( columnLabel ) ); +} + +uint16_t Mysql::ResultSet::getUInt16( uint32_t columnIndex ) const +{ + return static_cast< uint16_t >( getInt( columnIndex ) ); +} + +uint16_t Mysql::ResultSet::getUInt16( const std::string &columnLabel ) const +{ + return getUInt16( findColumn( columnLabel ) ); +} + +int16_t Mysql::ResultSet::getInt16( uint32_t columnIndex ) const +{ + return static_cast< int16_t >( getInt( columnIndex ) ); +} + +int16_t Mysql::ResultSet::getInt16( const std::string &columnLabel ) const +{ + return getUInt16( findColumn( columnLabel ) ); +} + +int32_t Mysql::ResultSet::getInt( uint32_t columnIndex ) const +{ + if( columnIndex == 0 || columnIndex > m_numFields ) + throw std::runtime_error( "ResultSet::getInt: invalid value of 'columnIndex'" ); + + if( ( getFieldMeta( columnIndex )->flags & UNSIGNED_FLAG ) != 0 ) + return static_cast< uint32_t >( getInt64( columnIndex ) ); + + return static_cast< int32_t >( getInt64( columnIndex ) ); +} + +int32_t Mysql::ResultSet::getInt( const std::string &columnLabel ) const +{ + return getInt( findColumn( columnLabel ) ); +} + +uint32_t Mysql::ResultSet::getUInt( uint32_t columnIndex ) const +{ + if( columnIndex == 0 || columnIndex > m_numFields ) + throw std::runtime_error( "ResultSet::getUInt: invalid value of 'columnIndex'" ); + + return static_cast< uint32_t >( getUInt64( columnIndex ) ); +} + +uint32_t Mysql::ResultSet::getUInt( const std::string &columnLabel ) const +{ + return getUInt( findColumn( columnLabel ) ); +} + +long double Mysql::ResultSet::getDouble( uint32_t columnIndex ) const +{ + if( columnIndex == 0 || columnIndex > m_numFields ) + throw std::runtime_error( "ResultSet::getDouble: invalid value of 'columnIndex'" ); + + m_lastQueriedColumn = columnIndex; + + if( m_row[columnIndex - 1] == nullptr ) + { + m_wasNull = true; + return 0.0; + } + m_wasNull = false; + if( getFieldMeta(columnIndex)->type == MYSQL_TYPE_BIT ) + return static_cast< long double >( getInt64( columnIndex ) ); + + return Mysql::Util::strtonum( m_row[columnIndex - 1] ); +} + +long double Mysql::ResultSet::getDouble( const std::string &columnLabel ) const +{ + return getDouble( findColumn( columnLabel ) ); +} + +float Mysql::ResultSet::getFloat( uint32_t columnIndex ) const +{ + return static_cast< float >( getDouble( columnIndex ) ); +} + +float Mysql::ResultSet::getFloat( const std::string &columnLabel ) const +{ + return getFloat( findColumn( columnLabel ) ); +} + +bool Mysql::ResultSet::getBoolean( uint32_t columnIndex ) const +{ + if( columnIndex == 0 || columnIndex > m_numFields ) + throw std::runtime_error( "ResultSet::getBoolean: invalid value of 'columnIndex'" ); + + return getInt( columnIndex ) ? true : false; +} + +bool Mysql::ResultSet::getBoolean( const std::string &columnLabel ) const +{ + return getInt( columnLabel ) ? true : false; +} + +std::string Mysql::ResultSet::getString( uint32_t columnIndex ) const +{ + if( columnIndex == 0 || columnIndex > m_numFields ) + throw std::runtime_error( "ResultSet::getString: invalid value of 'columnIndex'" ); + + if( m_row == nullptr || m_row[columnIndex - 1] == nullptr ) + { + m_wasNull = true; + return ""; + } + + if( getFieldMeta( columnIndex )->type == MYSQL_TYPE_BIT ) + { + char buf[30]; + snprintf( buf, sizeof( buf ) - 1, "%llu", ( unsigned long long ) getUInt64( columnIndex ) ); + return std::string( buf ); + } + + size_t len = mysql_fetch_lengths( m_pRes )[columnIndex - 1]; + m_wasNull = false; + return std::string( m_row[columnIndex - 1], len ); +} + +std::string Mysql::ResultSet::getString( const std::string &columnLabel ) const +{ + return getString( findColumn( columnLabel ) ); +} + +bool Mysql::ResultSet::isNull( uint32_t columnIndex ) const +{ + if( columnIndex == 0 || columnIndex > m_numFields ) + throw std::runtime_error( "ResultSet::isNull: invalid value of 'columnIndex'" ); + + return ( m_row[columnIndex - 1] == nullptr ); +} + +bool Mysql::ResultSet::isNull( const std::string &columnLabel ) const +{ + return isNull( findColumn( columnLabel ) ); +} + +std::istream* Mysql::ResultSet::getBlob( uint32_t columnIndex ) const +{ + if( columnIndex == 0 || columnIndex > m_numFields ) + throw std::runtime_error( "ResultSet::getBlob: invalid value of 'columnIndex'" ); + + return new std::istringstream( getString( columnIndex ) ); +} + +std::istream* Mysql::ResultSet::getBlob( const std::string& columnLabel ) const +{ + return new std::istringstream( getString( columnLabel ) ); +} + +std::vector< char > Mysql::ResultSet::getBlobVector( uint32_t columnIndex ) const +{ + if( columnIndex == 0 || columnIndex > m_numFields ) + throw std::runtime_error( "ResultSet::getBlobVector: invalid value of 'columnIndex'" ); + + std::unique_ptr< std::istream > inStr( getBlob( columnIndex ) ); + char buff[4196]; + std::vector< char > data; + inStr->read( buff, sizeof( buff ) ); + if( inStr->gcount() ) + { + data.resize( static_cast< const uint32_t >( inStr->gcount() ) ); + memcpy(data.data(), buff, static_cast< size_t >( inStr->gcount() ) ); + } + return data; +} + +std::vector< char > Mysql::ResultSet::getBlobVector( const std::string& columnLabel ) const +{ + return getBlobVector( findColumn( columnLabel ) ); +} diff --git a/deps/mysqlConnector/ResultSet.h b/deps/mysqlConnector/ResultSet.h new file mode 100644 index 00000000..b68eda61 --- /dev/null +++ b/deps/mysqlConnector/ResultSet.h @@ -0,0 +1,107 @@ +#ifndef SAPPHIRE_RESULTSET_H +#define SAPPHIRE_RESULTSET_H + +#include + +#include "ResultSetBase.h" +#include + +typedef char **MYSQL_ROW; +typedef struct st_mysql_res MYSQL_RES; +typedef struct st_mysql_field MYSQL_FIELD; + +namespace Mysql +{ + class Statement; + + class ResultSet + { + MYSQL_ROW m_row; + + uint32_t m_numFields; + uint64_t m_numRows; + uint64_t m_rowPosition; + + using FieldNameIndexMap = std::map< std::string, uint32_t >; + + FieldNameIndexMap m_fieldNameToIndex; + + mutable bool m_wasNull; + + mutable uint32_t m_lastQueriedColumn; + + std::shared_ptr< Statement > m_pStmt; + + MYSQL_RES* m_pRes; + + protected: + MYSQL_FIELD* getFieldMeta( unsigned int columnIndex ) const; + + public: + ResultSet( MYSQL_RES* res, std::shared_ptr< Statement > par ); + + virtual ~ResultSet(); + + virtual uint32_t findColumn( const std::string& columnLabel ) const; + + virtual std::istream * getBlob( uint32_t columnIndex ) const; + virtual std::istream * getBlob( const std::string& columnLabel ) const; + + virtual std::vector< char > getBlobVector( uint32_t columnIndex ) const; + virtual std::vector< char > getBlobVector( const std::string& columnLabel ) const; + + virtual bool getBoolean( uint32_t columnIndex ) const; + virtual bool getBoolean( const std::string& columnLabel ) const; + + virtual long double getDouble( uint32_t columnIndex ) const; + virtual long double getDouble( const std::string& columnLabel ) const; + + virtual float getFloat( uint32_t columnIndex ) const; + virtual float getFloat( const std::string& columnLabel ) const; + + virtual int32_t getInt( uint32_t columnIndex ) const; + virtual int32_t getInt( const std::string& columnLabel ) const; + + virtual uint32_t getUInt( uint32_t columnIndex ) const; + virtual uint32_t getUInt( const std::string& columnLabel ) const; + + virtual int16_t getInt16( uint32_t columnIndex ) const; + virtual int16_t getInt16( const std::string& columnLabel ) const; + + virtual uint16_t getUInt16( uint32_t columnIndex ) const; + virtual uint16_t getUInt16( const std::string& columnLabel ) const; + + virtual int64_t getInt64( uint32_t columnIndex ) const; + virtual int64_t getInt64( const std::string& columnLabel ) const; + + virtual uint64_t getUInt64( uint32_t columnIndex ) const; + virtual uint64_t getUInt64( const std::string& columnLabel ) const; + + //sql::ResultSetMetaData * getMetaData() const; + + virtual size_t getRow() const; + + virtual const std::shared_ptr< Statement > getStatement() const; + + virtual std::string getString( uint32_t columnIndex ) const; + virtual std::string getString( const std::string& columnLabel ) const; + + virtual bool isFirst() const; + + virtual bool isLast() const; + + virtual bool isNull( uint32_t columnIndex ) const; + + virtual bool isNull( const std::string& columnLabel ) const; + + virtual bool next(); + + size_t rowsCount() const; + private: + ResultSet( const ResultSet& ); + void operator=( ResultSet& ); + }; +} + + +#endif //SAPPHIRE_RESULTSET_H diff --git a/deps/mysqlConnector/ResultSetBase.h b/deps/mysqlConnector/ResultSetBase.h new file mode 100644 index 00000000..235d8511 --- /dev/null +++ b/deps/mysqlConnector/ResultSetBase.h @@ -0,0 +1,65 @@ +#ifndef SAPPHIRE_RESULTSETBASE_H +#define SAPPHIRE_RESULTSETBASE_H + +#include +#include +#include + +namespace Mysql +{ + + class Statement; + + class ResultSetBase + { + public: + + virtual ~ResultSetBase() {} + + virtual uint32_t findColumn( const std::string &columnLabel ) const = 0; + + virtual std::istream *getBlob( uint32_t columnIndex ) const = 0; + virtual std::istream *getBlob( const std::string &columnLabel ) const = 0; + + virtual bool getBoolean( uint32_t columnIndex ) const = 0; + virtual bool getBoolean( const std::string &columnLabel ) const = 0; + + virtual long double getDouble( uint32_t columnIndex ) const = 0; + virtual long double getDouble( const std::string &columnLabel ) const = 0; + + virtual int32_t getInt( uint32_t columnIndex ) const = 0; + virtual int32_t getInt( const std::string &columnLabel ) const = 0; + + virtual uint32_t getUInt( uint32_t columnIndex ) const = 0; + virtual uint32_t getUInt( const std::string &columnLabel ) const = 0; + + virtual int64_t getInt64( uint32_t columnIndex ) const = 0; + virtual int64_t getInt64( const std::string &columnLabel ) const = 0; + + virtual uint64_t getUInt64( uint32_t columnIndex ) const = 0; + virtual uint64_t getUInt64( const std::string &columnLabel ) const = 0; + + //virtual ResultSetMetaData *getMetaData() const = 0; + + virtual size_t getRow() const = 0; + + virtual const Statement *getStatement() const = 0; + + virtual std::string getString( uint32_t columnIndex ) const = 0; + virtual std::string getString( const std::string &columnLabel ) const = 0; + + virtual bool isFirst() const = 0; + + virtual bool isLast() const = 0; + + virtual bool isNull( uint32_t columnIndex ) const = 0; + + virtual bool isNull( const std::string &columnLabel ) const = 0; + + virtual bool next() = 0; + + virtual size_t rowsCount() const = 0; + }; +} + +#endif //SAPPHIRE_RESULTSETBASE_H diff --git a/deps/mysqlConnector/Statement.cpp b/deps/mysqlConnector/Statement.cpp new file mode 100644 index 00000000..543e5b1b --- /dev/null +++ b/deps/mysqlConnector/Statement.cpp @@ -0,0 +1,67 @@ +#include "Statement.h" +#include "Connection.h" +#include "ResultSet.h" +#include "mysql_util.h" +#include + +std::shared_ptr< Mysql::Connection > Mysql::Statement::getConnection() +{ + return m_pConnection; +} + +Mysql::Statement::Statement( std::shared_ptr< Mysql::Connection > conn ) : + m_pConnection( conn ) +{ + +} + +void Mysql::Statement::doQuery( const std::string &q ) +{ + mysql_real_query( m_pConnection->getRawCon(), q.c_str(), q.length() ); + + if( errNo() ) + throw std::runtime_error( m_pConnection->getError() ); + + m_warningsCount = getWarningCount(); +} + +bool Mysql::Statement::execute( const std::string &sql ) +{ + doQuery( sql ); + bool ret = mysql_field_count( m_pConnection->getRawCon() ) == 0; + m_lastUpdateCount = mysql_affected_rows( m_pConnection->getRawCon() ); + return ret; +} + +uint64_t Mysql::Statement::getUpdateCount() +{ + return m_lastUpdateCount; +} + +uint32_t Mysql::Statement::getWarningCount() +{ + return mysql_warning_count( m_pConnection->getRawCon() ); +} + +uint32_t Mysql::Statement::errNo() +{ + return mysql_errno( m_pConnection->getRawCon() ); +} + +std::shared_ptr< Mysql::ResultSet > Mysql::Statement::executeQuery( const std::string &sql ) +{ + m_lastUpdateCount = UL64(~0); + doQuery( sql ); + + return std::make_shared< ResultSet >( mysql_store_result( m_pConnection->getRawCon() ), shared_from_this() ); +} + +std::shared_ptr< Mysql::ResultSet > Mysql::Statement::getResultSet() +{ + if( errNo() != 0 ) + throw std::runtime_error( "Error during getResultSet() : " + std::to_string( errNo() ) + ": " + + m_pConnection->getError() ); + + return std::make_shared< ResultSet >( mysql_store_result( m_pConnection->getRawCon() ), shared_from_this() ); +} + diff --git a/deps/mysqlConnector/Statement.h b/deps/mysqlConnector/Statement.h new file mode 100644 index 00000000..06405f0f --- /dev/null +++ b/deps/mysqlConnector/Statement.h @@ -0,0 +1,50 @@ +#ifndef SAPPHIRE_STATEMENT_H +#define SAPPHIRE_STATEMENT_H + +#include +#include +#include + +namespace Mysql +{ + class Connection; + class ResultSet; + + class Statement : public std::enable_shared_from_this< Statement > + { + protected: + std::shared_ptr< Connection > m_pConnection; + + void doQuery( const std::string& q ); + + uint64_t m_lastUpdateCount; + + unsigned int m_warningsCount; + + public: + Statement( std::shared_ptr< Connection > conn ); + + virtual ~Statement() {}; + + virtual std::shared_ptr< Connection > getConnection(); + + virtual bool execute( const std::string& sql ); + + virtual std::shared_ptr< ResultSet > executeQuery( const std::string& sql ); + + virtual std::shared_ptr< ResultSet > getResultSet(); + + virtual uint64_t getUpdateCount(); + + virtual uint32_t getWarningCount(); + + virtual uint32_t errNo(); + + private: + /* Prevent use of these */ + Statement( const Statement& ); + void operator=( Statement& ); + }; + +} +#endif //SAPPHIRE_STATEMENT_H diff --git a/deps/mysqlConnector/StatementBase.h b/deps/mysqlConnector/StatementBase.h new file mode 100644 index 00000000..201d5c83 --- /dev/null +++ b/deps/mysqlConnector/StatementBase.h @@ -0,0 +1,32 @@ +#ifndef SAPPHIRE_STATEMENTBASE_H +#define SAPPHIRE_STATEMENTBASE_H + +#include + +namespace Mysql +{ + class Connection; + class ResultSet; + + class StatementBase + { + public: + virtual ~StatementBase() {}; + + virtual Connection* getConnection() = 0; + + virtual bool execute( const std::string& sql ) = 0; + + virtual ResultSet* executeQuery( const std::string& sql ) = 0; + + virtual ResultSet* getResultSet() = 0; + + virtual uint64_t getUpdateCount() = 0; + + virtual uint32_t getWarningCount() = 0; + + virtual uint32_t errNo() = 0; + }; +} + +#endif //SAPPHIRE_STATEMENTBASE_H diff --git a/deps/mysqlConnector/mysql_util.cpp b/deps/mysqlConnector/mysql_util.cpp new file mode 100644 index 00000000..40277d0d --- /dev/null +++ b/deps/mysqlConnector/mysql_util.cpp @@ -0,0 +1,457 @@ +#include "mysql_util.h" +#include "DataType.h" +#include +#include +#include + +long double Mysql::Util::strtold(const char *nptr, char **endptr) +{ + /* + * Experienced odd compilation errors on one of windows build hosts - + * cmake reported there is strold function. Since double and long double on windows + * are of the same size - we are using strtod on those platforms regardless + * to the HAVE_FUNCTION_STRTOLD value + */ +#ifdef _WIN32 + return ::strtod(nptr, endptr); +#else + # ifndef HAVE_FUNCTION_STRTOLD + // on linux, strtod would return a value that is 1 less than the input string + // for reasons when the number is in the 64bit range + return std::strtoll( nptr, endptr, 10 ); + + //return ::strtod(nptr, endptr); +# else +# if defined(__hpux) && defined(_LONG_DOUBLE) + union { + long_double l_d; + long double ld; + } u; + u.l_d = ::strtold( nptr, endptr); + return u.ld; +# else + return ::strtold(nptr, endptr); +# endif +# endif +#endif + +} + +int64_t Mysql::Util::strtoll( const char* nptr, char** endptr ) +{ + return static_cast< int64_t >( strtold( nptr, endptr ) ); +} + + +uint64_t Mysql::Util::strtoull( const char* nptr, char** endptr ) +{ + return static_cast< uint64_t >( strtold( nptr, endptr ) ); +} + +long double Mysql::Util::strtonum( const std::string &str, int radix ) +{ + using iter_t = std::istreambuf_iterator< char >; + static std::locale c_locale( "C" ); + static const std::num_get< char > &cvt = std::use_facet< std::num_get< char > >( c_locale ); + + std::istringstream inp( str ); + long double val = 0.0L; + + inp.imbue( c_locale ); + + switch( radix ) + { + case 10: inp.setf( std::ios_base::dec, std::ios_base::basefield ); break; + case 16: inp.setf( std::ios_base::hex, std::ios_base::basefield ); break; + case 8: inp.setf( std::ios_base::oct, std::ios_base::basefield ); break; + default: + inp.setf( std::ios_base::fmtflags( 0 ), std::ios_base::basefield ); + break; + } + + iter_t beg( inp ), end; + std::ios::iostate err = std::ios_base::goodbit; + + cvt.get( beg, end, inp, err, val ); + + return val; +} + +#define cppconn_mbcharlen_big5 NULL +#define check_mb_big5 NULL +#define cppconn_mbcharlen_ujis NULL +#define check_mb_ujis NULL +#define cppconn_mbcharlen_sjis NULL +#define check_mb_sjis NULL +#define cppconn_mbcharlen_euckr NULL +#define check_mb_euckr NULL +#define cppconn_mbcharlen_gb2312 NULL +#define check_mb_gb2312 NULL +#define cppconn_mbcharlen_gbk NULL +#define check_mb_gbk NULL +#define cppconn_mbcharlen_utf8 NULL +#define check_mb_utf8_valid NULL +#define cppconn_mbcharlen_ucs2 NULL +#define check_mb_ucs2 NULL +#define cppconn_mbcharlen_cp932 NULL +#define check_mb_cp932 NULL +#define cppconn_mbcharlen_eucjpms NULL +#define check_mb_eucjpms NULL +#define cppconn_mbcharlen_utf8 NULL +#define check_mb_utf8_valid NULL +#define cppconn_mbcharlen_utf8mb4 cppconn_mbcharlen_utf8 +#define check_mb_utf8mb4_valid check_mb_utf8_valid +#define cppconn_mbcharlen_utf16 NULL +#define check_mb_utf16_valid NULL +#define cppconn_mbcharlen_utf32 NULL +#define check_mb_utf32_valid NULL + +/* {{{ our_charsets60 */ +const Mysql::Util::OUR_CHARSET our_charsets60[] = + { + { 1, "big5","big5_chinese_ci", 1, 2, "", cppconn_mbcharlen_big5, check_mb_big5}, + { 3, "dec8", "dec8_swedisch_ci", 1, 1, "", NULL, NULL}, + { 4, "cp850", "cp850_general_ci", 1, 1, "", NULL, NULL}, + { 6, "hp8", "hp8_english_ci", 1, 1, "", NULL, NULL}, + { 7, "koi8r", "koi8r_general_ci", 1, 1, "", NULL, NULL}, + { 8, "latin1", "latin1_swedish_ci", 1, 1, "", NULL, NULL}, + { 9, "latin2", "latin2_general_ci", 1, 1, "", NULL, NULL}, + { 10, "swe7", "swe7_swedish_ci", 1, 1, "", NULL, NULL}, + { 11, "ascii", "ascii_general_ci", 1, 1, "", NULL, NULL}, + { 12, "ujis", "ujis_japanese_ci", 1, 3, "", cppconn_mbcharlen_ujis, check_mb_ujis}, + { 13, "sjis", "sjis_japanese_ci", 1, 2, "", cppconn_mbcharlen_sjis, check_mb_sjis}, + { 16, "hebrew", "hebrew_general_ci", 1, 1, "", NULL, NULL}, + { 18, "tis620", "tis620_thai_ci", 1, 1, "", NULL, NULL}, + { 19, "euckr", "euckr_korean_ci", 1, 2, "", cppconn_mbcharlen_euckr, check_mb_euckr}, + { 22, "koi8u", "koi8u_general_ci", 1, 1, "", NULL, NULL}, + { 24, "gb2312", "gb2312_chinese_ci", 1, 2, "", cppconn_mbcharlen_gb2312, check_mb_gb2312}, + { 25, "greek", "greek_general_ci", 1, 1, "", NULL, NULL}, + { 26, "cp1250", "cp1250_general_ci", 1, 1, "", NULL, NULL}, + { 28, "gbk", "gbk_chinese_ci", 1, 2, "", cppconn_mbcharlen_gbk, check_mb_gbk}, + { 30, "latin5", "latin5_turkish_ci", 1, 1, "", NULL, NULL}, + { 32, "armscii8", "armscii8_general_ci", 1, 1, "", NULL, NULL}, + { 33, "utf8", "utf8_general_ci", 1, 3, "UTF-8 Unicode", cppconn_mbcharlen_utf8, check_mb_utf8_valid}, + { 35, "ucs2", "ucs2_general_ci", 2, 2, "UCS-2 Unicode", cppconn_mbcharlen_ucs2, check_mb_ucs2}, + { 36, "cp866", "cp866_general_ci", 1, 1, "", NULL, NULL}, + { 37, "keybcs2", "keybcs2_general_ci", 1, 1, "", NULL, NULL}, + { 38, "macce", "macce_general_ci", 1, 1, "", NULL, NULL}, + { 39, "macroman", "macroman_general_ci", 1, 1, "", NULL, NULL}, + { 40, "cp852", "cp852_general_ci", 1, 1, "", NULL, NULL}, + { 41, "latin7", "latin7_general_ci", 1, 1, "", NULL, NULL}, + { 51, "cp1251", "cp1251_general_ci", 1, 1, "", NULL, NULL}, + { 57, "cp1256", "cp1256_general_ci", 1, 1, "", NULL, NULL}, + { 59, "cp1257", "cp1257_general_ci", 1, 1, "", NULL, NULL}, + { 63, "binary", "binary", 1, 1, "", NULL, NULL}, + { 92, "geostd8", "geostd8_general_ci", 1, 1, "", NULL, NULL}, + { 95, "cp932", "cp932_japanese_ci", 1, 2, "", cppconn_mbcharlen_cp932, check_mb_cp932}, + { 97, "eucjpms", "eucjpms_japanese_ci", 1, 3, "", cppconn_mbcharlen_eucjpms, check_mb_eucjpms}, + { 2, "latin2", "latin2_czech_cs", 1, 1, "", NULL, NULL}, + { 5, "latin1", "latin1_german_ci", 1, 1, "", NULL, NULL}, + { 14, "cp1251", "cp1251_bulgarian_ci", 1, 1, "", NULL, NULL}, + { 15, "latin1", "latin1_danish_ci", 1, 1, "", NULL, NULL}, + { 17, "filename", "filename", 1, 5, "", NULL, NULL}, + { 20, "latin7", "latin7_estonian_cs", 1, 1, "", NULL, NULL}, + { 21, "latin2", "latin2_hungarian_ci", 1, 1, "", NULL, NULL}, + { 23, "cp1251", "cp1251_ukrainian_ci", 1, 1, "", NULL, NULL}, + { 27, "latin2", "latin2_croatian_ci", 1, 1, "", NULL, NULL}, + { 29, "cp1257", "cp1257_lithunian_ci", 1, 1, "", NULL, NULL}, + { 31, "latin1", "latin1_german2_ci", 1, 1, "", NULL, NULL}, + { 34, "cp1250", "cp1250_czech_cs", 1, 1, "", NULL, NULL}, + { 42, "latin7", "latin7_general_cs", 1, 1, "", NULL, NULL}, + { 43, "macce", "macce_bin", 1, 1, "", NULL, NULL}, + { 44, "cp1250", "cp1250_croatian_ci", 1, 1, "", NULL, NULL}, + { 47, "latin1", "latin1_bin", 1, 1, "", NULL, NULL}, + { 48, "latin1", "latin1_general_ci", 1, 1, "", NULL, NULL}, + { 49, "latin1", "latin1_general_cs", 1, 1, "", NULL, NULL}, + { 50, "cp1251", "cp1251_bin", 1, 1, "", NULL, NULL}, + { 52, "cp1251", "cp1251_general_cs", 1, 1, "", NULL, NULL}, + { 53, "macroman", "macroman_bin", 1, 1, "", NULL, NULL}, + { 58, "cp1257", "cp1257_bin", 1, 1, "", NULL, NULL}, + { 60, "armascii8", "armascii8_bin", 1, 1, "", NULL, NULL}, + { 65, "ascii", "ascii_bin", 1, 1, "", NULL, NULL}, + { 66, "cp1250", "cp1250_bin", 1, 1, "", NULL, NULL}, + { 67, "cp1256", "cp1256_bin", 1, 1, "", NULL, NULL}, + { 68, "cp866", "cp866_bin", 1, 1, "", NULL, NULL}, + { 69, "dec8", "dec8_bin", 1, 1, "", NULL, NULL}, + { 70, "greek", "greek_bin", 1, 1, "", NULL, NULL}, + { 71, "hebrew", "hebrew_bin", 1, 1, "", NULL, NULL}, + { 72, "hp8", "hp8_bin", 1, 1, "", NULL, NULL}, + { 73, "keybcs2", "keybcs2_bin", 1, 1, "", NULL, NULL}, + { 74, "koi8r", "koi8r_bin", 1, 1, "", NULL, NULL}, + { 75, "koi8u", "koi8u_bin", 1, 1, "", NULL, NULL}, + { 77, "latin2", "latin2_bin", 1, 1, "", NULL, NULL}, + { 78, "latin5", "latin5_bin", 1, 1, "", NULL, NULL}, + { 79, "latin7", "latin7_bin", 1, 1, "", NULL, NULL}, + { 80, "cp850", "cp850_bin", 1, 1, "", NULL, NULL}, + { 81, "cp852", "cp852_bin", 1, 1, "", NULL, NULL}, + { 82, "swe7", "swe7_bin", 1, 1, "", NULL, NULL}, + { 93, "geostd8", "geostd8_bin", 1, 1, "", NULL, NULL}, + { 83, "utf8", "utf8_bin", 1, 3, "UTF-8 Unicode", cppconn_mbcharlen_utf8, check_mb_utf8_valid}, + { 84, "big5", "big5_bin", 1, 2, "", cppconn_mbcharlen_big5, check_mb_big5}, + { 85, "euckr", "euckr_bin", 1, 2, "", cppconn_mbcharlen_euckr, check_mb_euckr}, + { 86, "gb2312", "gb2312_bin", 1, 2, "", cppconn_mbcharlen_gb2312, check_mb_gb2312}, + { 87, "gbk", "gbk_bin", 1, 2, "", cppconn_mbcharlen_gbk, check_mb_gbk}, + { 88, "sjis", "sjis_bin", 1, 2, "", cppconn_mbcharlen_sjis, check_mb_sjis}, + { 89, "tis620", "tis620_bin", 1, 1, "", NULL, NULL}, + { 90, "ucs2", "ucs2_bin", 2, 2, "UCS-2 Unicode", cppconn_mbcharlen_ucs2, check_mb_ucs2}, + { 91, "ujis", "ujis_bin", 1, 3, "", cppconn_mbcharlen_ujis, check_mb_ujis}, + { 94, "latin1", "latin1_spanish_ci", 1, 1, "", NULL, NULL}, + { 96, "cp932", "cp932_bin", 1, 2, "", cppconn_mbcharlen_cp932, check_mb_cp932}, + { 99, "cp1250", "cp1250_polish_ci", 1, 1, "", NULL, NULL}, + { 98, "eucjpms", "eucjpms_bin", 1, 3, "", cppconn_mbcharlen_eucjpms, check_mb_eucjpms}, + { 128, "ucs2", "ucs2_unicode_ci", 2, 2, "", cppconn_mbcharlen_ucs2, check_mb_ucs2}, + { 129, "ucs2", "ucs2_icelandic_ci", 2, 2, "", cppconn_mbcharlen_ucs2, check_mb_ucs2}, + { 130, "ucs2", "ucs2_latvian_ci", 2, 2, "", cppconn_mbcharlen_ucs2, check_mb_ucs2}, + { 131, "ucs2", "ucs2_romanian_ci", 2, 2, "", cppconn_mbcharlen_ucs2, check_mb_ucs2}, + { 132, "ucs2", "ucs2_slovenian_ci", 2, 2, "", cppconn_mbcharlen_ucs2, check_mb_ucs2}, + { 133, "ucs2", "ucs2_polish_ci", 2, 2, "", cppconn_mbcharlen_ucs2, check_mb_ucs2}, + { 134, "ucs2", "ucs2_estonian_ci", 2, 2, "", cppconn_mbcharlen_ucs2, check_mb_ucs2}, + { 135, "ucs2", "ucs2_spanish_ci", 2, 2, "", cppconn_mbcharlen_ucs2, check_mb_ucs2}, + { 136, "ucs2", "ucs2_swedish_ci", 2, 2, "", cppconn_mbcharlen_ucs2, check_mb_ucs2}, + { 137, "ucs2", "ucs2_turkish_ci", 2, 2, "", cppconn_mbcharlen_ucs2, check_mb_ucs2}, + { 138, "ucs2", "ucs2_czech_ci", 2, 2, "", cppconn_mbcharlen_ucs2, check_mb_ucs2}, + { 139, "ucs2", "ucs2_danish_ci", 2, 2, "", cppconn_mbcharlen_ucs2, check_mb_ucs2}, + { 140, "ucs2", "ucs2_lithunian_ci", 2, 2, "", cppconn_mbcharlen_ucs2, check_mb_ucs2}, + { 141, "ucs2", "ucs2_slovak_ci", 2, 2, "", cppconn_mbcharlen_ucs2, check_mb_ucs2}, + { 142, "ucs2", "ucs2_spanish2_ci", 2, 2, "", cppconn_mbcharlen_ucs2, check_mb_ucs2}, + { 143, "ucs2", "ucs2_roman_ci", 2, 2, "", cppconn_mbcharlen_ucs2, check_mb_ucs2}, + { 144, "ucs2", "ucs2_persian_ci", 2, 2, "", cppconn_mbcharlen_ucs2, check_mb_ucs2}, + { 145, "ucs2", "ucs2_esperanto_ci", 2, 2, "", cppconn_mbcharlen_ucs2, check_mb_ucs2}, + { 146, "ucs2", "ucs2_hungarian_ci", 2, 2, "", cppconn_mbcharlen_ucs2, check_mb_ucs2}, + { 147, "ucs2", "ucs2_sinhala_ci", 2, 2, "", cppconn_mbcharlen_ucs2, check_mb_ucs2}, + { 148, "ucs2", "ucs2_german2_ci", 2, 2, "", cppconn_mbcharlen_ucs2, check_mb_ucs2}, + { 149, "ucs2", "ucs2_croatian_ci", 2, 2, "", cppconn_mbcharlen_ucs2, check_mb_ucs2}, + { 150, "ucs2", "ucs2_unicode_520_ci", 2, 2, "", cppconn_mbcharlen_ucs2, check_mb_ucs2}, + { 192, "utf8", "utf8_unicode_ci", 1, 3, "", cppconn_mbcharlen_utf8, check_mb_utf8_valid}, + { 193, "utf8", "utf8_icelandic_ci", 1, 3, "", cppconn_mbcharlen_utf8, check_mb_utf8_valid}, + { 194, "utf8", "utf8_latvian_ci", 1, 3, "", cppconn_mbcharlen_utf8, check_mb_utf8_valid}, + { 195, "utf8", "utf8_romanian_ci", 1, 3, "", cppconn_mbcharlen_utf8, check_mb_utf8_valid}, + { 196, "utf8", "utf8_slovenian_ci", 1, 3, "", cppconn_mbcharlen_utf8, check_mb_utf8_valid}, + { 197, "utf8", "utf8_polish_ci", 1, 3, "", cppconn_mbcharlen_utf8, check_mb_utf8_valid}, + { 198, "utf8", "utf8_estonian_ci", 1, 3, "", cppconn_mbcharlen_utf8, check_mb_utf8_valid}, + { 199, "utf8", "utf8_spanish_ci", 1, 3, "", cppconn_mbcharlen_utf8, check_mb_utf8_valid}, + { 200, "utf8", "utf8_swedish_ci", 1, 3, "", cppconn_mbcharlen_utf8, check_mb_utf8_valid}, + { 201, "utf8", "utf8_turkish_ci", 1, 3, "", cppconn_mbcharlen_utf8, check_mb_utf8_valid}, + { 202, "utf8", "utf8_czech_ci", 1, 3, "", cppconn_mbcharlen_utf8, check_mb_utf8_valid}, + { 203, "utf8", "utf8_danish_ci", 1, 3, "", cppconn_mbcharlen_utf8, check_mb_utf8_valid }, + { 204, "utf8", "utf8_lithunian_ci", 1, 3, "", cppconn_mbcharlen_utf8, check_mb_utf8_valid }, + { 205, "utf8", "utf8_slovak_ci", 1, 3, "", cppconn_mbcharlen_utf8, check_mb_utf8_valid}, + { 206, "utf8", "utf8_spanish2_ci", 1, 3, "", cppconn_mbcharlen_utf8, check_mb_utf8_valid}, + { 207, "utf8", "utf8_roman_ci", 1, 3, "", cppconn_mbcharlen_utf8, check_mb_utf8_valid}, + { 208, "utf8", "utf8_persian_ci", 1, 3, "", cppconn_mbcharlen_utf8, check_mb_utf8_valid}, + { 209, "utf8", "utf8_esperanto_ci", 1, 3, "", cppconn_mbcharlen_utf8, check_mb_utf8_valid}, + { 210, "utf8", "utf8_hungarian_ci", 1, 3, "", cppconn_mbcharlen_utf8, check_mb_utf8_valid}, + { 211, "utf8", "utf8_sinhala_ci", 1, 3, "", cppconn_mbcharlen_utf8, check_mb_utf8_valid}, + { 212, "utf8", "utf8_german2_ci", 1, 3, "", cppconn_mbcharlen_utf8, check_mb_utf8_valid}, + { 213, "utf8", "utf8_croatian_ci", 1, 3, "", cppconn_mbcharlen_utf8, check_mb_utf8_valid}, + { 214, "utf8", "utf8_unicode_520_ci", 1, 3, "", cppconn_mbcharlen_utf8, check_mb_utf8_valid}, + { 215, "utf8", "utf8_vietnamese_ci", 1, 3, "", cppconn_mbcharlen_utf8, check_mb_utf8_valid}, + { 223, "utf8", "utf8_general_mysql500_ci", 1, 3, "", cppconn_mbcharlen_utf8, check_mb_utf8_valid}, + { 45, "utf8mb4", "utf8mb4_general_ci", 1, 4, "", cppconn_mbcharlen_utf8mb4, check_mb_utf8mb4_valid}, + { 46, "utf8mb4", "utf8mb4_bin", 1, 4, "", cppconn_mbcharlen_utf8mb4, check_mb_utf8mb4_valid}, + { 224, "utf8mb4", "utf8mb4_unicode_ci", 1, 4, "", cppconn_mbcharlen_utf8mb4, check_mb_utf8mb4_valid}, + { 225, "utf8mb4", "utf8mb4_icelandic_ci", 1, 4, "", cppconn_mbcharlen_utf8mb4, check_mb_utf8mb4_valid}, + { 226, "utf8mb4", "utf8mb4_latvian_ci", 1, 4, "", cppconn_mbcharlen_utf8mb4, check_mb_utf8mb4_valid}, + { 227, "utf8mb4", "utf8mb4_romanian_ci", 1, 4, "", cppconn_mbcharlen_utf8mb4, check_mb_utf8mb4_valid}, + { 228, "utf8mb4", "utf8mb4_slovenian_ci", 1, 4, "", cppconn_mbcharlen_utf8mb4, check_mb_utf8mb4_valid}, + { 229, "utf8mb4", "utf8mb4_polish_ci", 1, 4, "", cppconn_mbcharlen_utf8mb4, check_mb_utf8mb4_valid}, + { 230, "utf8mb4", "utf8mb4_estonian_ci", 1, 4, "", cppconn_mbcharlen_utf8mb4, check_mb_utf8mb4_valid}, + { 231, "utf8mb4", "utf8mb4_spanish_ci", 1, 4, "", cppconn_mbcharlen_utf8mb4, check_mb_utf8mb4_valid}, + { 232, "utf8mb4", "utf8mb4_swedish_ci", 1, 4, "", cppconn_mbcharlen_utf8mb4, check_mb_utf8mb4_valid}, + { 233, "utf8mb4", "utf8mb4_turkish_ci", 1, 4, "", cppconn_mbcharlen_utf8mb4, check_mb_utf8mb4_valid}, + { 234, "utf8mb4", "utf8mb4_czech_ci", 1, 4, "", cppconn_mbcharlen_utf8mb4, check_mb_utf8mb4_valid}, + { 235, "utf8mb4", "utf8mb4_danish_ci", 1, 4, "", cppconn_mbcharlen_utf8mb4, check_mb_utf8mb4_valid}, + { 236, "utf8mb4", "utf8mb4_lithuanian_ci", 1, 4, "", cppconn_mbcharlen_utf8mb4, check_mb_utf8mb4_valid}, + { 237, "utf8mb4", "utf8mb4_slovak_ci", 1, 4, "", cppconn_mbcharlen_utf8mb4, check_mb_utf8mb4_valid}, + { 238, "utf8mb4", "utf8mb4_spanish2_ci", 1, 4, "", cppconn_mbcharlen_utf8mb4, check_mb_utf8mb4_valid}, + { 239, "utf8mb4", "utf8mb4_roman_ci", 1, 4, "", cppconn_mbcharlen_utf8mb4, check_mb_utf8mb4_valid}, + { 240, "utf8mb4", "utf8mb4_persian_ci", 1, 4, "", cppconn_mbcharlen_utf8mb4, check_mb_utf8mb4_valid}, + { 241, "utf8mb4", "utf8mb4_esperanto_ci", 1, 4, "", cppconn_mbcharlen_utf8mb4, check_mb_utf8mb4_valid}, + { 242, "utf8mb4", "utf8mb4_hungarian_ci", 1, 4, "", cppconn_mbcharlen_utf8mb4, check_mb_utf8mb4_valid}, + { 243, "utf8mb4", "utf8mb4_sinhala_ci", 1, 4, "", cppconn_mbcharlen_utf8mb4, check_mb_utf8mb4_valid}, + { 244, "utf8mb4", "utf8mb4_german2_ci", 1, 4, "", cppconn_mbcharlen_utf8mb4, check_mb_utf8mb4_valid}, + { 245, "utf8mb4", "utf8mb4_croatian_ci", 1, 4, "", cppconn_mbcharlen_utf8mb4, check_mb_utf8mb4_valid}, + { 246, "utf8mb4", "utf8mb4_unicode_520_ci", 1, 4, "", cppconn_mbcharlen_utf8mb4, check_mb_utf8mb4_valid}, + { 247, "utf8mb4", "utf8mb4_vietnamese_ci", 1, 4, "", cppconn_mbcharlen_utf8mb4, check_mb_utf8mb4_valid}, + + /*Should not really happen, but adding them */ + { 254, "utf8", "utf8_general_cs", 1, 3, "", cppconn_mbcharlen_utf8, check_mb_utf8_valid}, + + { 101, "utf16", "utf16_unicode_ci", 2, 4, "", cppconn_mbcharlen_utf16, check_mb_utf16_valid}, + { 102, "utf16", "utf16_icelandic_ci", 2, 4, "", cppconn_mbcharlen_utf16, check_mb_utf16_valid}, + { 103, "utf16", "utf16_latvian_ci", 2, 4, "", cppconn_mbcharlen_utf16, check_mb_utf16_valid}, + { 104, "utf16", "utf16_romanian_ci", 2, 4, "", cppconn_mbcharlen_utf16, check_mb_utf16_valid}, + { 105, "utf16", "utf16_slovenian_ci", 2, 4, "", cppconn_mbcharlen_utf16, check_mb_utf16_valid}, + { 106, "utf16", "utf16_polish_ci", 2, 4, "", cppconn_mbcharlen_utf16, check_mb_utf16_valid}, + { 107, "utf16", "utf16_estonian_ci", 2, 4, "", cppconn_mbcharlen_utf16, check_mb_utf16_valid}, + { 108, "utf16", "utf16_spanish_ci", 2, 4, "", cppconn_mbcharlen_utf16, check_mb_utf16_valid}, + { 109, "utf16", "utf16_swedish_ci", 2, 4, "", cppconn_mbcharlen_utf16, check_mb_utf16_valid}, + { 110, "utf16", "utf16_turkish_ci", 2, 4, "", cppconn_mbcharlen_utf16, check_mb_utf16_valid}, + { 111, "utf16", "utf16_czech_ci", 2, 4, "", cppconn_mbcharlen_utf16, check_mb_utf16_valid}, + { 112, "utf16", "utf16_danish_ci", 2, 4, "", cppconn_mbcharlen_utf16, check_mb_utf16_valid}, + { 113, "utf16", "utf16_lithuanian_ci", 2, 4, "", cppconn_mbcharlen_utf16, check_mb_utf16_valid}, + { 114, "utf16", "utf16_slovak_ci", 2, 4, "", cppconn_mbcharlen_utf16, check_mb_utf16_valid}, + { 115, "utf16", "utf16_spanish2_ci", 2, 4, "", cppconn_mbcharlen_utf16, check_mb_utf16_valid}, + { 116, "utf16", "utf16_roman_ci", 2, 4, "", cppconn_mbcharlen_utf16, check_mb_utf16_valid}, + { 117, "utf16", "utf16_persian_ci", 2, 4, "", cppconn_mbcharlen_utf16, check_mb_utf16_valid}, + { 118, "utf16", "utf16_esperanto_ci", 2, 4, "", cppconn_mbcharlen_utf16, check_mb_utf16_valid}, + { 119, "utf16", "utf16_hungarian_ci", 2, 4, "", cppconn_mbcharlen_utf16, check_mb_utf16_valid}, + { 120, "utf16", "utf16_sinhala_ci", 2, 4, "", cppconn_mbcharlen_utf16, check_mb_utf16_valid}, + { 121, "utf16", "utf16_german2_ci", 2, 4, "", cppconn_mbcharlen_utf16, check_mb_utf16_valid}, + { 122, "utf16", "utf16_croatian_ci", 2, 4, "", cppconn_mbcharlen_utf16, check_mb_utf16_valid}, + { 123, "utf16", "utf16_unicode_520_ci", 2, 4, "", cppconn_mbcharlen_utf16, check_mb_utf16_valid}, + { 124, "utf16", "utf16_vietnamese_ci", 2, 4, "", cppconn_mbcharlen_utf16, check_mb_utf16_valid}, + + { 160, "utf32", "utf32_unicode_ci", 4, 4, "", cppconn_mbcharlen_utf32, check_mb_utf32_valid}, + { 161, "utf32", "utf32_icelandic_ci", 4, 4, "", cppconn_mbcharlen_utf32, check_mb_utf32_valid}, + { 162, "utf32", "utf32_latvian_ci", 4, 4, "", cppconn_mbcharlen_utf32, check_mb_utf32_valid}, + { 163, "utf32", "utf32_romanian_ci", 4, 4, "", cppconn_mbcharlen_utf32, check_mb_utf32_valid}, + { 164, "utf32", "utf32_slovenian_ci", 4, 4, "", cppconn_mbcharlen_utf32, check_mb_utf32_valid}, + { 165, "utf32", "utf32_polish_ci", 4, 4, "", cppconn_mbcharlen_utf32, check_mb_utf32_valid}, + { 166, "utf32", "utf32_estonian_ci", 4, 4, "", cppconn_mbcharlen_utf32, check_mb_utf32_valid}, + { 167, "utf32", "utf32_spanish_ci", 4, 4, "", cppconn_mbcharlen_utf32, check_mb_utf32_valid}, + { 168, "utf32", "utf32_swedish_ci", 4, 4, "", cppconn_mbcharlen_utf32, check_mb_utf32_valid}, + { 169, "utf32", "utf32_turkish_ci", 4, 4, "", cppconn_mbcharlen_utf32, check_mb_utf32_valid}, + { 170, "utf32", "utf32_czech_ci", 4, 4, "", cppconn_mbcharlen_utf32, check_mb_utf32_valid}, + { 171, "utf32", "utf32_danish_ci", 4, 4, "", cppconn_mbcharlen_utf32, check_mb_utf32_valid}, + { 172, "utf32", "utf32_lithuanian_ci", 4, 4, "", cppconn_mbcharlen_utf32, check_mb_utf32_valid}, + { 173, "utf32", "utf32_slovak_ci", 4, 4, "", cppconn_mbcharlen_utf32, check_mb_utf32_valid}, + { 174, "utf32", "utf32_spanish2_ci", 4, 4, "", cppconn_mbcharlen_utf32, check_mb_utf32_valid}, + { 175, "utf32", "utf32_roman_ci", 4, 4, "", cppconn_mbcharlen_utf32, check_mb_utf32_valid}, + { 176, "utf32", "utf32_persian_ci", 4, 4, "", cppconn_mbcharlen_utf32, check_mb_utf32_valid}, + { 177, "utf32", "utf32_esperanto_ci", 4, 4, "", cppconn_mbcharlen_utf32, check_mb_utf32_valid}, + { 178, "utf32", "utf32_hungarian_ci", 4, 4, "", cppconn_mbcharlen_utf32, check_mb_utf32_valid}, + { 179, "utf32", "utf32_sinhala_ci", 4, 4, "", cppconn_mbcharlen_utf32, check_mb_utf32_valid}, + { 180, "utf32", "utf32_german2_ci", 4, 4, "", cppconn_mbcharlen_utf32, check_mb_utf32_valid}, + { 181, "utf32", "utf32_croatian_ci", 4, 4, "", cppconn_mbcharlen_utf32, check_mb_utf32_valid}, + { 182, "utf32", "utf32_unicode_520_ci", 4, 4, "", cppconn_mbcharlen_utf32, check_mb_utf32_valid}, + { 183, "utf32", "utf32_vietnamese_ci", 4, 4, "", cppconn_mbcharlen_utf32, check_mb_utf32_valid}, + + { 0, NULL, NULL, 0, 0, NULL, NULL, NULL} +}; +#define MAGIC_BINARY_CHARSET_NR 63 + +const Mysql::Util::OUR_CHARSET* Mysql::Util::find_charset(unsigned int charsetnr) +{ + const OUR_CHARSET * c = our_charsets60; + + do { + if (c->nr == charsetnr) { + return c; + } + ++c; + } while (c[0].nr != 0); + return NULL; +} + +int Mysql::Util::mysql_type_to_datatype( const MYSQL_FIELD* const field ) +{ + switch( field->type ) + { + case MYSQL_TYPE_BIT: + if( field->flags != ( BINARY_FLAG|UNSIGNED_FLAG ) ) + return Mysql::DataType::BIT; + return Mysql::DataType::BINARY; + case MYSQL_TYPE_DECIMAL: + case MYSQL_TYPE_NEWDECIMAL: + return Mysql::DataType::DECIMAL; + case MYSQL_TYPE_TINY: + return Mysql::DataType::TINYINT; + case MYSQL_TYPE_SHORT: + return Mysql::DataType::SMALLINT; + case MYSQL_TYPE_INT24: + return Mysql::DataType::MEDIUMINT; + case MYSQL_TYPE_LONG: + return Mysql::DataType::INTEGER; + case MYSQL_TYPE_LONGLONG: + return Mysql::DataType::BIGINT; + case MYSQL_TYPE_FLOAT: + return Mysql::DataType::REAL; + case MYSQL_TYPE_DOUBLE: + return Mysql::DataType::DOUBLE; + case MYSQL_TYPE_NULL: + return Mysql::DataType::SQLNULL; + case MYSQL_TYPE_TIMESTAMP: + return Mysql::DataType::TIMESTAMP; + case MYSQL_TYPE_DATE: + return Mysql::DataType::DATE; + case MYSQL_TYPE_TIME: + return Mysql::DataType::TIME; + case MYSQL_TYPE_YEAR: + return Mysql::DataType::YEAR; + case MYSQL_TYPE_DATETIME: + return Mysql::DataType::TIMESTAMP; + case MYSQL_TYPE_TINY_BLOB:// should no appear over the wire + { + bool isBinary = ( field->flags & BINARY_FLAG ) && + field->charsetnr == MAGIC_BINARY_CHARSET_NR; + const Mysql::Util::OUR_CHARSET * const cs = + Mysql::Util::find_charset(field->charsetnr); + if (!cs) { + std::ostringstream msg("Server sent unknown charsetnr ("); + msg << field->charsetnr << ") . Please report"; + throw std::runtime_error( msg.str() ); + } + return isBinary ? Mysql::DataType::VARBINARY : Mysql::DataType::VARCHAR; + } + case MYSQL_TYPE_MEDIUM_BLOB:// should no appear over the wire + case MYSQL_TYPE_LONG_BLOB:// should no appear over the wire + case MYSQL_TYPE_BLOB: + { + bool isBinary = ( field->flags & BINARY_FLAG ) && + field->charsetnr == MAGIC_BINARY_CHARSET_NR; + const Mysql::Util::OUR_CHARSET * const cs = + Mysql::Util::find_charset( field->charsetnr ); + if( !cs ) + { + std::ostringstream msg("Server sent unknown charsetnr ("); + msg << field->charsetnr << ") . Please report"; + throw std::runtime_error( msg.str() ); + } + return isBinary ? Mysql::DataType::LONGVARBINARY : Mysql::DataType::LONGVARCHAR; + } + case MYSQL_TYPE_VARCHAR: + case MYSQL_TYPE_VAR_STRING: + if( field->flags & SET_FLAG ) + { + return Mysql::DataType::SET; + } + if( field->flags & ENUM_FLAG ) + { + return Mysql::DataType::ENUM; + } + if( ( field->flags & BINARY_FLAG ) && field->charsetnr == MAGIC_BINARY_CHARSET_NR ) + { + return Mysql::DataType::VARBINARY; + } + return Mysql::DataType::VARCHAR; + case MYSQL_TYPE_STRING: + if( field->flags & SET_FLAG ) + { + return Mysql::DataType::SET; + } + if( field->flags & ENUM_FLAG ) + { + return Mysql::DataType::ENUM; + } + if( ( field->flags & BINARY_FLAG ) && field->charsetnr == MAGIC_BINARY_CHARSET_NR ) + { + return Mysql::DataType::BINARY; + } + return Mysql::DataType::CHAR; + case MYSQL_TYPE_ENUM: + /* This hould never happen - MYSQL_TYPE_ENUM is not sent over the wire, just used in the server */ + return Mysql::DataType::ENUM; + case MYSQL_TYPE_SET: + /* This hould never happen - MYSQL_TYPE_SET is not sent over the wire, just used in the server */ + return Mysql::DataType::SET; + case MYSQL_TYPE_GEOMETRY: + return Mysql::DataType::GEOMETRY; +#if LIBMYSQL_VERSION_ID > 50700 + case MYSQL_TYPE_JSON: + return Mysql::DataType::JSON; +#endif //LIBMYSQL_VERSION_ID > 50700 + default: + return Mysql::DataType::UNKNOWN; + } +} diff --git a/deps/mysqlConnector/mysql_util.h b/deps/mysqlConnector/mysql_util.h new file mode 100644 index 00000000..624475ec --- /dev/null +++ b/deps/mysqlConnector/mysql_util.h @@ -0,0 +1,117 @@ +/* +Copyright (c) 2008, 2012, Oracle and/or its affiliates. All rights reserved. + +The MySQL Connector/C++ is licensed under the terms of the GPLv2 +, like most +MySQL Connectors. There are special exceptions to the terms and +conditions of the GPLv2 as it is applied to this software, see the +FLOSS License Exception +. + +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; version 2 of the License. + +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, write to the Free Software Foundation, Inc., +51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef SAPPHIRE_MYSQL_UTIL_H +#define SAPPHIRE_MYSQL_UTIL_H + +#include +#include + +typedef struct st_mysql_field MYSQL_FIELD; +#ifndef UL64 +#ifdef _WIN32 +#define UL64(x) x##ui64 +#else +#define UL64(x) x##ULL +#endif +#endif + +#ifndef L64 +#ifdef _WIN32 +#define L64(x) x##i64 +#else +#define L64(x) x##LL +#endif +#endif + +#define NULLCSTR static_cast(0) + + +#define bit_uint1korr(A) (*(((uint8_t*)(A)))) + +#define bit_uint2korr(A) ((uint16_t) (((uint16_t) (((unsigned char*) (A))[1])) +\ + ((uint16_t) (((unsigned char*) (A))[0]) << 8))) +#define bit_uint3korr(A) ((uint32_t) (((uint32_t) (((unsigned char*) (A))[2])) +\ + (((uint32_t) (((unsigned char*) (A))[1])) << 8) +\ + (((uint32_t) (((unsigned char*) (A))[0])) << 16))) +#define bit_uint4korr(A) ((uint32_t) (((uint32_t) (((unsigned char*) (A))[3])) +\ + (((uint32_t) (((unsigned char*) (A))[2])) << 8) +\ + (((uint32_t) (((unsigned char*) (A))[1])) << 16) +\ + (((uint32_t) (((unsigned char*) (A))[0])) << 24))) +#define bit_uint5korr(A) ((uint64_t)(((uint32_t) (((unsigned char*) (A))[4])) +\ + (((uint32_t) (((unsigned char*) (A))[3])) << 8) +\ + (((uint32_t) (((unsigned char*) (A))[2])) << 16) +\ + (((uint32_t) (((unsigned char*) (A))[1])) << 24)) +\ + (((uint64_t) (((unsigned char*) (A))[0])) << 32)) +#define bit_uint6korr(A) ((uint64_t)(((uint32_t) (((unsigned char*) (A))[5])) +\ + (((uint32_t) (((unsigned char*) (A))[4])) << 8) +\ + (((uint32_t) (((unsigned char*) (A))[3])) << 16) +\ + (((uint32_t) (((unsigned char*) (A))[2])) << 24)) +\ + (((uint64_t) (((uint32_t) (((unsigned char*) (A))[1])) +\ + (((uint32_t) (((unsigned char*) (A))[0]) << 8)))) <<\ + 32)) +#define bit_uint7korr(A) ((uint64_t)(((uint32_t) (((unsigned char*) (A))[6])) +\ + (((uint32_t) (((unsigned char*) (A))[5])) << 8) +\ + (((uint32_t) (((unsigned char*) (A))[4])) << 16) +\ + (((uint32_t) (((unsigned char*) (A))[3])) << 24)) +\ + (((uint64_t) (((uint32_t) (((unsigned char*) (A))[2])) +\ + (((uint32_t) (((unsigned char*) (A))[1])) << 8) +\ + (((uint32_t) (((unsigned char*) (A))[0])) << 16))) <<\ + 32)) +#define bit_uint8korr(A) ((uint64_t)(((uint32_t) (((unsigned char*) (A))[7])) +\ + (((uint32_t) (((unsigned char*) (A))[6])) << 8) +\ + (((uint32_t) (((unsigned char*) (A))[5])) << 16) +\ + (((uint32_t) (((unsigned char*) (A))[4])) << 24)) +\ + (((uint64_t) (((uint32_t) (((unsigned char*) (A))[3])) +\ + (((uint32_t) (((unsigned char*) (A))[2])) << 8) +\ + (((uint32_t) (((unsigned char*) (A))[1])) << 16) +\ + (((uint32_t) (((unsigned char*) (A))[0])) << 24))) <<\ + 32)) +namespace Mysql +{ +namespace Util +{ + long double strtold( const char *nptr, char **endptr ); + long double strtonum( const std::string &str, int radix = 10 ); + int64_t strtoll( const char* nptr, char** endptr ); + uint64_t strtoull( const char* nptr, char** endptr ); + int32_t mysql_type_to_datatype( const MYSQL_FIELD * const field ); + + typedef struct st_our_charset + { + unsigned int nr; + const char *name; + const char *collation; + unsigned int char_minlen; + unsigned int char_maxlen; + const char *comment; + unsigned int (*mb_charlen)(unsigned int c); + unsigned int (*mb_valid)(const char *start, const char *end); + } OUR_CHARSET; + + const OUR_CHARSET * find_charset(unsigned int charsetnr); + +} +} +#endif //SAPPHIRE_MYSQL_UTIL_H diff --git a/deps/nlohmann/json.hpp b/deps/nlohmann/json.hpp new file mode 100644 index 00000000..b132e850 --- /dev/null +++ b/deps/nlohmann/json.hpp @@ -0,0 +1,19430 @@ +/* + __ _____ _____ _____ + __| | __| | | | JSON for Modern C++ +| | |__ | | | | | | version 3.3.0 +|_____|_____|_____|_|___| https://github.com/nlohmann/json + +Licensed under the MIT License . +SPDX-License-Identifier: MIT +Copyright (c) 2013-2018 Niels Lohmann . + +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. +*/ + +#ifndef NLOHMANN_JSON_HPP +#define NLOHMANN_JSON_HPP + +#define NLOHMANN_JSON_VERSION_MAJOR 3 +#define NLOHMANN_JSON_VERSION_MINOR 3 +#define NLOHMANN_JSON_VERSION_PATCH 0 + +#include // all_of, find, for_each +#include // assert +#include // and, not, or +#include // nullptr_t, ptrdiff_t, size_t +#include // hash, less +#include // initializer_list +#include // istream, ostream +#include // iterator_traits, random_access_iterator_tag +#include // accumulate +#include // string, stoi, to_string +#include // declval, forward, move, pair, swap + +// #include +#ifndef NLOHMANN_JSON_FWD_HPP +#define NLOHMANN_JSON_FWD_HPP + +#include // int64_t, uint64_t +#include // map +#include // allocator +#include // string +#include // vector + +/*! +@brief namespace for Niels Lohmann +@see https://github.com/nlohmann +@since version 1.0.0 +*/ +namespace nlohmann +{ +/*! +@brief default JSONSerializer template argument + +This serializer ignores the template arguments and uses ADL +([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl)) +for serialization. +*/ +template +struct adl_serializer; + +template class ObjectType = + std::map, + template class ArrayType = std::vector, + class StringType = std::string, class BooleanType = bool, + class NumberIntegerType = std::int64_t, + class NumberUnsignedType = std::uint64_t, + class NumberFloatType = double, + template class AllocatorType = std::allocator, + template class JSONSerializer = + adl_serializer> +class basic_json; + +/*! +@brief JSON Pointer + +A JSON pointer defines a string syntax for identifying a specific value +within a JSON document. It can be used with functions `at` and +`operator[]`. Furthermore, JSON pointers are the base for JSON patches. + +@sa [RFC 6901](https://tools.ietf.org/html/rfc6901) + +@since version 2.0.0 +*/ +template +class json_pointer; + +/*! +@brief default JSON class + +This type is the default specialization of the @ref basic_json class which +uses the standard template types. + +@since version 1.0.0 +*/ +using json = basic_json<>; +} // namespace nlohmann + +#endif + +// #include + + +// This file contains all internal macro definitions +// You MUST include macro_unscope.hpp at the end of json.hpp to undef all of them + +// exclude unsupported compilers +#if !defined(JSON_SKIP_UNSUPPORTED_COMPILER_CHECK) + #if defined(__clang__) + #if (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) < 30400 + #error "unsupported Clang version - see https://github.com/nlohmann/json#supported-compilers" + #endif + #elif defined(__GNUC__) && !(defined(__ICC) || defined(__INTEL_COMPILER)) + #if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) < 40800 + #error "unsupported GCC version - see https://github.com/nlohmann/json#supported-compilers" + #endif + #endif +#endif + +// disable float-equal warnings on GCC/clang +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + +// disable documentation warnings on clang +#if defined(__clang__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wdocumentation" +#endif + +// allow for portable deprecation warnings +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) + #define JSON_DEPRECATED __attribute__((deprecated)) +#elif defined(_MSC_VER) + #define JSON_DEPRECATED __declspec(deprecated) +#else + #define JSON_DEPRECATED +#endif + +// allow to disable exceptions +#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION) + #define JSON_THROW(exception) throw exception + #define JSON_TRY try + #define JSON_CATCH(exception) catch(exception) + #define JSON_INTERNAL_CATCH(exception) catch(exception) +#else + #define JSON_THROW(exception) std::abort() + #define JSON_TRY if(true) + #define JSON_CATCH(exception) if(false) + #define JSON_INTERNAL_CATCH(exception) if(false) +#endif + +// override exception macros +#if defined(JSON_THROW_USER) + #undef JSON_THROW + #define JSON_THROW JSON_THROW_USER +#endif +#if defined(JSON_TRY_USER) + #undef JSON_TRY + #define JSON_TRY JSON_TRY_USER +#endif +#if defined(JSON_CATCH_USER) + #undef JSON_CATCH + #define JSON_CATCH JSON_CATCH_USER + #undef JSON_INTERNAL_CATCH + #define JSON_INTERNAL_CATCH JSON_CATCH_USER +#endif +#if defined(JSON_INTERNAL_CATCH_USER) + #undef JSON_INTERNAL_CATCH + #define JSON_INTERNAL_CATCH JSON_INTERNAL_CATCH_USER +#endif + +// manual branch prediction +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) + #define JSON_LIKELY(x) __builtin_expect(!!(x), 1) + #define JSON_UNLIKELY(x) __builtin_expect(!!(x), 0) +#else + #define JSON_LIKELY(x) x + #define JSON_UNLIKELY(x) x +#endif + +// C++ language standard detection +#if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464 + #define JSON_HAS_CPP_17 + #define JSON_HAS_CPP_14 +#elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1) + #define JSON_HAS_CPP_14 +#endif + +// Ugly macros to avoid uglier copy-paste when specializing basic_json. They +// may be removed in the future once the class is split. + +#define NLOHMANN_BASIC_JSON_TPL_DECLARATION \ + template class ObjectType, \ + template class ArrayType, \ + class StringType, class BooleanType, class NumberIntegerType, \ + class NumberUnsignedType, class NumberFloatType, \ + template class AllocatorType, \ + template class JSONSerializer> + +#define NLOHMANN_BASIC_JSON_TPL \ + basic_json + +// #include + + +#include // not +#include // size_t +#include // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type + +namespace nlohmann +{ +namespace detail +{ +// alias templates to reduce boilerplate +template +using enable_if_t = typename std::enable_if::type; + +template +using uncvref_t = typename std::remove_cv::type>::type; + +// implementation of C++14 index_sequence and affiliates +// source: https://stackoverflow.com/a/32223343 +template +struct index_sequence +{ + using type = index_sequence; + using value_type = std::size_t; + static constexpr std::size_t size() noexcept + { + return sizeof...(Ints); + } +}; + +template +struct merge_and_renumber; + +template +struct merge_and_renumber, index_sequence> + : index_sequence < I1..., (sizeof...(I1) + I2)... > {}; + +template +struct make_index_sequence + : merge_and_renumber < typename make_index_sequence < N / 2 >::type, + typename make_index_sequence < N - N / 2 >::type > {}; + +template<> struct make_index_sequence<0> : index_sequence<> {}; +template<> struct make_index_sequence<1> : index_sequence<0> {}; + +template +using index_sequence_for = make_index_sequence; + +// dispatch utility (taken from ranges-v3) +template struct priority_tag : priority_tag < N - 1 > {}; +template<> struct priority_tag<0> {}; + +// taken from ranges-v3 +template +struct static_const +{ + static constexpr T value{}; +}; + +template +constexpr T static_const::value; +} // namespace detail +} // namespace nlohmann + +// #include + + +#include // not +#include // numeric_limits +#include // false_type, is_constructible, is_integral, is_same, true_type +#include // declval + +// #include + +// #include + +// #include + + +#include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +template struct make_void +{ + using type = void; +}; +template using void_t = typename make_void::type; +} // namespace detail +} // namespace nlohmann + + +// http://en.cppreference.com/w/cpp/experimental/is_detected +namespace nlohmann +{ +namespace detail +{ +struct nonesuch +{ + nonesuch() = delete; + ~nonesuch() = delete; + nonesuch(nonesuch const&) = delete; + void operator=(nonesuch const&) = delete; +}; + +template class Op, + class... Args> +struct detector +{ + using value_t = std::false_type; + using type = Default; +}; + +template class Op, class... Args> +struct detector>, Op, Args...> +{ + using value_t = std::true_type; + using type = Op; +}; + +template