Merge branch 'develop' into MAX-324
Conflicts: query_classifier/query_classifier.h
This commit is contained in:
@ -1,5 +1,6 @@
|
|||||||
cmake_minimum_required(VERSION 2.6)
|
cmake_minimum_required(VERSION 2.6)
|
||||||
message(STATUS "CMake version: ${CMAKE_VERSION}")
|
message(STATUS "CMake version: ${CMAKE_VERSION}")
|
||||||
|
|
||||||
include(macros.cmake)
|
include(macros.cmake)
|
||||||
|
|
||||||
enable_testing()
|
enable_testing()
|
||||||
@ -13,10 +14,14 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/")
|
|||||||
|
|
||||||
project(MaxScale)
|
project(MaxScale)
|
||||||
|
|
||||||
|
#Disabled for now pending evaluation
|
||||||
|
#include(CheckPlatform.cmake)
|
||||||
|
|
||||||
check_deps()
|
check_deps()
|
||||||
check_dirs()
|
check_dirs()
|
||||||
find_package(Valgrind)
|
find_package(Valgrind)
|
||||||
find_package(MySQLClient)
|
find_package(MySQLClient)
|
||||||
|
find_package(MySQLConfig)
|
||||||
|
|
||||||
set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_RPATH}:${CMAKE_INSTALL_PREFIX}/lib:${CMAKE_INSTALL_PREFIX}/modules)
|
set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_RPATH}:${CMAKE_INSTALL_PREFIX}/lib:${CMAKE_INSTALL_PREFIX}/modules)
|
||||||
|
|
||||||
@ -120,6 +125,10 @@ message(STATUS "Installing MaxScale to: ${CMAKE_INSTALL_PREFIX}/")
|
|||||||
install(FILES server/MaxScale_template.cnf DESTINATION etc)
|
install(FILES server/MaxScale_template.cnf DESTINATION etc)
|
||||||
install(FILES ${ERRMSG} DESTINATION mysql)
|
install(FILES ${ERRMSG} DESTINATION mysql)
|
||||||
install(FILES ${DOCS} DESTINATION Documentation)
|
install(FILES ${DOCS} DESTINATION Documentation)
|
||||||
|
install(FILES ${CMAKE_SOURCE_DIR}/COPYRIGHT DESTINATION ${CMAKE_INSTALL_PREFIX}/)
|
||||||
|
install(FILES ${CMAKE_SOURCE_DIR}/README DESTINATION ${CMAKE_INSTALL_PREFIX}/)
|
||||||
|
install(FILES ${CMAKE_SOURCE_DIR}/LICENSE DESTINATION ${CMAKE_INSTALL_PREFIX}/)
|
||||||
|
install(FILES ${CMAKE_SOURCE_DIR}/SETUP DESTINATION ${CMAKE_INSTALL_PREFIX}/)
|
||||||
install(DIRECTORY DESTINATION log)
|
install(DIRECTORY DESTINATION log)
|
||||||
|
|
||||||
if(${CMAKE_VERSION} VERSION_LESS 2.8.12)
|
if(${CMAKE_VERSION} VERSION_LESS 2.8.12)
|
||||||
|
45
CheckPlatform.cmake
Normal file
45
CheckPlatform.cmake
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
#Checks for all the C system headers found in all the files
|
||||||
|
|
||||||
|
include(CheckFunctionExists)
|
||||||
|
include(CheckIncludeFiles)
|
||||||
|
|
||||||
|
check_include_files(arpa/inet.h HAVE_ARPA_INET)
|
||||||
|
check_include_files(crypt.h HAVE_CRYPT)
|
||||||
|
check_include_files(ctype.h HAVE_CTYPE)
|
||||||
|
check_include_files(dirent.h HAVE_DIRENT)
|
||||||
|
check_include_files(dlfcn.h HAVE_DLFCN)
|
||||||
|
check_include_files(errno.h HAVE_ERRNO)
|
||||||
|
check_include_files(execinfo.h HAVE_EXECINFO)
|
||||||
|
check_include_files(fcntl.h HAVE_FCNTL)
|
||||||
|
check_include_files(ftw.h HAVE_FTW)
|
||||||
|
check_include_files(getopt.h HAVE_GETOPT)
|
||||||
|
check_include_files(ini.h HAVE_INI)
|
||||||
|
check_include_files(math.h HAVE_MATH)
|
||||||
|
check_include_files(memlog.h HAVE_MEMLOG)
|
||||||
|
check_include_files(netdb.h HAVE_NETDB)
|
||||||
|
check_include_files(netinet/in.h HAVE_NETINET_IN)
|
||||||
|
check_include_files(openssl/aes.h HAVE_OPENSSL_AES)
|
||||||
|
check_include_files(openssl/sha.h HAVE_OPENSSL_SHA)
|
||||||
|
check_include_files(pthread.h HAVE_PTHREAD)
|
||||||
|
check_include_files(pwd.h HAVE_PWD)
|
||||||
|
check_include_files(rdtsc.h HAVE_RDTSC)
|
||||||
|
check_include_files(regex.h HAVE_REGEX)
|
||||||
|
check_include_files(signal.h HAVE_SIGNAL)
|
||||||
|
check_include_files(stdarg.h HAVE_STDARG)
|
||||||
|
check_include_files(stdbool.h HAVE_STDBOOL)
|
||||||
|
check_include_files(stdint.h HAVE_STDINT)
|
||||||
|
check_include_files(stdio.h HAVE_STDIO)
|
||||||
|
check_include_files(stdlib.h HAVE_STDLIB)
|
||||||
|
check_include_files(string.h HAVE_STRING)
|
||||||
|
check_include_files(strings.h HAVE_STRINGS)
|
||||||
|
check_include_files(sys/epoll.h HAVE_SYS_EPOLL)
|
||||||
|
check_include_files(sys/ioctl.h HAVE_SYS_IOCTL)
|
||||||
|
check_include_files(syslog.h HAVE_SYSLOG)
|
||||||
|
check_include_files(sys/param.h HAVE_SYS_PARAM)
|
||||||
|
check_include_files(sys/socket.h HAVE_SYS_SOCKET)
|
||||||
|
check_include_files(sys/stat.h HAVE_SYS_STAT)
|
||||||
|
check_include_files(sys/time.h HAVE_SYS_TIME)
|
||||||
|
check_include_files(sys/types.h HAVE_SYS_TYPES)
|
||||||
|
check_include_files(sys/un.h HAVE_SYS_UN)
|
||||||
|
check_include_files(time.h HAVE_TIME)
|
||||||
|
check_include_files(unistd.h HAVE_UNISTD)
|
BIN
Documentation/MariaDB MaxScale 1.0.4 Release Notes.pdf
Normal file
BIN
Documentation/MariaDB MaxScale 1.0.4 Release Notes.pdf
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
13
FindMySQLConfig.cmake
Normal file
13
FindMySQLConfig.cmake
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# This CMake file tries to find the the MySQL configuration tool
|
||||||
|
# The following variables are set:
|
||||||
|
# MYSQLCONFIG_FOUND - System has MySQL and the tool was found
|
||||||
|
# MYSQLCONFIG_EXECUTABLE - The MySQL configuration tool executable
|
||||||
|
find_program(MYSQLCONFIG_EXECUTABLE mysql_config)
|
||||||
|
if(MYSQLCONFIG_EXECUTABLE MATCHES "MYSQLCONFIG_EXECUTABLE-NOTFOUND")
|
||||||
|
message(FATAL_ERROR "Cannot find mysql_config.")
|
||||||
|
set(MYSQLCONFIG_FOUND FALSE CACHE INTERNAL "")
|
||||||
|
unset(MYSQLCONFIG_EXECUTABLE)
|
||||||
|
else()
|
||||||
|
message(STATUS "mysql_config found: ${MYSQLCONFIG_EXECUTABLE}")
|
||||||
|
set(MYSQLCONFIG_FOUND TRUE CACHE INTERNAL "")
|
||||||
|
endif()
|
2
debian/rules
vendored
2
debian/rules
vendored
@ -3,7 +3,7 @@
|
|||||||
$(MAKE) ROOT_PATH=$(shell pwd) HOME="" clean
|
$(MAKE) ROOT_PATH=$(shell pwd) HOME="" clean
|
||||||
$(MAKE) ROOT_PATH=$(shell pwd) HOME="" depend
|
$(MAKE) ROOT_PATH=$(shell pwd) HOME="" depend
|
||||||
$(MAKE) ROOT_PATH=$(shell pwd) HOME=""
|
$(MAKE) ROOT_PATH=$(shell pwd) HOME=""
|
||||||
$(MAKE) DEST="$(shell pwd)/binaries" ROOT_PATH=$(shell pwd) HOME="" ERRMSG="/usr/share/mysql/english" EMBEDDED_LIB="/usr/lib/x86_64-linux-gnu/" install
|
$(MAKE) DEST="$(shell pwd)/binaries" ROOT_PATH=$(shell pwd) HOME="" ERRMSG="/usr/share/mysql/english" EMBEDDED_LIB="$(mysql_config --variable=pkglibdir)" install
|
||||||
dh $@
|
dh $@
|
||||||
override_dh_usrlocal:
|
override_dh_usrlocal:
|
||||||
override_dh_auto_clean:
|
override_dh_auto_clean:
|
||||||
|
@ -677,14 +677,18 @@ static int logmanager_write_log(
|
|||||||
logfile_rotate(lf); /*< wakes up file writer */
|
logfile_rotate(lf); /*< wakes up file writer */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
/** Length of string that will be written, limited by bufsize */
|
/** Length of string that will be written, limited by bufsize */
|
||||||
int safe_str_len;
|
int safe_str_len;
|
||||||
/** Length of session id */
|
/** Length of session id */
|
||||||
int sesid_str_len;
|
int sesid_str_len;
|
||||||
|
|
||||||
/** 2 braces, 2 spaces and terminating char */
|
/**
|
||||||
|
* 2 braces, 2 spaces and terminating char
|
||||||
|
* If session id is stored to tls_log_info structure, allocate
|
||||||
|
* room for session id too.
|
||||||
|
*/
|
||||||
if (id == LOGFILE_TRACE && tls_log_info.li_sesid != 0)
|
if (id == LOGFILE_TRACE && tls_log_info.li_sesid != 0)
|
||||||
{
|
{
|
||||||
sesid_str_len = 2+2+get_decimal_len(tls_log_info.li_sesid)+1;
|
sesid_str_len = 2+2+get_decimal_len(tls_log_info.li_sesid)+1;
|
||||||
@ -747,7 +751,6 @@ static int logmanager_write_log(
|
|||||||
wp += strlen(wp);
|
wp += strlen(wp);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write timestamp with at most <timestamp_len> characters
|
* Write timestamp with at most <timestamp_len> characters
|
||||||
* to wp.
|
* to wp.
|
||||||
@ -1511,7 +1514,7 @@ return_unregister:
|
|||||||
|
|
||||||
logmanager_unregister();
|
logmanager_unregister();
|
||||||
|
|
||||||
return_err:
|
return_err:
|
||||||
|
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
@ -1848,10 +1851,9 @@ static char* fname_conf_get_suffix(
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* Parameters:
|
* Parameters:
|
||||||
* @param lm - <usage>
|
* @param lm Log manager pointer
|
||||||
* <description>
|
|
||||||
*
|
*
|
||||||
* @return
|
* @return succp true if succeed, otherwise false.
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* @details If logfile is supposed to be located to shared memory
|
* @details If logfile is supposed to be located to shared memory
|
||||||
@ -1871,6 +1873,7 @@ static bool logfiles_init(
|
|||||||
bool store_shmem;
|
bool store_shmem;
|
||||||
bool write_syslog;
|
bool write_syslog;
|
||||||
|
|
||||||
|
/** Open syslog immediately. Print pid of loggind process. */
|
||||||
if (syslog_id_str != NULL)
|
if (syslog_id_str != NULL)
|
||||||
{
|
{
|
||||||
openlog(syslog_ident_str, LOG_PID | LOG_NDELAY, LOG_USER);
|
openlog(syslog_ident_str, LOG_PID | LOG_NDELAY, LOG_USER);
|
||||||
@ -2483,21 +2486,13 @@ static bool file_is_symlink(
|
|||||||
* link name. Create block buffer for logfile.
|
* link name. Create block buffer for logfile.
|
||||||
*
|
*
|
||||||
* Parameters:
|
* Parameters:
|
||||||
* @param logfile - <usage>
|
* @param logfile log file
|
||||||
* <description>
|
* @param logfile_id identifier for log file
|
||||||
*
|
* @param logmanager log manager pointer
|
||||||
* @param logfile_id - <usage>
|
* @param store_shmem flag to indicate whether log is physically written to shmem
|
||||||
* <description>
|
* @param write_syslog flag to indicate whether log is also written to syslog
|
||||||
*
|
*
|
||||||
* @param logmanager - <usage>
|
|
||||||
* <description>
|
|
||||||
*
|
|
||||||
* @param store_shmem - <usage>
|
|
||||||
* <description>
|
|
||||||
*
|
|
||||||
* @return true if succeed, false otherwise
|
* @return true if succeed, false otherwise
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
static bool logfile_init(
|
static bool logfile_init(
|
||||||
logfile_t* logfile,
|
logfile_t* logfile,
|
||||||
@ -2841,7 +2836,7 @@ static void* thr_filewriter_fun(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Process all logfiles which have buffered writes. */
|
/** Process all logfiles which have buffered writes. */
|
||||||
for (i=LOGFILE_FIRST; i<=LOGFILE_LAST; i <<= 1)
|
for (i=LOGFILE_FIRST; i<=LOGFILE_LAST; i <<= 1)
|
||||||
{
|
{
|
||||||
retry_flush_on_exit:
|
retry_flush_on_exit:
|
||||||
/**
|
/**
|
||||||
@ -3084,7 +3079,7 @@ static int find_last_seqno(
|
|||||||
|
|
||||||
for (i=0, p=parts; p->sp_string != NULL; i++, p=p->sp_next)
|
for (i=0, p=parts; p->sp_string != NULL; i++, p=p->sp_next)
|
||||||
{
|
{
|
||||||
if (snstr != NULL && i == seqnoidx)
|
if (snstr != NULL && i == seqnoidx && strnlen(snstr,NAME_MAX) < NAME_MAX)
|
||||||
{
|
{
|
||||||
strncat(filename, snstr, NAME_MAX - 1); /*< add sequence number */
|
strncat(filename, snstr, NAME_MAX - 1); /*< add sequence number */
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,7 @@ typedef struct log_info_st
|
|||||||
{
|
{
|
||||||
size_t li_sesid;
|
size_t li_sesid;
|
||||||
int li_enabled_logs;
|
int li_enabled_logs;
|
||||||
} log_info_t;
|
} log_info_t;
|
||||||
|
|
||||||
#define LE LOGFILE_ERROR
|
#define LE LOGFILE_ERROR
|
||||||
#define LM LOGFILE_MESSAGE
|
#define LM LOGFILE_MESSAGE
|
||||||
@ -113,6 +113,7 @@ void skygw_logmanager_exit(void);
|
|||||||
void skygw_log_done(void);
|
void skygw_log_done(void);
|
||||||
int skygw_log_write(logfile_id_t id, const char* format, ...);
|
int skygw_log_write(logfile_id_t id, const char* format, ...);
|
||||||
int skygw_log_flush(logfile_id_t id);
|
int skygw_log_flush(logfile_id_t id);
|
||||||
|
void skygw_log_sync_all(void);
|
||||||
int skygw_log_rotate(logfile_id_t id);
|
int skygw_log_rotate(logfile_id_t id);
|
||||||
int skygw_log_write_flush(logfile_id_t id, const char* format, ...);
|
int skygw_log_write_flush(logfile_id_t id, const char* format, ...);
|
||||||
int skygw_log_enable(logfile_id_t id);
|
int skygw_log_enable(logfile_id_t id);
|
||||||
@ -121,8 +122,6 @@ void skygw_log_sync_all(void);
|
|||||||
|
|
||||||
EXTERN_C_BLOCK_END
|
EXTERN_C_BLOCK_END
|
||||||
|
|
||||||
void writebuf_clear(void* data);
|
|
||||||
|
|
||||||
const char* get_trace_prefix_default(void);
|
const char* get_trace_prefix_default(void);
|
||||||
const char* get_trace_suffix_default(void);
|
const char* get_trace_suffix_default(void);
|
||||||
const char* get_msg_prefix_default(void);
|
const char* get_msg_prefix_default(void);
|
||||||
|
@ -9,9 +9,9 @@ macro(set_maxscale_version)
|
|||||||
#MaxScale version number
|
#MaxScale version number
|
||||||
set(MAXSCALE_VERSION_MAJOR "1")
|
set(MAXSCALE_VERSION_MAJOR "1")
|
||||||
set(MAXSCALE_VERSION_MINOR "0")
|
set(MAXSCALE_VERSION_MINOR "0")
|
||||||
set(MAXSCALE_VERSION_PATCH "3")
|
set(MAXSCALE_VERSION_PATCH "5")
|
||||||
set(MAXSCALE_VERSION_NUMERIC "${MAXSCALE_VERSION_MAJOR}.${MAXSCALE_VERSION_MINOR}.${MAXSCALE_VERSION_PATCH}")
|
set(MAXSCALE_VERSION_NUMERIC "${MAXSCALE_VERSION_MAJOR}.${MAXSCALE_VERSION_MINOR}.${MAXSCALE_VERSION_PATCH}")
|
||||||
set(MAXSCALE_VERSION "${MAXSCALE_VERSION_MAJOR}.${MAXSCALE_VERSION_MINOR}.${MAXSCALE_VERSION_PATCH}-rc")
|
set(MAXSCALE_VERSION "${MAXSCALE_VERSION_MAJOR}.${MAXSCALE_VERSION_MINOR}.${MAXSCALE_VERSION_PATCH}-unstable")
|
||||||
|
|
||||||
endmacro()
|
endmacro()
|
||||||
|
|
||||||
@ -21,7 +21,7 @@ macro(set_variables)
|
|||||||
set(INSTALL_DIR "/usr/local/skysql/maxscale/" CACHE PATH "MaxScale installation directory.")
|
set(INSTALL_DIR "/usr/local/skysql/maxscale/" CACHE PATH "MaxScale installation directory.")
|
||||||
|
|
||||||
# Build type
|
# Build type
|
||||||
set(BUILD_TYPE "None" CACHE STRING "Build type, possible values are:None, Debug, Optimized.")
|
set(BUILD_TYPE "None" CACHE STRING "Build type, possible values are:None, Debug, DebugSymbols, Optimized.")
|
||||||
|
|
||||||
# hostname or IP address of MaxScale's host
|
# hostname or IP address of MaxScale's host
|
||||||
set(TEST_HOST "127.0.0.1" CACHE STRING "hostname or IP address of MaxScale's host")
|
set(TEST_HOST "127.0.0.1" CACHE STRING "hostname or IP address of MaxScale's host")
|
||||||
@ -271,7 +271,7 @@ endmacro()
|
|||||||
|
|
||||||
function(subdirs VAR DIRPATH)
|
function(subdirs VAR DIRPATH)
|
||||||
|
|
||||||
if(${CMAKE_VERSION} VERSION_LESS 2.12 )
|
if(${CMAKE_VERSION} VERSION_LESS 2.8.12 )
|
||||||
set(COMP_VAR PATH)
|
set(COMP_VAR PATH)
|
||||||
else()
|
else()
|
||||||
set(COMP_VAR DIRECTORY)
|
set(COMP_VAR DIRECTORY)
|
||||||
|
@ -67,28 +67,18 @@ extern __thread log_info_t tls_log_info;
|
|||||||
|
|
||||||
#define QTYPE_LESS_RESTRICTIVE_THAN_WRITE(t) (t<QUERY_TYPE_WRITE ? true : false)
|
#define QTYPE_LESS_RESTRICTIVE_THAN_WRITE(t) (t<QUERY_TYPE_WRITE ? true : false)
|
||||||
|
|
||||||
static THD* get_or_create_thd_for_parsing(
|
static THD* get_or_create_thd_for_parsing(MYSQL* mysql, char* query_str);
|
||||||
MYSQL* mysql,
|
static unsigned long set_client_flags(MYSQL* mysql);
|
||||||
char* query_str);
|
static bool create_parse_tree(THD* thd);
|
||||||
|
static skygw_query_type_t resolve_query_type(THD* thd);
|
||||||
static unsigned long set_client_flags(
|
|
||||||
MYSQL* mysql);
|
|
||||||
|
|
||||||
static bool create_parse_tree(
|
|
||||||
THD* thd);
|
|
||||||
|
|
||||||
static skygw_query_type_t resolve_query_type(
|
|
||||||
THD* thd);
|
|
||||||
|
|
||||||
static bool skygw_stmt_causes_implicit_commit(
|
static bool skygw_stmt_causes_implicit_commit(
|
||||||
LEX* lex,
|
LEX* lex,
|
||||||
int* autocommit_stmt);
|
int* autocommit_stmt);
|
||||||
|
|
||||||
static int is_autocommit_stmt(
|
static int is_autocommit_stmt(LEX* lex);
|
||||||
LEX* lex);
|
static void parsing_info_set_plain_str(void* ptr, char* str);
|
||||||
|
static void* skygw_get_affected_tables(void* lexptr);
|
||||||
|
|
||||||
static void parsing_info_set_plain_str(void* ptr,
|
|
||||||
char* str);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calls parser for the query includede in the buffer. Creates and adds parsing
|
* Calls parser for the query includede in the buffer. Creates and adds parsing
|
||||||
@ -107,6 +97,11 @@ skygw_query_type_t query_classifier_get_type(
|
|||||||
|
|
||||||
ss_info_dassert(querybuf != NULL, ("querybuf is NULL"));
|
ss_info_dassert(querybuf != NULL, ("querybuf is NULL"));
|
||||||
|
|
||||||
|
if (querybuf == NULL)
|
||||||
|
{
|
||||||
|
succp = false;
|
||||||
|
goto retblock;
|
||||||
|
}
|
||||||
/** Create parsing info for the query and store it to buffer */
|
/** Create parsing info for the query and store it to buffer */
|
||||||
succp = query_is_parsed(querybuf);
|
succp = query_is_parsed(querybuf);
|
||||||
|
|
||||||
@ -133,6 +128,7 @@ skygw_query_type_t query_classifier_get_type(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
retblock:
|
||||||
return qtype;
|
return qtype;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,7 +154,7 @@ bool parse_query (
|
|||||||
/** Do not parse without releasing previous parse info first */
|
/** Do not parse without releasing previous parse info first */
|
||||||
ss_dassert(!query_is_parsed(querybuf));
|
ss_dassert(!query_is_parsed(querybuf));
|
||||||
|
|
||||||
if (query_is_parsed(querybuf))
|
if (querybuf == NULL || query_is_parsed(querybuf))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -225,7 +221,7 @@ bool query_is_parsed(
|
|||||||
GWBUF* buf)
|
GWBUF* buf)
|
||||||
{
|
{
|
||||||
CHK_GWBUF(buf);
|
CHK_GWBUF(buf);
|
||||||
return GWBUF_IS_PARSED(buf);
|
return (buf != NULL && GWBUF_IS_PARSED(buf));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -957,13 +953,25 @@ return_rc:
|
|||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(NOT_USED)
|
||||||
char* skygw_query_classifier_get_stmtname(
|
char* skygw_query_classifier_get_stmtname(
|
||||||
MYSQL* mysql)
|
GWBUF* buf)
|
||||||
{
|
{
|
||||||
|
MYSQL* mysql;
|
||||||
|
|
||||||
|
if (buf == NULL ||
|
||||||
|
buf->gwbuf_bufobj == NULL ||
|
||||||
|
buf->gwbuf_bufobj->bo_data == NULL ||
|
||||||
|
(mysql = (MYSQL *)((parsing_info_t *)buf->gwbuf_bufobj->bo_data)->pi_handle) == NULL ||
|
||||||
|
mysql->thd == NULL ||
|
||||||
|
(THD *)(mysql->thd))->lex == NULL ||
|
||||||
|
(THD *)(mysql->thd))->lex->prepared_stmt_name == NULL)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
return ((THD *)(mysql->thd))->lex->prepared_stmt_name.str;
|
return ((THD *)(mysql->thd))->lex->prepared_stmt_name.str;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the parse tree from parsed querybuf.
|
* Get the parse tree from parsed querybuf.
|
||||||
@ -975,31 +983,29 @@ char* skygw_query_classifier_get_stmtname(
|
|||||||
LEX* get_lex(GWBUF* querybuf)
|
LEX* get_lex(GWBUF* querybuf)
|
||||||
{
|
{
|
||||||
|
|
||||||
parsing_info_t* pi;
|
parsing_info_t* pi;
|
||||||
MYSQL* mysql;
|
MYSQL* mysql;
|
||||||
THD* thd;
|
THD* thd;
|
||||||
|
|
||||||
if (!GWBUF_IS_PARSED(querybuf))
|
if (querybuf == NULL || !GWBUF_IS_PARSED(querybuf))
|
||||||
{
|
{
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
pi = (parsing_info_t *)gwbuf_get_buffer_object_data(querybuf,
|
pi = (parsing_info_t *)gwbuf_get_buffer_object_data(querybuf,
|
||||||
GWBUF_PARSING_INFO);
|
GWBUF_PARSING_INFO);
|
||||||
|
|
||||||
if (pi == NULL)
|
if (pi == NULL)
|
||||||
{
|
{
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((mysql = (MYSQL *)pi->pi_handle) == NULL ||
|
if ((mysql = (MYSQL *)pi->pi_handle) == NULL ||
|
||||||
(thd = (THD *)mysql->thd) == NULL)
|
(thd = (THD *)mysql->thd) == NULL)
|
||||||
{
|
{
|
||||||
ss_dassert(mysql != NULL &&
|
ss_dassert(mysql != NULL && thd != NULL);
|
||||||
thd != NULL);
|
return NULL;
|
||||||
return NULL;
|
}
|
||||||
}
|
return thd->lex;
|
||||||
|
|
||||||
return thd->lex;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1009,7 +1015,7 @@ LEX* get_lex(GWBUF* querybuf)
|
|||||||
* @param thd Pointer to a valid THD
|
* @param thd Pointer to a valid THD
|
||||||
* @return Pointer to the head of the TABLE_LIST chain or NULL in case of an error
|
* @return Pointer to the head of the TABLE_LIST chain or NULL in case of an error
|
||||||
*/
|
*/
|
||||||
void* skygw_get_affected_tables(void* lexptr)
|
static void* skygw_get_affected_tables(void* lexptr)
|
||||||
{
|
{
|
||||||
LEX* lex = (LEX*)lexptr;
|
LEX* lex = (LEX*)lexptr;
|
||||||
|
|
||||||
@ -1027,87 +1033,93 @@ void* skygw_get_affected_tables(void* lexptr)
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads the parsetree and lists all the affected tables and views in the query.
|
* Reads the parsetree and lists all the affected tables and views in the query.
|
||||||
* In the case of an error, the size of the table is set to zero and no memory is allocated.
|
* In the case of an error, the size of the table is set to zero and no memory
|
||||||
* The caller must free the allocated memory.
|
* is allocated. The caller must free the allocated memory.
|
||||||
*
|
*
|
||||||
* @param querybuf GWBUF where the table names are extracted from
|
* @param querybuf GWBUF where the table names are extracted from
|
||||||
* @param tblsize Pointer where the number of tables is written
|
* @param tblsize Pointer where the number of tables is written
|
||||||
* @return Array of null-terminated strings with the table names
|
* @return Array of null-terminated strings with the table names
|
||||||
*/
|
*/
|
||||||
char** skygw_get_table_names(GWBUF* querybuf,int* tblsize, bool fullnames)
|
char** skygw_get_table_names(GWBUF* querybuf, int* tblsize, bool fullnames)
|
||||||
{
|
{
|
||||||
LEX* lex;
|
LEX* lex;
|
||||||
TABLE_LIST* tbl;
|
TABLE_LIST* tbl;
|
||||||
int i = 0,
|
int i = 0,
|
||||||
currtblsz = 0;
|
currtblsz = 0;
|
||||||
char **tables = NULL,
|
char **tables = NULL,
|
||||||
**tmp = NULL;
|
**tmp = NULL;
|
||||||
|
|
||||||
if( (lex = get_lex(querybuf)) == NULL ||
|
if(querybuf == NULL ||
|
||||||
lex->current_select == NULL )
|
tblsize == NULL ||
|
||||||
|
(lex = get_lex(querybuf)) == NULL ||
|
||||||
|
lex->current_select == NULL)
|
||||||
{
|
{
|
||||||
goto retblock;
|
goto retblock;
|
||||||
}
|
}
|
||||||
|
|
||||||
lex->current_select = lex->all_selects_list;
|
lex->current_select = lex->all_selects_list;
|
||||||
|
|
||||||
while(lex->current_select){
|
while(lex->current_select)
|
||||||
|
{
|
||||||
tbl = (TABLE_LIST*)skygw_get_affected_tables(lex);
|
tbl = (TABLE_LIST*)skygw_get_affected_tables(lex);
|
||||||
|
|
||||||
while (tbl)
|
while (tbl)
|
||||||
{
|
{
|
||||||
if(i >= currtblsz){
|
if (i >= currtblsz)
|
||||||
|
|
||||||
tmp = (char**)malloc(sizeof(char*)*(currtblsz*2+1));
|
|
||||||
|
|
||||||
if(tmp){
|
|
||||||
if(currtblsz > 0){
|
|
||||||
int x;
|
|
||||||
for(x = 0;x<currtblsz;x++){
|
|
||||||
tmp[x] = tables[x];
|
|
||||||
}
|
|
||||||
free(tables);
|
|
||||||
}
|
|
||||||
|
|
||||||
tables = tmp;
|
|
||||||
currtblsz = currtblsz*2 + 1;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
if(tmp != NULL){
|
|
||||||
char *catnm = NULL;
|
|
||||||
|
|
||||||
if(fullnames)
|
|
||||||
{
|
{
|
||||||
if(tbl->db && strcmp(tbl->db,"skygw_virtual") != 0)
|
tmp = (char**)malloc(sizeof(char*)*(currtblsz*2+1));
|
||||||
|
|
||||||
|
if(tmp)
|
||||||
|
{
|
||||||
|
if(currtblsz > 0)
|
||||||
{
|
{
|
||||||
catnm = (char*)calloc(strlen(tbl->db) + strlen(tbl->table_name) + 2,sizeof(char));
|
int x;
|
||||||
|
for(x = 0; x<currtblsz; x++)
|
||||||
|
{
|
||||||
|
tmp[x] = tables[x];
|
||||||
|
}
|
||||||
|
free(tables);
|
||||||
|
}
|
||||||
|
tables = tmp;
|
||||||
|
currtblsz = currtblsz*2 + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(tmp != NULL)
|
||||||
|
{
|
||||||
|
char *catnm = NULL;
|
||||||
|
|
||||||
|
if(fullnames)
|
||||||
|
{
|
||||||
|
if (tbl->db &&
|
||||||
|
strcmp(tbl->db,"skygw_virtual") != 0)
|
||||||
|
{
|
||||||
|
catnm = (char*)calloc(strlen(tbl->db) +
|
||||||
|
strlen(tbl->table_name) +
|
||||||
|
2,
|
||||||
|
sizeof(char));
|
||||||
strcpy(catnm,tbl->db);
|
strcpy(catnm,tbl->db);
|
||||||
strcat(catnm,".");
|
strcat(catnm,".");
|
||||||
strcat(catnm,tbl->table_name);
|
strcat(catnm,tbl->table_name);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(catnm)
|
||||||
|
{
|
||||||
|
tables[i++] = catnm;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tables[i++] = strdup(tbl->table_name);
|
||||||
|
}
|
||||||
|
tbl=tbl->next_local;
|
||||||
}
|
}
|
||||||
|
} /*< while (tbl) */
|
||||||
if(catnm)
|
lex->current_select = lex->current_select->next_select_in_list();
|
||||||
{
|
} /*< while(lex->current_select) */
|
||||||
tables[i++] = catnm;
|
retblock:
|
||||||
}
|
*tblsize = i;
|
||||||
else
|
return tables;
|
||||||
{
|
|
||||||
tables[i++] = strdup(tbl->table_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
tbl=tbl->next_local;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lex->current_select = lex->current_select->next_select_in_list();
|
|
||||||
}
|
|
||||||
|
|
||||||
retblock:
|
|
||||||
*tblsize = i;
|
|
||||||
return tables;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1117,52 +1129,70 @@ char** skygw_get_table_names(GWBUF* querybuf,int* tblsize, bool fullnames)
|
|||||||
*/
|
*/
|
||||||
char* skygw_get_created_table_name(GWBUF* querybuf)
|
char* skygw_get_created_table_name(GWBUF* querybuf)
|
||||||
{
|
{
|
||||||
LEX* lex;
|
LEX* lex;
|
||||||
|
|
||||||
if((lex = get_lex(querybuf)) == NULL)
|
if(querybuf == NULL || (lex = get_lex(querybuf)) == NULL)
|
||||||
{
|
{
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(lex->create_last_non_select_table &&
|
if (lex->create_last_non_select_table &&
|
||||||
lex->create_last_non_select_table->table_name){
|
lex->create_last_non_select_table->table_name)
|
||||||
char* name = strdup(lex->create_last_non_select_table->table_name);
|
{
|
||||||
return name;
|
char* name = strdup(lex->create_last_non_select_table->table_name);
|
||||||
}else{
|
return name;
|
||||||
return NULL;
|
}
|
||||||
}
|
else
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether the query is a "real" query ie. SELECT,UPDATE,INSERT,DELETE or any variation of these.
|
* Checks whether the query is a "real" query ie. SELECT,UPDATE,INSERT,DELETE or
|
||||||
* Queries that affect the underlying database are not considered as real queries and the queries that target
|
* any variation of these. Queries that affect the underlying database are not
|
||||||
* specific row or variable data are regarded as the real queries.
|
* considered as real queries and the queries that target specific row or
|
||||||
|
* variable data are regarded as the real queries.
|
||||||
|
*
|
||||||
* @param GWBUF to analyze
|
* @param GWBUF to analyze
|
||||||
|
*
|
||||||
* @return true if the query is a real query, otherwise false
|
* @return true if the query is a real query, otherwise false
|
||||||
*/
|
*/
|
||||||
bool skygw_is_real_query(GWBUF* querybuf)
|
bool skygw_is_real_query(GWBUF* querybuf)
|
||||||
{
|
{
|
||||||
LEX* lex = get_lex(querybuf);
|
bool succp;
|
||||||
if(lex){
|
LEX* lex;
|
||||||
switch(lex->sql_command){
|
|
||||||
case SQLCOM_SELECT:
|
if (querybuf == NULL ||
|
||||||
return lex->all_selects_list->table_list.elements > 0;
|
(lex = get_lex(querybuf)) == NULL)
|
||||||
case SQLCOM_UPDATE:
|
{
|
||||||
case SQLCOM_INSERT:
|
succp = false;
|
||||||
case SQLCOM_INSERT_SELECT:
|
goto retblock;
|
||||||
case SQLCOM_DELETE:
|
|
||||||
case SQLCOM_TRUNCATE:
|
|
||||||
case SQLCOM_REPLACE:
|
|
||||||
case SQLCOM_REPLACE_SELECT:
|
|
||||||
case SQLCOM_PREPARE:
|
|
||||||
case SQLCOM_EXECUTE:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
switch(lex->sql_command) {
|
||||||
return false;
|
case SQLCOM_SELECT:
|
||||||
|
succp = lex->all_selects_list->table_list.elements > 0;
|
||||||
|
goto retblock;
|
||||||
|
break;
|
||||||
|
case SQLCOM_UPDATE:
|
||||||
|
case SQLCOM_INSERT:
|
||||||
|
case SQLCOM_INSERT_SELECT:
|
||||||
|
case SQLCOM_DELETE:
|
||||||
|
case SQLCOM_TRUNCATE:
|
||||||
|
case SQLCOM_REPLACE:
|
||||||
|
case SQLCOM_REPLACE_SELECT:
|
||||||
|
case SQLCOM_PREPARE:
|
||||||
|
case SQLCOM_EXECUTE:
|
||||||
|
succp = true;
|
||||||
|
goto retblock;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
succp = false;
|
||||||
|
goto retblock;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
retblock:
|
||||||
|
return succp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1173,10 +1203,11 @@ bool skygw_is_real_query(GWBUF* querybuf)
|
|||||||
*/
|
*/
|
||||||
bool is_drop_table_query(GWBUF* querybuf)
|
bool is_drop_table_query(GWBUF* querybuf)
|
||||||
{
|
{
|
||||||
LEX* lex;
|
LEX* lex;
|
||||||
|
|
||||||
return (lex = get_lex(querybuf)) != NULL &&
|
return (querybuf != NULL &&
|
||||||
lex->sql_command == SQLCOM_DROP_TABLE;
|
(lex = get_lex(querybuf)) != NULL &&
|
||||||
|
lex->sql_command == SQLCOM_DROP_TABLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1202,7 +1233,8 @@ char* skygw_get_canonical(
|
|||||||
Item* item;
|
Item* item;
|
||||||
char* querystr;
|
char* querystr;
|
||||||
|
|
||||||
if (!GWBUF_IS_PARSED(querybuf))
|
if (querybuf == NULL ||
|
||||||
|
!GWBUF_IS_PARSED(querybuf))
|
||||||
{
|
{
|
||||||
querystr = NULL;
|
querystr = NULL;
|
||||||
goto retblock;
|
goto retblock;
|
||||||
@ -1339,25 +1371,30 @@ retblock:
|
|||||||
void parsing_info_done(
|
void parsing_info_done(
|
||||||
void* ptr)
|
void* ptr)
|
||||||
{
|
{
|
||||||
parsing_info_t* pi = (parsing_info_t *)ptr;
|
parsing_info_t* pi;
|
||||||
|
|
||||||
|
if (ptr)
|
||||||
|
{
|
||||||
|
pi = (parsing_info_t *)ptr;
|
||||||
|
|
||||||
if (pi->pi_handle != NULL)
|
if (pi->pi_handle != NULL)
|
||||||
{
|
{
|
||||||
MYSQL* mysql = (MYSQL *)pi->pi_handle;
|
MYSQL* mysql = (MYSQL *)pi->pi_handle;
|
||||||
|
|
||||||
if (mysql->thd != NULL)
|
if (mysql->thd != NULL)
|
||||||
{
|
{
|
||||||
(*mysql->methods->free_embedded_thd)(mysql);
|
(*mysql->methods->free_embedded_thd)(mysql);
|
||||||
mysql->thd = NULL;
|
mysql->thd = NULL;
|
||||||
}
|
}
|
||||||
mysql_close(mysql);
|
mysql_close(mysql);
|
||||||
}
|
}
|
||||||
/** Free plain text query string */
|
/** Free plain text query string */
|
||||||
if (pi->pi_query_plain_str != NULL)
|
if (pi->pi_query_plain_str != NULL)
|
||||||
{
|
{
|
||||||
free(pi->pi_query_plain_str);
|
free(pi->pi_query_plain_str);
|
||||||
}
|
}
|
||||||
free(pi);
|
free(pi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -83,13 +83,13 @@ typedef struct parsing_info_st {
|
|||||||
skygw_query_type_t query_classifier_get_type(GWBUF* querybuf);
|
skygw_query_type_t query_classifier_get_type(GWBUF* querybuf);
|
||||||
|
|
||||||
/** Free THD context and close MYSQL */
|
/** Free THD context and close MYSQL */
|
||||||
char* skygw_query_classifier_get_stmtname(MYSQL* mysql);
|
#if defined(NOT_USED)
|
||||||
|
char* skygw_query_classifier_get_stmtname(GWBUF* buf);
|
||||||
|
#endif
|
||||||
char* skygw_get_created_table_name(GWBUF* querybuf);
|
char* skygw_get_created_table_name(GWBUF* querybuf);
|
||||||
bool is_drop_table_query(GWBUF* querybuf);
|
bool is_drop_table_query(GWBUF* querybuf);
|
||||||
bool skygw_is_real_query(GWBUF* querybuf);
|
bool skygw_is_real_query(GWBUF* querybuf);
|
||||||
void* skygw_get_affected_tables(void* lexptr);
|
char** skygw_get_table_names(GWBUF* querybuf, int* tblsize, bool fullnames);
|
||||||
char** skygw_get_table_names(GWBUF* querybuf,int* tblsize,bool fullnames);
|
|
||||||
char** skygw_get_database_names(GWBUF* querybuf,int* size);
|
|
||||||
char* skygw_get_canonical(GWBUF* querybuf);
|
char* skygw_get_canonical(GWBUF* querybuf);
|
||||||
bool parse_query (GWBUF* querybuf);
|
bool parse_query (GWBUF* querybuf);
|
||||||
parsing_info_t* parsing_info_init(void (*donefun)(void *));
|
parsing_info_t* parsing_info_init(void (*donefun)(void *));
|
||||||
|
@ -217,6 +217,7 @@ static void sigterm_handler (int i) {
|
|||||||
LOGIF(LE, (skygw_log_write_flush(
|
LOGIF(LE, (skygw_log_write_flush(
|
||||||
LOGFILE_ERROR,
|
LOGFILE_ERROR,
|
||||||
"MaxScale received signal SIGTERM. Exiting.")));
|
"MaxScale received signal SIGTERM. Exiting.")));
|
||||||
|
skygw_log_sync_all();
|
||||||
shutdown_server();
|
shutdown_server();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -228,6 +229,7 @@ sigint_handler (int i)
|
|||||||
LOGIF(LE, (skygw_log_write_flush(
|
LOGIF(LE, (skygw_log_write_flush(
|
||||||
LOGFILE_ERROR,
|
LOGFILE_ERROR,
|
||||||
"MaxScale received signal SIGINT. Shutting down.")));
|
"MaxScale received signal SIGINT. Shutting down.")));
|
||||||
|
skygw_log_sync_all();
|
||||||
shutdown_server();
|
shutdown_server();
|
||||||
fprintf(stderr, "\n\nShutting down MaxScale\n\n");
|
fprintf(stderr, "\n\nShutting down MaxScale\n\n");
|
||||||
}
|
}
|
||||||
@ -270,6 +272,8 @@ sigfatal_handler (int i)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
skygw_log_sync_all();
|
||||||
|
|
||||||
/* re-raise signal to enforce core dump */
|
/* re-raise signal to enforce core dump */
|
||||||
fprintf(stderr, "\n\nWriting core dump\n");
|
fprintf(stderr, "\n\nWriting core dump\n");
|
||||||
signal_set(i, SIG_DFL);
|
signal_set(i, SIG_DFL);
|
||||||
@ -1527,7 +1531,7 @@ int main(int argc, char **argv)
|
|||||||
free(log_context);
|
free(log_context);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*<
|
/**
|
||||||
* Init Log Manager for MaxScale.
|
* Init Log Manager for MaxScale.
|
||||||
* If $MAXSCALE_HOME is set then write the logs into $MAXSCALE_HOME/log.
|
* If $MAXSCALE_HOME is set then write the logs into $MAXSCALE_HOME/log.
|
||||||
* The skygw_logmanager_init expects to take arguments as passed to main
|
* The skygw_logmanager_init expects to take arguments as passed to main
|
||||||
@ -1537,23 +1541,28 @@ int main(int argc, char **argv)
|
|||||||
{
|
{
|
||||||
char buf[1024];
|
char buf[1024];
|
||||||
char *argv[8];
|
char *argv[8];
|
||||||
bool succp;
|
bool succp;
|
||||||
|
/** Set log directory under $MAXSCALE_HOME/log */
|
||||||
sprintf(buf, "%s/log", home_dir);
|
sprintf(buf, "%s/log", home_dir);
|
||||||
if(mkdir(buf, 0777) != 0){
|
|
||||||
|
if(mkdir(buf, 0777) != 0)
|
||||||
if(errno != EEXIST){
|
{
|
||||||
fprintf(stderr,
|
if(errno != EEXIST)
|
||||||
"Error: Cannot create log directory: %s\n",buf);
|
{
|
||||||
goto return_main;
|
fprintf(stderr,
|
||||||
}
|
"Error: Cannot create log directory: %s\n",
|
||||||
}
|
buf);
|
||||||
|
goto return_main;
|
||||||
|
}
|
||||||
|
}
|
||||||
argv[0] = "MaxScale";
|
argv[0] = "MaxScale";
|
||||||
argv[1] = "-j";
|
argv[1] = "-j";
|
||||||
argv[2] = buf;
|
argv[2] = buf;
|
||||||
|
|
||||||
if (logtofile)
|
if (logtofile)
|
||||||
{
|
{
|
||||||
argv[3] = "-l"; /*< write to syslog */
|
argv[3] = "-l"; /*< write to syslog */
|
||||||
|
/** Logs that should be syslogged */
|
||||||
argv[4] = "LOGFILE_MESSAGE,LOGFILE_ERROR"
|
argv[4] = "LOGFILE_MESSAGE,LOGFILE_ERROR"
|
||||||
"LOGFILE_DEBUG,LOGFILE_TRACE";
|
"LOGFILE_DEBUG,LOGFILE_TRACE";
|
||||||
argv[5] = NULL;
|
argv[5] = NULL;
|
||||||
@ -1562,9 +1571,9 @@ int main(int argc, char **argv)
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
argv[3] = "-s"; /*< store to shared memory */
|
argv[3] = "-s"; /*< store to shared memory */
|
||||||
argv[4] = "LOGFILE_DEBUG,LOGFILE_TRACE"; /*< ..these logs to shm */
|
argv[4] = "LOGFILE_DEBUG,LOGFILE_TRACE"; /*< to shm */
|
||||||
argv[5] = "-l"; /*< write to syslog */
|
argv[5] = "-l"; /*< write to syslog */
|
||||||
argv[6] = "LOGFILE_MESSAGE,LOGFILE_ERROR"; /*< ..these logs to syslog */
|
argv[6] = "LOGFILE_MESSAGE,LOGFILE_ERROR"; /*< to syslog */
|
||||||
argv[7] = NULL;
|
argv[7] = NULL;
|
||||||
succp = skygw_logmanager_init(7, argv);
|
succp = skygw_logmanager_init(7, argv);
|
||||||
}
|
}
|
||||||
@ -1575,8 +1584,7 @@ int main(int argc, char **argv)
|
|||||||
goto return_main;
|
goto return_main;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
/*<
|
|
||||||
* Resolve the full pathname for configuration file and check for
|
* Resolve the full pathname for configuration file and check for
|
||||||
* read accessibility.
|
* read accessibility.
|
||||||
*/
|
*/
|
||||||
|
@ -674,6 +674,14 @@ poll_set_maxwait(unsigned int maxwait)
|
|||||||
* to process the DCB. If there are pending events the DCB will be moved to the
|
* to process the DCB. If there are pending events the DCB will be moved to the
|
||||||
* back of the queue so that other DCB's will have a share of the threads to
|
* back of the queue so that other DCB's will have a share of the threads to
|
||||||
* execute events for them.
|
* execute events for them.
|
||||||
|
*
|
||||||
|
* Including session id to log entries depends on this function. Assumption is
|
||||||
|
* that when maxscale thread starts processing of an event it processes one
|
||||||
|
* and only one session until it returns from this function. Session id is
|
||||||
|
* read to thread's local storage in macro LOGIF_MAYBE(...) and reset back
|
||||||
|
* to zero just before returning in LOGIF(...) macro.
|
||||||
|
* Thread local storage (tls_log_info_t) follows thread and is accessed every
|
||||||
|
* time log is written to particular log.
|
||||||
*
|
*
|
||||||
* @param thread_id The thread ID of the calling thread
|
* @param thread_id The thread ID of the calling thread
|
||||||
* @return 0 if no DCB's have been processed
|
* @return 0 if no DCB's have been processed
|
||||||
@ -798,7 +806,7 @@ unsigned long qtime;
|
|||||||
simple_mutex_unlock(&dcb->dcb_write_lock);
|
simple_mutex_unlock(&dcb->dcb_write_lock);
|
||||||
#else
|
#else
|
||||||
atomic_add(&pollStats.n_write, 1);
|
atomic_add(&pollStats.n_write, 1);
|
||||||
|
/** Read session id to thread's local storage */
|
||||||
LOGIF_MAYBE(LT, (dcb_get_ses_log_info(
|
LOGIF_MAYBE(LT, (dcb_get_ses_log_info(
|
||||||
dcb,
|
dcb,
|
||||||
&tls_log_info.li_sesid,
|
&tls_log_info.li_sesid,
|
||||||
@ -852,6 +860,7 @@ unsigned long qtime;
|
|||||||
dcb,
|
dcb,
|
||||||
dcb->fd)));
|
dcb->fd)));
|
||||||
atomic_add(&pollStats.n_read, 1);
|
atomic_add(&pollStats.n_read, 1);
|
||||||
|
/** Read session id to thread's local storage */
|
||||||
LOGIF_MAYBE(LT, (dcb_get_ses_log_info(
|
LOGIF_MAYBE(LT, (dcb_get_ses_log_info(
|
||||||
dcb,
|
dcb,
|
||||||
&tls_log_info.li_sesid,
|
&tls_log_info.li_sesid,
|
||||||
@ -891,6 +900,7 @@ unsigned long qtime;
|
|||||||
strerror(eno))));
|
strerror(eno))));
|
||||||
}
|
}
|
||||||
atomic_add(&pollStats.n_error, 1);
|
atomic_add(&pollStats.n_error, 1);
|
||||||
|
/** Read session id to thread's local storage */
|
||||||
LOGIF_MAYBE(LT, (dcb_get_ses_log_info(
|
LOGIF_MAYBE(LT, (dcb_get_ses_log_info(
|
||||||
dcb,
|
dcb,
|
||||||
&tls_log_info.li_sesid,
|
&tls_log_info.li_sesid,
|
||||||
@ -919,6 +929,7 @@ unsigned long qtime;
|
|||||||
{
|
{
|
||||||
dcb->flags |= DCBF_HUNG;
|
dcb->flags |= DCBF_HUNG;
|
||||||
spinlock_release(&dcb->dcb_initlock);
|
spinlock_release(&dcb->dcb_initlock);
|
||||||
|
/** Read session id to thread's local storage */
|
||||||
LOGIF_MAYBE(LT, (dcb_get_ses_log_info(
|
LOGIF_MAYBE(LT, (dcb_get_ses_log_info(
|
||||||
dcb,
|
dcb,
|
||||||
&tls_log_info.li_sesid,
|
&tls_log_info.li_sesid,
|
||||||
@ -951,6 +962,7 @@ unsigned long qtime;
|
|||||||
{
|
{
|
||||||
dcb->flags |= DCBF_HUNG;
|
dcb->flags |= DCBF_HUNG;
|
||||||
spinlock_release(&dcb->dcb_initlock);
|
spinlock_release(&dcb->dcb_initlock);
|
||||||
|
/** Read session id to thread's local storage */
|
||||||
LOGIF_MAYBE(LT, (dcb_get_ses_log_info(
|
LOGIF_MAYBE(LT, (dcb_get_ses_log_info(
|
||||||
dcb,
|
dcb,
|
||||||
&tls_log_info.li_sesid,
|
&tls_log_info.li_sesid,
|
||||||
@ -1016,6 +1028,7 @@ unsigned long qtime;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
dcb->evq.processing = 0;
|
dcb->evq.processing = 0;
|
||||||
|
/** Reset session id from thread's local storage */
|
||||||
LOGIF(LT, tls_log_info.li_sesid = 0);
|
LOGIF(LT, tls_log_info.li_sesid = 0);
|
||||||
spinlock_release(&pollqlock);
|
spinlock_release(&pollqlock);
|
||||||
|
|
||||||
|
@ -283,6 +283,9 @@ char *home, buf[1024];
|
|||||||
result += test4();
|
result += test4();
|
||||||
result += test5();
|
result += test5();
|
||||||
|
|
||||||
|
/* Add the default user back so other tests can use it */
|
||||||
|
admin_add_user("admin", "skysql");
|
||||||
|
|
||||||
exit(result);
|
exit(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,6 +60,8 @@
|
|||||||
#include <dcb.h>
|
#include <dcb.h>
|
||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
#include <poll.h>
|
#include <poll.h>
|
||||||
|
#include <mysql_client_server_protocol.h>
|
||||||
|
#include <housekeeper.h>
|
||||||
|
|
||||||
#define MYSQL_COM_QUIT 0x01
|
#define MYSQL_COM_QUIT 0x01
|
||||||
#define MYSQL_COM_INITDB 0x02
|
#define MYSQL_COM_INITDB 0x02
|
||||||
@ -73,6 +75,11 @@
|
|||||||
|
|
||||||
#define REPLY_TIMEOUT_SECOND 5
|
#define REPLY_TIMEOUT_SECOND 5
|
||||||
#define REPLY_TIMEOUT_MILLISECOND 1
|
#define REPLY_TIMEOUT_MILLISECOND 1
|
||||||
|
#define PARENT 0
|
||||||
|
#define CHILD 1
|
||||||
|
|
||||||
|
#define PTR_IS_RESULTSET(b) (b[0] == 0x01 && b[1] == 0x0 && b[2] == 0x0 && b[3] == 0x01)
|
||||||
|
#define PTR_IS_EOF(b) (b[4] == 0xfe)
|
||||||
|
|
||||||
static unsigned char required_packets[] = {
|
static unsigned char required_packets[] = {
|
||||||
MYSQL_COM_QUIT,
|
MYSQL_COM_QUIT,
|
||||||
@ -153,10 +160,9 @@ typedef struct {
|
|||||||
|
|
||||||
FILTER_DEF* dummy_filterdef;
|
FILTER_DEF* dummy_filterdef;
|
||||||
int active; /* filter is active? */
|
int active; /* filter is active? */
|
||||||
int waiting; /* if the client is waiting for a reply */
|
bool waiting[2]; /* if the client is waiting for a reply */
|
||||||
int replies; /* Number of queries received */
|
int eof[2];
|
||||||
int min_replies; /* Minimum number of replies to receive
|
int replies[2]; /* Number of queries received */
|
||||||
* before forwarding the packet to the client*/
|
|
||||||
DCB *branch_dcb; /* Client DCB for "branch" service */
|
DCB *branch_dcb; /* Client DCB for "branch" service */
|
||||||
SESSION *branch_session;/* The branch service session */
|
SESSION *branch_session;/* The branch service session */
|
||||||
int n_duped; /* Number of duplicated queries */
|
int n_duped; /* Number of duplicated queries */
|
||||||
@ -202,6 +208,83 @@ static int hcfn(
|
|||||||
return strcmp(i1,i2);
|
return strcmp(i1,i2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
orphan_free(void* data)
|
||||||
|
{
|
||||||
|
spinlock_acquire(&orphanLock);
|
||||||
|
orphan_session_t *ptr = allOrphans, *finished = NULL, *tmp = NULL;
|
||||||
|
#ifdef SS_DEBUG
|
||||||
|
int o_stopping = 0, o_ready = 0, o_freed = 0;
|
||||||
|
#endif
|
||||||
|
while(ptr)
|
||||||
|
{
|
||||||
|
if(ptr->session->state == SESSION_STATE_TO_BE_FREED)
|
||||||
|
{
|
||||||
|
if(ptr == allOrphans)
|
||||||
|
{
|
||||||
|
tmp = ptr;
|
||||||
|
allOrphans = ptr->next;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tmp = allOrphans;
|
||||||
|
while(tmp && tmp->next != ptr)
|
||||||
|
tmp = tmp->next;
|
||||||
|
if(tmp)
|
||||||
|
{
|
||||||
|
tmp->next = ptr->next;
|
||||||
|
tmp = ptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#ifdef SS_DEBUG
|
||||||
|
else if(ptr->session->state == SESSION_STATE_STOPPING)
|
||||||
|
{
|
||||||
|
o_stopping++;
|
||||||
|
}
|
||||||
|
else if(ptr->session->state == SESSION_STATE_ROUTER_READY)
|
||||||
|
{
|
||||||
|
o_ready++;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
ptr = ptr->next;
|
||||||
|
if(tmp)
|
||||||
|
{
|
||||||
|
tmp->next = finished;
|
||||||
|
finished = tmp;
|
||||||
|
tmp = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
spinlock_release(&orphanLock);
|
||||||
|
|
||||||
|
#ifdef SS_DEBUG
|
||||||
|
if(o_stopping + o_ready > 0)
|
||||||
|
skygw_log_write(LOGFILE_DEBUG, "tee.c: %d orphans in "
|
||||||
|
"SESSION_STATE_STOPPING, %d orphans in "
|
||||||
|
"SESSION_STATE_ROUTER_READY. ", o_stopping, o_ready);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
while(finished)
|
||||||
|
{
|
||||||
|
o_freed++;
|
||||||
|
tmp = finished;
|
||||||
|
finished = finished->next;
|
||||||
|
|
||||||
|
tmp->session->service->router->freeSession(
|
||||||
|
tmp->session->service->router_instance,
|
||||||
|
tmp->session->router_session);
|
||||||
|
|
||||||
|
tmp->session->state = SESSION_STATE_FREE;
|
||||||
|
free(tmp->session);
|
||||||
|
free(tmp);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef SS_DEBUG
|
||||||
|
skygw_log_write(LOGFILE_DEBUG, "tee.c: %d orphans freed.", o_freed);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of the mandatory version entry point
|
* Implementation of the mandatory version entry point
|
||||||
*
|
*
|
||||||
@ -221,6 +304,7 @@ void
|
|||||||
ModuleInit()
|
ModuleInit()
|
||||||
{
|
{
|
||||||
spinlock_init(&orphanLock);
|
spinlock_init(&orphanLock);
|
||||||
|
hktask_add("tee orphan cleanup",orphan_free,NULL,15);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -490,7 +574,6 @@ char *remote, *userName;
|
|||||||
}
|
}
|
||||||
|
|
||||||
ses->tail = *dummy_upstream;
|
ses->tail = *dummy_upstream;
|
||||||
my_session->min_replies = 2;
|
|
||||||
my_session->branch_session = ses;
|
my_session->branch_session = ses;
|
||||||
my_session->branch_dcb = dcb;
|
my_session->branch_dcb = dcb;
|
||||||
my_session->dummy_filterdef = dummy;
|
my_session->dummy_filterdef = dummy;
|
||||||
@ -605,78 +688,6 @@ SESSION* ses = my_session->branch_session;
|
|||||||
}
|
}
|
||||||
free(session);
|
free(session);
|
||||||
|
|
||||||
spinlock_acquire(&orphanLock);
|
|
||||||
orphan_session_t *ptr = allOrphans, *finished = NULL,*tmp = NULL;
|
|
||||||
#ifdef SS_DEBUG
|
|
||||||
int o_stopping = 0, o_ready = 0,o_freed = 0;
|
|
||||||
#endif
|
|
||||||
while(ptr)
|
|
||||||
{
|
|
||||||
if(ptr->session->state == SESSION_STATE_TO_BE_FREED)
|
|
||||||
{
|
|
||||||
if(ptr == allOrphans)
|
|
||||||
{
|
|
||||||
tmp = ptr;
|
|
||||||
allOrphans = ptr->next;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
tmp = allOrphans;
|
|
||||||
while(tmp && tmp->next != ptr)
|
|
||||||
tmp = tmp->next;
|
|
||||||
if(tmp)
|
|
||||||
{
|
|
||||||
tmp->next = ptr->next;
|
|
||||||
tmp = ptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#ifdef SS_DEBUG
|
|
||||||
else if(ptr->session->state == SESSION_STATE_STOPPING)
|
|
||||||
{
|
|
||||||
o_stopping++;
|
|
||||||
}
|
|
||||||
else if(ptr->session->state == SESSION_STATE_ROUTER_READY)
|
|
||||||
{
|
|
||||||
o_ready++;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
ptr = ptr->next;
|
|
||||||
if(tmp)
|
|
||||||
{
|
|
||||||
tmp->next = finished;
|
|
||||||
finished = tmp;
|
|
||||||
tmp = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
spinlock_release(&orphanLock);
|
|
||||||
|
|
||||||
#ifdef SS_DEBUG
|
|
||||||
if(o_stopping + o_ready > 0)
|
|
||||||
skygw_log_write(LOGFILE_DEBUG,"tee.c: %d orphans in "
|
|
||||||
"SESSION_STATE_STOPPING, %d orphans in "
|
|
||||||
"SESSION_STATE_ROUTER_READY. ",o_stopping,o_ready);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
while(finished)
|
|
||||||
{
|
|
||||||
#ifdef SS_DEBUG
|
|
||||||
skygw_log_write(LOGFILE_DEBUG,"tee.c: %d orphans freed.",++o_freed);
|
|
||||||
#endif
|
|
||||||
tmp = finished;
|
|
||||||
finished = finished->next;
|
|
||||||
|
|
||||||
tmp->session->service->router->freeSession(
|
|
||||||
tmp->session->service->router_instance,
|
|
||||||
tmp->session->router_session);
|
|
||||||
|
|
||||||
tmp->session->state = SESSION_STATE_FREE;
|
|
||||||
free(tmp->session);
|
|
||||||
free(tmp);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@ -761,8 +772,6 @@ GWBUF *clone = NULL;
|
|||||||
(my_instance->nomatch == NULL ||
|
(my_instance->nomatch == NULL ||
|
||||||
regexec(&my_instance->nore,ptr,0,NULL, 0) != 0))
|
regexec(&my_instance->nore,ptr,0,NULL, 0) != 0))
|
||||||
{
|
{
|
||||||
char *dummy;
|
|
||||||
|
|
||||||
length = modutil_MySQL_query_len(queue, &residual);
|
length = modutil_MySQL_query_len(queue, &residual);
|
||||||
clone = gwbuf_clone_all(queue);
|
clone = gwbuf_clone_all(queue);
|
||||||
my_session->residual = residual;
|
my_session->residual = residual;
|
||||||
@ -775,9 +784,14 @@ GWBUF *clone = NULL;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* Pass the query downstream */
|
/* Pass the query downstream */
|
||||||
|
|
||||||
my_session->replies = 0;
|
ss_dassert(my_session->tee_replybuf == NULL);
|
||||||
rval = my_session->down.routeQuery(my_session->down.instance,
|
|
||||||
|
memset(my_session->replies,0,2*sizeof(int));
|
||||||
|
memset(my_session->eof,0,2*sizeof(int));
|
||||||
|
memset(my_session->waiting,0,2*sizeof(bool));
|
||||||
|
|
||||||
|
rval = my_session->down.routeQuery(my_session->down.instance,
|
||||||
my_session->down.session,
|
my_session->down.session,
|
||||||
queue);
|
queue);
|
||||||
if (clone)
|
if (clone)
|
||||||
@ -809,9 +823,42 @@ GWBUF *clone = NULL;
|
|||||||
}
|
}
|
||||||
my_session->n_rejected++;
|
my_session->n_rejected++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return rval;
|
return rval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scans the GWBUF for EOF packets. If two packets for this session have been found
|
||||||
|
* from either the parent or the child branch, mark the response set from that branch as over.
|
||||||
|
* @param session The Tee filter session
|
||||||
|
* @param branch Parent or child branch
|
||||||
|
* @param reply Buffer to scan
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
scan_resultset(TEE_SESSION *session, int branch, GWBUF *reply)
|
||||||
|
{
|
||||||
|
unsigned char* ptr = (unsigned char*) reply->start;
|
||||||
|
unsigned char* end = (unsigned char*) reply->end;
|
||||||
|
int pktlen = 0;
|
||||||
|
|
||||||
|
while(ptr < end)
|
||||||
|
{
|
||||||
|
pktlen = gw_mysql_get_byte3(ptr) + 4;
|
||||||
|
if(PTR_IS_EOF(ptr))
|
||||||
|
{
|
||||||
|
session->eof[branch]++;
|
||||||
|
|
||||||
|
if(session->eof[branch] == 2)
|
||||||
|
{
|
||||||
|
session->waiting[branch] = false;
|
||||||
|
session->eof[branch] = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ptr += pktlen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The clientReply entry point. This is passed the response buffer
|
* The clientReply entry point. This is passed the response buffer
|
||||||
@ -826,33 +873,51 @@ GWBUF *clone = NULL;
|
|||||||
static int
|
static int
|
||||||
clientReply (FILTER* instance, void *session, GWBUF *reply)
|
clientReply (FILTER* instance, void *session, GWBUF *reply)
|
||||||
{
|
{
|
||||||
int rc;
|
int rc, branch;
|
||||||
TEE_SESSION *my_session = (TEE_SESSION *) session;
|
TEE_SESSION *my_session = (TEE_SESSION *) session;
|
||||||
|
|
||||||
spinlock_acquire(&my_session->tee_lock);
|
spinlock_acquire(&my_session->tee_lock);
|
||||||
|
|
||||||
ss_dassert(my_session->active);
|
ss_dassert(my_session->active);
|
||||||
my_session->replies++;
|
|
||||||
|
|
||||||
if (my_session->tee_replybuf == NULL &&
|
branch = instance == NULL ? CHILD : PARENT;
|
||||||
instance != NULL)
|
unsigned char *ptr = (unsigned char*)reply->start;
|
||||||
{
|
|
||||||
my_session->tee_replybuf = reply;
|
if(my_session->replies[branch] == 0)
|
||||||
}
|
{
|
||||||
else
|
if(PTR_IS_RESULTSET(ptr))
|
||||||
{
|
{
|
||||||
gwbuf_free(reply);
|
my_session->waiting[branch] = true;
|
||||||
}
|
my_session->eof[branch] = 0;
|
||||||
|
}
|
||||||
if((my_session->branch_session == NULL ||
|
}
|
||||||
my_session->replies >= my_session->min_replies) &&
|
|
||||||
my_session->tee_replybuf != NULL)
|
if(my_session->waiting[branch])
|
||||||
|
{
|
||||||
|
scan_resultset(my_session,branch,reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(branch == PARENT)
|
||||||
|
{
|
||||||
|
ss_dassert(my_session->tee_replybuf == NULL)
|
||||||
|
my_session->tee_replybuf = reply;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
gwbuf_free(reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
my_session->replies[branch]++;
|
||||||
|
|
||||||
|
if(my_session->tee_replybuf != NULL &&
|
||||||
|
(my_session->branch_session == NULL ||
|
||||||
|
my_session->waiting[PARENT] ||
|
||||||
|
(!my_session->waiting[CHILD] && !my_session->waiting[PARENT])))
|
||||||
{
|
{
|
||||||
rc = my_session->up.clientReply (
|
rc = my_session->up.clientReply (
|
||||||
my_session->up.instance,
|
my_session->up.instance,
|
||||||
my_session->up.session,
|
my_session->up.session,
|
||||||
my_session->tee_replybuf);
|
my_session->tee_replybuf);
|
||||||
my_session->replies = 0;
|
|
||||||
my_session->tee_replybuf = NULL;
|
my_session->tee_replybuf = NULL;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -15,6 +15,7 @@ router=readwritesplit
|
|||||||
servers=server1,server2,server3,server4
|
servers=server1,server2,server3,server4
|
||||||
user=maxuser
|
user=maxuser
|
||||||
passwd=maxpwd
|
passwd=maxpwd
|
||||||
|
max_slave_connections=100%
|
||||||
|
|
||||||
[DBShard Router]
|
[DBShard Router]
|
||||||
type=service
|
type=service
|
||||||
@ -29,6 +30,7 @@ router=readwritesplit
|
|||||||
servers=server1,server2,server3,server4
|
servers=server1,server2,server3,server4
|
||||||
user=maxuser
|
user=maxuser
|
||||||
passwd=maxpwd
|
passwd=maxpwd
|
||||||
|
max_slave_connections=100%
|
||||||
filters=Hint
|
filters=Hint
|
||||||
|
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user