summaryrefslogtreecommitdiffstats
path: root/unittest/sql
diff options
context:
space:
mode:
Diffstat (limited to 'unittest/sql')
-rw-r--r--unittest/sql/CMakeLists.txt36
-rw-r--r--unittest/sql/dummy_builtins.cc26
-rw-r--r--unittest/sql/explain_filename-t.cc166
-rw-r--r--unittest/sql/mf_iocache-t.cc474
-rw-r--r--unittest/sql/my_apc-t.cc229
-rw-r--r--unittest/sql/my_json_writer-t.cc128
6 files changed, 1059 insertions, 0 deletions
diff --git a/unittest/sql/CMakeLists.txt b/unittest/sql/CMakeLists.txt
new file mode 100644
index 00000000..b8682de7
--- /dev/null
+++ b/unittest/sql/CMakeLists.txt
@@ -0,0 +1,36 @@
+# Copyright (c) 2006, 2013, Oracle and/or its affiliates. All rights reserved.
+#
+# 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-1335 USA
+
+MY_ADD_TESTS(my_apc LINK_LIBRARIES mysys EXT cc)
+
+INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/sql
+ ${CMAKE_SOURCE_DIR}/include
+ ${CMAKE_SOURCE_DIR}/unittest/mytap
+ ${CMAKE_SOURCE_DIR}/extra/yassl/include)
+ADD_EXECUTABLE(explain_filename-t explain_filename-t.cc dummy_builtins.cc)
+
+
+TARGET_LINK_LIBRARIES(explain_filename-t sql mytap)
+MY_ADD_TEST(explain_filename)
+
+ADD_EXECUTABLE(mf_iocache-t mf_iocache-t.cc ../../sql/mf_iocache_encr.cc)
+TARGET_LINK_LIBRARIES(mf_iocache-t mysys mytap mysys_ssl)
+ADD_DEPENDENCIES(mf_iocache-t GenError)
+MY_ADD_TEST(mf_iocache)
+
+# Json writer needs String which needs sql library
+ADD_EXECUTABLE(my_json_writer-t my_json_writer-t.cc dummy_builtins.cc)
+TARGET_LINK_LIBRARIES(my_json_writer-t sql mytap)
+MY_ADD_TEST(my_json_writer)
diff --git a/unittest/sql/dummy_builtins.cc b/unittest/sql/dummy_builtins.cc
new file mode 100644
index 00000000..31d043d1
--- /dev/null
+++ b/unittest/sql/dummy_builtins.cc
@@ -0,0 +1,26 @@
+/* 2018 MariaDB
+
+ 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-1335 USA */
+
+#include <my_global.h>
+#include <mysql/plugin.h>
+struct st_maria_plugin *mysql_optional_plugins[]=
+{
+ 0
+};
+
+struct st_maria_plugin *mysql_mandatory_plugins[]=
+{
+ 0
+};
diff --git a/unittest/sql/explain_filename-t.cc b/unittest/sql/explain_filename-t.cc
new file mode 100644
index 00000000..32291d5e
--- /dev/null
+++ b/unittest/sql/explain_filename-t.cc
@@ -0,0 +1,166 @@
+/*
+ Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
+
+ 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-1335 USA
+*/
+
+/** Unit test case for the function explain_filename(). */
+
+#include <tap.h>
+#include <mysqld_error.h>
+#include <sql_class.h>
+#include <sql_table.h>
+
+#define BUFLEN 1000
+char to[BUFLEN];
+char from[BUFLEN];
+
+static const char *error_messages_txt[1000];
+static const char **error_messages[1]= { error_messages_txt };
+
+int setup()
+{
+ system_charset_info = &my_charset_utf8mb3_bin;
+ my_default_lc_messages = &my_locale_en_US;
+
+ /* Populate the necessary error messages */
+ error_messages[0][ER_DATABASE_NAME - ER_ERROR_FIRST] = "Database";
+ error_messages[0][ER_TABLE_NAME - ER_ERROR_FIRST] = "Table";
+ error_messages[0][ER_PARTITION_NAME - ER_ERROR_FIRST] = "Partition";
+ error_messages[0][ER_SUBPARTITION_NAME - ER_ERROR_FIRST] = "Subpartition";
+ error_messages[0][ER_TEMPORARY_NAME - ER_ERROR_FIRST] = "Temporary";
+ error_messages[0][ER_RENAMED_NAME - ER_ERROR_FIRST] = "Renamed";
+
+ my_default_lc_messages->errmsgs->errmsgs = error_messages;
+
+ return 0;
+}
+
+void test_1(const char *in, const char *exp, enum_explain_filename_mode mode)
+{
+ char out[BUFLEN];
+
+ uint len1 = explain_filename(0, in, out, BUFLEN, mode);
+
+ /* expected output and actual output must be same */
+ bool pass = (strcmp(exp, out) == 0);
+
+ /* length returned by explain_filename is fine */
+ bool length = (len1 == strlen(exp));
+
+ ok( (pass && length) , "(%d): %s => %s", mode, in, out);
+}
+
+int main(int argc __attribute__((unused)),char *argv[])
+{
+ MY_INIT(argv[0]);
+ setup();
+ plan(22);
+
+ test_1("test/t1.ibd",
+ "Database `test`, Table `t1.ibd`",
+ EXPLAIN_ALL_VERBOSE);
+
+ test_1("test/t1.ibd",
+ "`test`.`t1.ibd`",
+ EXPLAIN_PARTITIONS_VERBOSE);
+
+ test_1("test/t1.ibd",
+ "`test`.`t1.ibd`",
+ EXPLAIN_PARTITIONS_AS_COMMENT);
+
+ test_1("test/t1#TMP#",
+ "Database `test`, Table `t1#TMP#`",
+ EXPLAIN_ALL_VERBOSE);
+
+ test_1("test/#sql-2882.ibd",
+ "Database `test`, Table `#sql-2882.ibd`",
+ EXPLAIN_ALL_VERBOSE);
+
+ test_1("test/t1#REN#",
+ "Database `test`, Table `t1#REN#`",
+ EXPLAIN_ALL_VERBOSE);
+
+ test_1("test/t1@0023REN@0023",
+ "Database `test`, Table `t1#REN#`",
+ EXPLAIN_ALL_VERBOSE);
+
+ test_1("test/t1#p#p1",
+ "Database `test`, Table `t1`, Partition `p1`",
+ EXPLAIN_ALL_VERBOSE);
+
+ test_1("test/t1#P#p1",
+ "`test`.`t1` /* Partition `p1` */",
+ EXPLAIN_PARTITIONS_AS_COMMENT);
+
+ test_1("test/t1#P#p1@00231",
+ "`test`.`t1` /* Partition `p1#1` */",
+ EXPLAIN_PARTITIONS_AS_COMMENT);
+
+ test_1("test/t1#P#p1#SP#sp1",
+ "`test`.`t1` /* Partition `p1`, Subpartition `sp1` */",
+ EXPLAIN_PARTITIONS_AS_COMMENT);
+
+ test_1("test/t1#p1#SP#sp1",
+ "`test`.`t1#p1#SP#sp1`",
+ EXPLAIN_PARTITIONS_AS_COMMENT);
+
+ test_1("test/t1#p#p1@00232#SP#sp1@00231#REN#",
+ "`test`.`t1` /* Renamed Partition `p1#2`, Subpartition `sp1#1` */",
+ EXPLAIN_PARTITIONS_AS_COMMENT);
+
+ test_1("test/t1#p#p1#SP#sp1#TMP#",
+ "`test`.`t1` /* Temporary Partition `p1`, Subpartition `sp1` */",
+ EXPLAIN_PARTITIONS_AS_COMMENT);
+
+ test_1("test/#sql-t1#P#p1#SP#sp1#TMP#",
+ "`test`.`#sql-t1#P#p1#SP#sp1#TMP#` /* Temporary Partition `p1`, Subpartition `sp1` */",
+ EXPLAIN_PARTITIONS_AS_COMMENT);
+
+ test_1("test/#sql-t1#P#p1#SP#sp1",
+ "`test`.`#sql-t1#P#p1#SP#sp1` /* Partition `p1`, Subpartition `sp1` */",
+ EXPLAIN_PARTITIONS_AS_COMMENT);
+
+ test_1("test/#sqlx-33",
+ "`test`.`#sqlx-33`",
+ EXPLAIN_PARTITIONS_AS_COMMENT);
+
+ test_1("test/#mysql50#t",
+ "`test`.`#mysql50#t`",
+ EXPLAIN_PARTITIONS_AS_COMMENT);
+
+ test_1("#mysql50#t",
+ "`#mysql50#t`",
+ EXPLAIN_PARTITIONS_AS_COMMENT);
+
+ test_1("@0023t",
+ "`#t`",
+ EXPLAIN_PARTITIONS_AS_COMMENT);
+
+ test_1("test/t@0023",
+ "`test`.`t#`",
+ EXPLAIN_PARTITIONS_AS_COMMENT);
+
+ /*
+ If a character not allowed in my_charset_filename is encountered,
+ then it will not be converted to system_charset_info!
+ */
+ test_1("test/t@0023#",
+ "`test`.`t@0023#`",
+ EXPLAIN_PARTITIONS_AS_COMMENT);
+
+ my_end(0);
+ return exit_status();
+}
+
diff --git a/unittest/sql/mf_iocache-t.cc b/unittest/sql/mf_iocache-t.cc
new file mode 100644
index 00000000..cc97d3b2
--- /dev/null
+++ b/unittest/sql/mf_iocache-t.cc
@@ -0,0 +1,474 @@
+/* Copyright (C) 2015 MariaDB
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1335 USA */
+
+#include <my_global.h>
+#include <my_sys.h>
+#include <my_crypt.h>
+#include <tap.h>
+
+/*** tweaks and stubs for encryption code to compile ***************/
+#define KEY_SIZE (128/8)
+
+my_bool encrypt_tmp_files;
+int init_io_cache_encryption();
+
+uint encryption_key_get_latest_version_func(uint)
+{
+ return 1;
+}
+
+uint encryption_key_id_exists_func(uint)
+{
+ return 1;
+}
+
+uint encryption_key_version_exists_func(uint, uint)
+{
+ return 1;
+}
+
+uint encryption_key_get_func(uint, uint, uchar* key, uint* size)
+{
+ if (*size < KEY_SIZE)
+ {
+ *size= KEY_SIZE;
+ return ENCRYPTION_KEY_BUFFER_TOO_SMALL;
+ }
+ memset(key, KEY_SIZE, *size= KEY_SIZE);
+ return 0;
+}
+
+uint encryption_ctx_size_func(unsigned int, unsigned int)
+{
+ return MY_AES_CTX_SIZE;
+}
+
+
+#ifdef HAVE_EncryptAes128Gcm
+enum my_aes_mode aes_mode= MY_AES_GCM;
+#else
+enum my_aes_mode aes_mode= MY_AES_CBC;
+#endif
+
+int encryption_ctx_init_func(void *ctx, const unsigned char* key, unsigned int klen,
+ const unsigned char* iv, unsigned int ivlen,
+ int flags, unsigned int key_id,
+ unsigned int key_version)
+{
+ return my_aes_crypt_init(ctx, aes_mode, flags, key, klen, iv, ivlen);
+}
+
+uint encryption_encrypted_length_func(unsigned int slen, unsigned int key_id, unsigned int key_version)
+{
+ return my_aes_get_size(aes_mode, slen);
+}
+
+struct encryption_service_st encryption_handler=
+{
+ encryption_key_get_latest_version_func,
+ encryption_key_get_func,
+ encryption_ctx_size_func,
+ encryption_ctx_init_func,
+ my_aes_crypt_update,
+ my_aes_crypt_finish,
+ encryption_encrypted_length_func
+};
+
+void sql_print_information(const char *format, ...)
+{
+}
+
+void sql_print_error(const char *format, ...)
+{
+}
+
+/*** end of encryption tweaks and stubs ****************************/
+
+static IO_CACHE info;
+#define CACHE_SIZE 16384
+
+#define INFO_TAIL ", pos_in_file = %llu, pos_in_mem = %lu", \
+ info.pos_in_file, (ulong) ((info.type == READ_CACHE ? info.read_pos : info.write_pos) - info.request_pos)
+
+#define FILL 0x5A
+
+int data_bad(const uchar *buf, size_t len)
+{
+ const uchar *end= buf + len;
+ while (buf < end)
+ if (*buf++ != FILL)
+ return 1;
+ return 0;
+}
+
+void temp_io_cache()
+{
+ int res;
+ uchar buf[CACHE_SIZE + 200];
+ memset(buf, FILL, sizeof(buf));
+
+ diag("temp io_cache with%s encryption", encrypt_tmp_files?"":"out");
+
+ init_io_cache_encryption();
+
+ res= open_cached_file(&info, 0, 0, CACHE_SIZE, 0);
+ ok(res == 0, "open_cached_file" INFO_TAIL);
+
+ res= my_b_write(&info, buf, 100);
+ ok(res == 0 && info.pos_in_file == 0, "small write" INFO_TAIL );
+
+ res= my_b_write(&info, buf, sizeof(buf));
+ ok(res == 0 && info.pos_in_file == CACHE_SIZE, "large write" INFO_TAIL);
+
+ res= reinit_io_cache(&info, WRITE_CACHE, 250, 0, 0);
+ ok(res == 0, "reinit with rewind" INFO_TAIL);
+
+ res= my_b_write(&info, buf, sizeof(buf));
+ ok(res == 0, "large write" INFO_TAIL);
+
+ res= my_b_flush_io_cache(&info, 1);
+ ok(res == 0, "flush" INFO_TAIL);
+
+ res= reinit_io_cache(&info, READ_CACHE, 0, 0, 0);
+ ok(res == 0, "reinit READ_CACHE" INFO_TAIL);
+
+ res= (int)my_pread(info.file, buf, 50, 50, MYF(MY_NABP));
+ ok(res == 0 && data_bad(buf, 50) == encrypt_tmp_files,
+ "file must be %sreadable", encrypt_tmp_files ?"un":"");
+
+ res= my_b_read(&info, buf, 50) || data_bad(buf, 50);
+ ok(res == 0 && info.pos_in_file == 0, "small read" INFO_TAIL);
+
+ res= my_b_read(&info, buf, sizeof(buf)) || data_bad(buf, sizeof(buf));
+ ok(res == 0 && info.pos_in_file == CACHE_SIZE, "large read" INFO_TAIL);
+
+ close_cached_file(&info);
+}
+
+void mdev9044()
+{
+ int res;
+ uchar buf[CACHE_SIZE + 200];
+
+ diag("MDEV-9044 Binlog corruption in Galera");
+
+ res= open_cached_file(&info, 0, 0, CACHE_SIZE, 0);
+ ok(res == 0, "open_cached_file" INFO_TAIL);
+
+ res= my_b_write(&info, USTRING_WITH_LEN("first write\0"));
+ ok(res == 0, "first write" INFO_TAIL);
+
+ res= my_b_flush_io_cache(&info, 1);
+ ok(res == 0, "flush" INFO_TAIL);
+
+ res= reinit_io_cache(&info, WRITE_CACHE, 0, 0, 0);
+ ok(res == 0, "reinit WRITE_CACHE" INFO_TAIL);
+
+ res= my_b_write(&info, USTRING_WITH_LEN("second write\0"));
+ ok(res == 0, "second write" INFO_TAIL );
+
+ res= reinit_io_cache(&info, READ_CACHE, 0, 0, 0);
+ ok(res == 0, "reinit READ_CACHE" INFO_TAIL);
+
+ res= (int)my_b_fill(&info);
+ ok(res == 0, "fill" INFO_TAIL);
+
+ res= reinit_io_cache(&info, READ_CACHE, 0, 0, 0);
+ ok(res == 0, "reinit READ_CACHE" INFO_TAIL);
+
+ res= my_b_read(&info, buf, sizeof(buf));
+ ok(res == 1 && strcmp((char*)buf, "second write") == 0, "read '%s'", buf);
+
+ close_cached_file(&info);
+}
+
+/* 2 Reads (with my_b_fill) in cache makes second read to fail */
+void mdev10259()
+{
+ int res;
+ uchar buf[200];
+ memset(buf, FILL, sizeof(buf));
+
+ diag("MDEV-10259- mysqld crash with certain statement length and order with"
+ " Galera and encrypt-tmp-files=1");
+
+ init_io_cache_encryption();
+
+ res= open_cached_file(&info, 0, 0, CACHE_SIZE, 0);
+ ok(res == 0, "open_cached_file" INFO_TAIL);
+
+ res= my_b_write(&info, buf, sizeof(buf));
+ ok(res == 0 && info.pos_in_file == 0, "200 write" INFO_TAIL);
+
+ res= my_b_flush_io_cache(&info, 1);
+ ok(res == 0, "flush" INFO_TAIL);
+
+ my_off_t saved_pos= my_b_tell(&info);
+ res= reinit_io_cache(&info, READ_CACHE, 0, 0, 0);
+ ok(res == 0, "reinit READ_CACHE" INFO_TAIL);
+
+ size_t s= my_b_fill(&info);
+ ok(s == 200, "fill" INFO_TAIL);
+
+ s= my_b_fill(&info);
+ ok(s == 0, "fill" INFO_TAIL);
+
+ s= my_b_fill(&info);
+ ok(s == 0, "fill" INFO_TAIL);
+
+ res= reinit_io_cache(&info, WRITE_CACHE, saved_pos, 0, 0);
+ ok(res == 0, "reinit WRITE_CACHE" INFO_TAIL);
+
+ res= reinit_io_cache(&info, READ_CACHE, 0, 0, 0);
+ ok(res == 0, "reinit READ_CACHE" INFO_TAIL);
+
+ ok(200 == my_b_bytes_in_cache(&info),"my_b_bytes_in_cache == 200");
+
+ s= my_b_fill(&info);
+ ok(s == 0, "fill" INFO_TAIL);
+
+ s= my_b_fill(&info);
+ ok(s == 0, "fill" INFO_TAIL);
+
+ s= my_b_fill(&info);
+ ok(s == 0, "fill" INFO_TAIL);
+
+ res= reinit_io_cache(&info, WRITE_CACHE, saved_pos, 0, 0);
+ ok(res == 0, "reinit WRITE_CACHE" INFO_TAIL);
+
+ res= reinit_io_cache(&info, READ_CACHE, 0, 0, 0);
+ ok(res == 0, "reinit READ_CACHE" INFO_TAIL);
+
+ ok(200 == my_b_bytes_in_cache(&info),"my_b_bytes_in_cache == 200");
+
+ res= my_b_read(&info, buf, sizeof(buf)) || data_bad(buf, sizeof(buf));
+ ok(res == 0 && info.pos_in_file == 0, "large read" INFO_TAIL);
+
+ close_cached_file(&info);
+
+}
+
+void mdev14014()
+{
+ int res;
+ uchar buf_o[200];
+ uchar buf_i[200];
+ memset(buf_i, 0, sizeof( buf_i));
+ memset(buf_o, FILL, sizeof(buf_o));
+
+ diag("MDEV-14014 Dump thread reads past last 'officially' written byte");
+
+ init_io_cache_encryption();
+
+ res= open_cached_file(&info, 0, 0, CACHE_SIZE, 0);
+ ok(res == 0, "open_cached_file" INFO_TAIL);
+
+ res= my_b_write(&info, buf_o, sizeof(buf_o));
+ ok(res == 0, "buffer is written" INFO_TAIL);
+
+ res= my_b_flush_io_cache(&info, 1);
+ ok(res == 0, "flush" INFO_TAIL);
+
+ res= reinit_io_cache(&info, READ_CACHE, 0, 0, 0);
+ ok(res == 0, "reinit READ_CACHE" INFO_TAIL);
+
+ info.end_of_file= 100;
+ res= my_b_read(&info, buf_i, sizeof(buf_i));
+ ok(res == 1 && buf_i[100] == 0 && buf_i[200-1] == 0,
+ "short read leaves buf_i[100..200-1] == 0");
+
+ close_cached_file(&info);
+}
+
+void mdev17133()
+{
+ my_off_t res;
+ int k;
+ const int eof_iter=4, read_iter= 4;
+ uchar buf_i[1024*256]; // read
+ uchar buf_o[sizeof(buf_i)]; // write
+ const size_t eof_block_size= sizeof(buf_o) / eof_iter;
+ const size_t read_size= eof_block_size / read_iter;
+ size_t total;
+
+ srand((uint) time(NULL));
+ memset(buf_i, 0, sizeof( buf_i));
+ memset(buf_o, FILL, sizeof(buf_o));
+
+ diag("MDEV-17133 Dump thread reads from the past");
+
+ init_io_cache_encryption();
+
+ res= open_cached_file(&info, 0, 0, CACHE_SIZE, 0);
+ ok(res == 0, "open_cached_file" INFO_TAIL);
+
+ res= my_b_write(&info, buf_o, sizeof(buf_o));
+ ok(res == 0, "buffer is written" INFO_TAIL);
+ res= my_b_tell(&info);
+ ok(res == sizeof(buf_o), "cache size as expected");
+
+ res= my_b_flush_io_cache(&info, 1);
+ ok(res == 0, "flush" INFO_TAIL);
+ res= reinit_io_cache(&info, READ_CACHE, 0, 0, 0);
+ ok(res == 0, "reinit READ_CACHE" INFO_TAIL);
+
+ // read the written data by chunks of variable size eof_iter times
+ for (k= eof_iter, info.end_of_file=0, total= 0; k; k--)
+ {
+ int i;
+ size_t curr_read_size;
+ info.end_of_file=
+ k == 1 ? sizeof(buf_o) :
+ MY_MIN(sizeof(buf_o),
+ info.end_of_file + eof_block_size +
+ // plus 25% of block for randomization to the average
+ eof_block_size/4 - rand() % (eof_block_size/2));
+
+ // read a chunk by blocks of variable size read_iter times
+ // the last block completes the current chunk
+ for (i= 0; i < read_iter; i++, total += curr_read_size)
+ {
+ char buf_check[eof_block_size];
+ size_t a,b;
+
+ a= (size_t)(info.end_of_file - total);
+ b= read_size + read_size/4 - rand() % (read_size/2);
+ curr_read_size= (i == read_iter - 1) ? a :
+ MY_MIN(a, b);
+
+ DBUG_ASSERT(curr_read_size <= info.end_of_file - total);
+
+ res= my_b_read(&info, buf_i + total, MY_MIN(19, curr_read_size));
+ ok(res == 0, "read of 19");
+ // mark read bytes in the used part of the cache buffer
+ memset(info.buffer, 0, info.read_pos - info.buffer);
+
+ // random size 2nd read
+ res= my_b_read(&info, buf_i + total + MY_MIN(19, curr_read_size),
+ 19 >= curr_read_size ? 0 : curr_read_size - 19);
+ ok(res == 0, "rest of read %zu", curr_read_size - 19);
+ // mark read bytes in the used part of the cache buffer
+ memset(info.buffer, 0, info.read_pos - info.buffer);
+
+ // check that no marked bytes are read
+ memset(buf_check, FILL, curr_read_size);
+ ok(memcmp(buf_i + total, buf_check, curr_read_size) == 0,
+ "read correct data");
+ }
+ ok(info.pos_in_file + (info.read_end - info.buffer) == info.end_of_file,
+ "cache is read up to eof");
+ ok(total == info.end_of_file, "total matches eof");
+ }
+ ok(total == sizeof(buf_i), "read total size match");
+ ok(buf_i[sizeof(buf_i) - 1] == FILL, "data read correctly");
+
+ close_cached_file(&info);
+}
+
+
+void mdev10963()
+{
+ int res;
+ uint n_checks= 8;
+ uchar buf[1024 * 512];
+ uint n_frag= sizeof(buf)/(2 * CACHE_SIZE);
+ FILE *file;
+ myf my_flags= MYF(MY_WME);
+ const char *file_name="cache.log";
+
+ memset(buf, FILL, sizeof(buf));
+ diag("MDEV-10963 Fragmented BINLOG query");
+
+ init_io_cache_encryption();
+ srand((uint) time(NULL));
+
+ /* copying source */
+ res= open_cached_file(&info, 0, 0, CACHE_SIZE, 0);
+ ok(res == 0, "open_cached_file" INFO_TAIL);
+ res= my_b_write(&info, buf, sizeof(buf));
+
+ ulonglong total_size= my_b_tell(&info);
+ ok(res == 0 && total_size == sizeof(buf), "cache is written");
+
+ /* destination */
+ file= my_fopen(file_name, O_RDWR | O_TRUNC | O_CREAT, my_flags);
+ ok(my_fileno(file) > 0, "opened file fd = %d", my_fileno(file));
+
+ /*
+ For n_checks times verify a sequence of copying with random fragment
+ size ranging from zero to about the double of the cache read buffer size.
+ */
+ for (; n_checks; n_checks--, rewind(file))
+ {
+ // copied size is an estimate can be incremeneted to greater than total_size
+ ulong copied_size= 0;
+
+ res= reinit_io_cache(&info, READ_CACHE, 0L, FALSE, FALSE);
+ ok(res == 0, "cache turned to read");
+
+ for (ulong i= 0, curr_size= 0; i < n_frag; i++, copied_size += curr_size)
+ {
+ curr_size= rand() % (2 * (total_size - copied_size) / (n_frag - i));
+
+ DBUG_ASSERT(curr_size <= total_size - copied_size || i == n_frag - 1);
+
+ res= my_b_copy_to_file(&info, file, curr_size);
+ ok(res == 0, "%lu of the cache copied to file", curr_size);
+ }
+ /*
+ Regardless of total_size <> copied_size the function succeeds:
+ when total_size < copied_size the huge overflowed value of the last
+ argument is ignored because nothing already left uncopied in the cache.
+ */
+ res= my_b_copy_to_file(&info, file, (size_t) total_size - copied_size);
+ ok(res == 0, "%llu of the cache copied to file", total_size - copied_size);
+ ok(my_ftell(file, my_flags) == sizeof(buf),
+ "file written in %d fragments", n_frag+1);
+
+ res= reinit_io_cache(&info, WRITE_CACHE, total_size, 0, 0);
+ ok(res == 0 && my_b_tell(&info) == sizeof(buf), "cache turned to write");
+ }
+ close_cached_file(&info);
+ my_fclose(file, my_flags);
+ my_delete(file_name, MYF(MY_WME));
+}
+
+int main(int argc __attribute__((unused)),char *argv[])
+{
+ MY_INIT(argv[0]);
+ plan(277);
+
+ /* temp files with and without encryption */
+ encrypt_tmp_files= 1;
+ temp_io_cache();
+
+ encrypt_tmp_files= 0;
+ temp_io_cache();
+
+ /* regression tests */
+ mdev9044();
+
+ encrypt_tmp_files= 1;
+ mdev10259();
+ encrypt_tmp_files= 0;
+
+ mdev14014();
+ mdev17133();
+ mdev10963();
+
+ my_end(0);
+ return exit_status();
+}
+
diff --git a/unittest/sql/my_apc-t.cc b/unittest/sql/my_apc-t.cc
new file mode 100644
index 00000000..c08e7281
--- /dev/null
+++ b/unittest/sql/my_apc-t.cc
@@ -0,0 +1,229 @@
+/*
+ Copyright (c) 2012, Monty Program Ab
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1335 USA */
+
+/*
+ This file does standalone APC system tests.
+*/
+#include <my_global.h>
+#include <my_pthread.h>
+#include <my_sys.h>
+
+#include <stdio.h>
+
+#include <tap.h>
+
+/*
+ A fake THD with enter_cond/exit_cond and some other members.
+*/
+PSI_stage_info stage_show_explain;
+class THD
+{
+ mysql_mutex_t* thd_mutex;
+public:
+ bool killed;
+
+ THD() : killed(FALSE) {}
+ inline const char* ENTER_COND(mysql_cond_t *cond, mysql_mutex_t* mutex,
+ PSI_stage_info*, PSI_stage_info*)
+ {
+ mysql_mutex_assert_owner(mutex);
+ thd_mutex= mutex;
+ return NULL;
+ }
+ inline void EXIT_COND(PSI_stage_info*)
+ {
+ mysql_mutex_unlock(thd_mutex);
+ }
+};
+
+#include "../sql/my_apc.h"
+
+#define MY_APC_STANDALONE 1
+#include "../sql/my_apc.cc"
+
+volatile bool started= FALSE;
+volatile bool service_should_exit= FALSE;
+volatile bool requestors_should_exit=FALSE;
+
+/* Counters for APC calls */
+int apcs_served= 0;
+int apcs_missed=0;
+int apcs_timed_out=0;
+mysql_mutex_t apc_counters_mutex;
+
+inline void increment_counter(int *var)
+{
+ mysql_mutex_lock(&apc_counters_mutex);
+ *var= *var+1;
+ mysql_mutex_unlock(&apc_counters_mutex);
+}
+
+volatile bool have_errors= false;
+
+Apc_target apc_target;
+mysql_mutex_t target_mutex;
+
+int int_rand(int size)
+{
+ return (int) (0.5 + ((double)rand() / RAND_MAX) * size);
+}
+
+/*
+ APC target thread (the one that will serve the APC requests). We will have
+ one target.
+*/
+void *test_apc_service_thread(void *ptr)
+{
+ my_thread_init();
+ mysql_mutex_init(0, &target_mutex, MY_MUTEX_INIT_FAST);
+ apc_target.init(&target_mutex);
+ apc_target.enable();
+ started= TRUE;
+ diag("test_apc_service_thread started");
+ while (!service_should_exit)
+ {
+ //apc_target.disable();
+ my_sleep(10000);
+ //apc_target.enable();
+ for (int i = 0; i < 10 && !service_should_exit; i++)
+ {
+ apc_target.process_apc_requests();
+ my_sleep(int_rand(30));
+ }
+ }
+ apc_target.disable();
+ apc_target.destroy();
+ mysql_mutex_destroy(&target_mutex);
+ my_thread_end();
+ pthread_exit(0);
+ return NULL;
+}
+
+
+/*
+ One APC request (to write 'value' into *where_to)
+*/
+class Apc_order : public Apc_target::Apc_call
+{
+public:
+ int value; // The value
+ int *where_to; // Where to write it
+ Apc_order(int a, int *b) : value(a), where_to(b) {}
+
+ void call_in_target_thread()
+ {
+ my_sleep(int_rand(1000));
+ *where_to = value;
+ increment_counter(&apcs_served);
+ }
+};
+
+
+/*
+ APC requestor thread. It makes APC requests, and checks if they were actually
+ executed.
+*/
+void *test_apc_requestor_thread(void *ptr)
+{
+ my_thread_init();
+ diag("test_apc_requestor_thread started");
+ THD my_thd;
+
+ while (!requestors_should_exit)
+ {
+ int dst_value= 0;
+ int src_value= int_rand(4*1000*100);
+ /* Create an APC to do "dst_value= src_value" assignment */
+ Apc_order apc_order(src_value, &dst_value);
+ bool timed_out;
+
+ mysql_mutex_lock(&target_mutex);
+ bool res= apc_target.make_apc_call(&my_thd, &apc_order, 60, &timed_out);
+ if (res)
+ {
+ if (timed_out)
+ increment_counter(&apcs_timed_out);
+ else
+ increment_counter(&apcs_missed);
+
+ if (dst_value != 0)
+ {
+ diag("APC was done even though return value says it wasnt!");
+ have_errors= true;
+ }
+ }
+ else
+ {
+ if (dst_value != src_value)
+ {
+ diag("APC was not done even though return value says it was!");
+ have_errors= true;
+ }
+ }
+ //my_sleep(300);
+ }
+ diag("test_apc_requestor_thread exiting");
+ my_thread_end();
+ return NULL;
+}
+
+/* Number of APC requestor threads */
+const int N_THREADS=23;
+
+
+int main(int args, char **argv)
+{
+ pthread_t service_thr;
+ pthread_t request_thr[N_THREADS];
+ int i;
+
+ my_thread_global_init();
+
+ mysql_mutex_init(0, &apc_counters_mutex, MY_MUTEX_INIT_FAST);
+
+ plan(1);
+ diag("Testing APC delivery and execution");
+
+ pthread_create(&service_thr, NULL, test_apc_service_thread, (void*)NULL);
+ while (!started)
+ my_sleep(1000);
+ for (i = 0; i < N_THREADS; i++)
+ pthread_create(&request_thr[i], NULL, test_apc_requestor_thread, (void*)NULL);
+
+ for (i = 0; i < 15; i++)
+ {
+ my_sleep(500*1000);
+ diag("%d APCs served %d missed", apcs_served, apcs_missed);
+ }
+ diag("Shutting down requestors");
+ requestors_should_exit= TRUE;
+ for (i = 0; i < N_THREADS; i++)
+ pthread_join(request_thr[i], NULL);
+
+ diag("Shutting down service");
+ service_should_exit= TRUE;
+ pthread_join(service_thr, NULL);
+
+ mysql_mutex_destroy(&apc_counters_mutex);
+
+ diag("Done");
+ my_thread_end();
+ my_thread_global_end();
+
+ ok1(!have_errors);
+ return exit_status();
+}
+
diff --git a/unittest/sql/my_json_writer-t.cc b/unittest/sql/my_json_writer-t.cc
new file mode 100644
index 00000000..6398a589
--- /dev/null
+++ b/unittest/sql/my_json_writer-t.cc
@@ -0,0 +1,128 @@
+/*
+ Copyright (c) 2021, MariaDB Corporation.
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1335 USA */
+
+#include <my_global.h>
+#include <my_pthread.h>
+#include <my_sys.h>
+#include <stdio.h>
+#include <tap.h>
+
+/*
+ Unit tests for class Json_writer. At the moment there are only tests for the
+ "Fail an assertion if one attempts to produce invalid JSON" feature.
+*/
+
+struct TABLE;
+struct JOIN_TAB;
+class Json_writer;
+
+
+/* Several fake objects */
+class Opt_trace
+{
+public:
+ void enable_tracing_if_required() {}
+ void disable_tracing_if_required() {}
+ Json_writer *get_current_json() { return nullptr; }
+};
+
+class THD
+{
+public:
+ Opt_trace opt_trace;
+};
+
+#define JSON_WRITER_UNIT_TEST
+#include "../sql/my_json_writer.h"
+#include "../sql/my_json_writer.cc"
+
+int main(int args, char **argv)
+{
+ plan(NO_PLAN);
+ diag("Testing Json_writer checks");
+
+ {
+ Json_writer w;
+ w.start_object();
+ w.add_member("foo");
+ w.end_object();
+ ok(w.invalid_json, "Started a name but didn't add a value");
+ }
+
+ {
+ Json_writer w;
+ w.start_object();
+ w.add_ull(123);
+ ok(w.invalid_json, "Unnamed value in an object");
+ }
+
+ {
+ Json_writer w;
+ w.start_array();
+ w.add_member("bebebe").add_ull(345);
+ ok(w.invalid_json, "Named member in array");
+ }
+
+ {
+ Json_writer w;
+ w.start_object();
+ w.start_array();
+ ok(w.invalid_json, "Unnamed array in an object");
+ }
+
+ {
+ Json_writer w;
+ w.start_object();
+ w.start_object();
+ ok(w.invalid_json, "Unnamed object in an object");
+ }
+
+ {
+ Json_writer w;
+ w.start_array();
+ w.add_member("zzz");
+ w.start_object();
+ ok(w.invalid_json, "Named object in an array");
+ }
+ {
+ Json_writer w;
+ w.start_array();
+ w.add_member("zzz");
+ w.start_array();
+ ok(w.invalid_json, "Named array in an array");
+ }
+
+ {
+ Json_writer w;
+ w.start_array();
+ w.end_object();
+ ok(w.invalid_json, "JSON object end of array");
+ }
+
+ {
+ Json_writer w;
+ w.start_object();
+ w.end_array();
+ ok(w.invalid_json, "JSON array end of object");
+ }
+
+
+
+ diag("Done");
+
+ return exit_status();
+}
+