Merge branch '2.3' into 2.4
This commit is contained in:
3
system-test/cdc_datatypes/CMakeLists.txt
Normal file
3
system-test/cdc_datatypes/CMakeLists.txt
Normal file
@ -0,0 +1,3 @@
|
||||
add_test_executable(cdc_datatypes.cpp cdc_datatypes avro LABELS avrorouter binlogrouter BREAKS_REPL REPL_BACKEND)
|
||||
add_library(cdc_result cdc_result.cpp)
|
||||
target_link_libraries(cdc_datatypes cdc_result)
|
373
system-test/cdc_datatypes/cdc_datatypes.cpp
Normal file
373
system-test/cdc_datatypes/cdc_datatypes.cpp
Normal file
@ -0,0 +1,373 @@
|
||||
/**
|
||||
* @file cdc_connect.cpp Test the CDC protocol
|
||||
*/
|
||||
|
||||
#include <maxtest/testconnections.hh>
|
||||
#include <cdc_connector.h>
|
||||
#include "cdc_result.h"
|
||||
#include <iostream>
|
||||
#include <stdio.h>
|
||||
|
||||
static const char* table_name = "test.type";
|
||||
static const char* field_name = "a";
|
||||
|
||||
static const char* integer_types[] =
|
||||
{
|
||||
"TINYINT",
|
||||
"SMALLINT",
|
||||
"MEDIUMINT",
|
||||
"INT",
|
||||
"BIGINT",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char* integer_values[] =
|
||||
{
|
||||
"0",
|
||||
"1",
|
||||
"-1",
|
||||
"20",
|
||||
"-20",
|
||||
"NULL",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char* decimal_types[] =
|
||||
{
|
||||
"FLOAT",
|
||||
"DOUBLE",
|
||||
"DECIMAL(10, 2)",
|
||||
"DECIMAL(32, 2)",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char* decimal_values[] =
|
||||
{
|
||||
"0",
|
||||
"1.5",
|
||||
"-1.5",
|
||||
"20.5",
|
||||
"-20.5",
|
||||
"NULL",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char* string_types[] =
|
||||
{
|
||||
"CHAR(50)",
|
||||
"VARCHAR(50)",
|
||||
"TINYTEXT",
|
||||
"TEXT",
|
||||
"MEDIUMTEXT",
|
||||
"LONGTEXT",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char* string_values[] =
|
||||
{
|
||||
"\"Hello world!\"",
|
||||
"\"The quick brown fox jumps over the lazy dog\"",
|
||||
"NULL",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char* binary_types[] =
|
||||
{
|
||||
"BINARY(50)",
|
||||
"VARBINARY(50)",
|
||||
"TINYBLOB",
|
||||
"BLOB",
|
||||
"MEDIUMBLOB",
|
||||
"LONGBLOB",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char* binary_values[] =
|
||||
{
|
||||
"\"Hello world!\"",
|
||||
"\"The quick brown fox jumps over the lazy dog\"",
|
||||
"NULL",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char* datetime_types[] =
|
||||
{
|
||||
"DATETIME",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char* datetime_values[] =
|
||||
{
|
||||
"'2018-01-01 11:11:11'",
|
||||
"'0-00-00 00:00:00'",
|
||||
"NULL",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char* datetime2_types[] =
|
||||
{
|
||||
"DATETIME(6)",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char* datetime2_values[] =
|
||||
{
|
||||
"'2018-01-01 11:11:11.000001'",
|
||||
"'2018-01-01 11:11:11.000010'",
|
||||
"'2018-01-01 11:11:11.000100'",
|
||||
"'2018-01-01 11:11:11.001000'",
|
||||
"'2018-01-01 11:11:11.010000'",
|
||||
"'2018-01-01 11:11:11.100000'",
|
||||
"'0-00-00 00:00:00.000000'",
|
||||
"NULL",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char* timestamp_types[] =
|
||||
{
|
||||
"TIMESTAMP",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char* timestamp_values[] =
|
||||
{
|
||||
"'2018-01-01 11:11:11'",
|
||||
"'0-00-00 00:00:00'",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char* timestamp2_types[] =
|
||||
{
|
||||
"TIMESTAMP(6)",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char* timestamp2_values[] =
|
||||
{
|
||||
"'2018-01-01 11:11:11.000001'",
|
||||
"'2018-01-01 11:11:11.000010'",
|
||||
"'2018-01-01 11:11:11.000100'",
|
||||
"'2018-01-01 11:11:11.001000'",
|
||||
"'2018-01-01 11:11:11.010000'",
|
||||
"'2018-01-01 11:11:11.100000'",
|
||||
"'0-00-00 00:00:00.000000'",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char* date_types[] =
|
||||
{
|
||||
"DATE",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char* date_values[] =
|
||||
{
|
||||
"'2018-01-01'",
|
||||
"'0-00-00'",
|
||||
"NULL",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char* time_types[] =
|
||||
{
|
||||
"TIME",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char* time_values[] =
|
||||
{
|
||||
"'12:00:00'",
|
||||
"NULL",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char* time2_types[] =
|
||||
{
|
||||
"TIME(6)",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char* time2_values[] =
|
||||
{
|
||||
"'12:00:00.000001'",
|
||||
"'12:00:00.000010'",
|
||||
"'12:00:00.000100'",
|
||||
"'12:00:00.001000'",
|
||||
"'12:00:00.010000'",
|
||||
"'12:00:00.100000'",
|
||||
"NULL",
|
||||
NULL
|
||||
};
|
||||
|
||||
struct
|
||||
{
|
||||
const char** types;
|
||||
const char** values;
|
||||
} test_set[]
|
||||
{
|
||||
{integer_types, integer_values},
|
||||
{decimal_types, decimal_values},
|
||||
{string_types, string_values},
|
||||
{binary_types, binary_values},
|
||||
{datetime_types, datetime_values},
|
||||
{timestamp_types, timestamp_values},
|
||||
{date_types, date_values},
|
||||
{time_types, time_values},
|
||||
{datetime2_types, datetime2_values},
|
||||
{timestamp2_types, timestamp2_values},
|
||||
{time2_types, time2_values},
|
||||
{0, 0}
|
||||
};
|
||||
|
||||
void insert_data(TestConnections& test, const char* table, const char* type, const char** values)
|
||||
{
|
||||
test.repl->connect();
|
||||
execute_query(test.repl->nodes[0], "CREATE TABLE %s(%s %s)", table, field_name, type);
|
||||
|
||||
for (int i = 0; values[i]; i++)
|
||||
{
|
||||
execute_query(test.repl->nodes[0], "INSERT INTO %s VALUES (%s)", table, values[i]);
|
||||
}
|
||||
|
||||
execute_query(test.repl->nodes[0], "DROP TABLE %s", table);
|
||||
test.repl->close_connections();
|
||||
}
|
||||
|
||||
std::string type_to_table_name(const char* type)
|
||||
{
|
||||
std::string name = table_name;
|
||||
name += "_";
|
||||
name += type;
|
||||
|
||||
size_t offset = name.find('(');
|
||||
|
||||
if (offset != std::string::npos)
|
||||
{
|
||||
name[offset] = '_';
|
||||
|
||||
offset = name.find(')');
|
||||
|
||||
if (offset != std::string::npos)
|
||||
{
|
||||
name = name.substr(0, offset);
|
||||
}
|
||||
|
||||
offset = name.find(',');
|
||||
|
||||
if (offset != std::string::npos)
|
||||
{
|
||||
name = name.substr(0, offset);
|
||||
}
|
||||
}
|
||||
|
||||
offset = name.find(' ');
|
||||
|
||||
if (offset != std::string::npos)
|
||||
{
|
||||
name = name.substr(0, offset);
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
static std::string unquote(std::string str)
|
||||
{
|
||||
if (str[0] == '\"' || str[0] == '\'')
|
||||
{
|
||||
str = str.substr(1, str.length() - 2);
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
bool run_test(TestConnections& test)
|
||||
{
|
||||
bool rval = true;
|
||||
|
||||
test.tprintf("Inserting data");
|
||||
for (int x = 0; test_set[x].types; x++)
|
||||
{
|
||||
for (int i = 0; test_set[x].types[i]; i++)
|
||||
{
|
||||
std::string name = type_to_table_name(test_set[x].types[i]);
|
||||
insert_data(test, name.c_str(), test_set[x].types[i], test_set[x].values);
|
||||
}
|
||||
}
|
||||
|
||||
test.tprintf("Waiting for avrorouter to process data");
|
||||
test.repl->connect();
|
||||
execute_query(test.repl->nodes[0], "FLUSH LOGS");
|
||||
test.repl->close_connections();
|
||||
sleep(10);
|
||||
|
||||
for (int x = 0; test_set[x].types; x++)
|
||||
{
|
||||
for (int i = 0; test_set[x].types[i]; i++)
|
||||
{
|
||||
test.set_timeout(60);
|
||||
test.tprintf("Testing type: %s", test_set[x].types[i]);
|
||||
std::string name = type_to_table_name(test_set[x].types[i]);
|
||||
CDC::Connection conn(test.maxscales->IP[0], 4001, "skysql", "skysql");
|
||||
|
||||
if (conn.connect(name))
|
||||
{
|
||||
for (int j = 0; test_set[x].values[j]; j++)
|
||||
{
|
||||
CDC::SRow row;
|
||||
|
||||
if ((row = conn.read()))
|
||||
{
|
||||
std::string input = unquote(test_set[x].values[j]);
|
||||
std::string output = row->value(field_name);
|
||||
|
||||
if (input == output || (input == "NULL" && (output == "" || output == "0")))
|
||||
{
|
||||
// Expected result
|
||||
}
|
||||
else
|
||||
{
|
||||
test.tprintf("Result mismatch: %s(%s) => %s",
|
||||
test_set[x].types[i],
|
||||
input.c_str(),
|
||||
output.c_str());
|
||||
rval = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string err = conn.error();
|
||||
test.tprintf("Failed to read data: %s", err.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string err = conn.error();
|
||||
test.tprintf("Failed to request data: %s", err.c_str());
|
||||
rval = false;
|
||||
break;
|
||||
}
|
||||
test.stop_timeout();
|
||||
}
|
||||
}
|
||||
return rval;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
TestConnections::skip_maxscale_start(true);
|
||||
TestConnections::check_nodes(false);
|
||||
TestConnections test(argc, argv);
|
||||
|
||||
test.replicate_from_master();
|
||||
|
||||
if (!run_test(test))
|
||||
{
|
||||
test.add_result(1, "Test failed");
|
||||
}
|
||||
|
||||
test.check_maxscale_processes(0, 1);
|
||||
test.revert_replicate_from_master();
|
||||
|
||||
return test.global_result;
|
||||
}
|
82
system-test/cdc_datatypes/cdc_result.cpp
Normal file
82
system-test/cdc_datatypes/cdc_result.cpp
Normal file
@ -0,0 +1,82 @@
|
||||
#include "cdc_result.h"
|
||||
#include <iostream>
|
||||
#include <cstdlib>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <string.h>
|
||||
|
||||
using std::cout;
|
||||
using std::endl;
|
||||
|
||||
TestInput::TestInput(const std::string& value, const std::string& type, const std::string& name)
|
||||
: m_value(value)
|
||||
, m_type(type)
|
||||
, m_name(name)
|
||||
{
|
||||
if (m_value[0] == '"' || m_value[0] == '\'')
|
||||
{
|
||||
/** Remove quotes from the value */
|
||||
m_value = m_value.substr(1, m_value.length() - 2);
|
||||
}
|
||||
}
|
||||
|
||||
TestOutput::TestOutput(const std::string& input, const std::string& name)
|
||||
{
|
||||
json_error_t err;
|
||||
json_t* js = json_loads(input.c_str(), JSON_ALLOW_NUL, &err);
|
||||
|
||||
if (js)
|
||||
{
|
||||
json_t* value = json_object_get(js, name.c_str());
|
||||
|
||||
if (value)
|
||||
{
|
||||
std::stringstream ss;
|
||||
|
||||
if (json_is_string(value))
|
||||
{
|
||||
if (strlen(json_string_value(value)) == 0)
|
||||
{
|
||||
ss << "NULL";
|
||||
}
|
||||
else
|
||||
{
|
||||
ss << json_string_value(value);
|
||||
}
|
||||
}
|
||||
else if (json_is_integer(value))
|
||||
{
|
||||
ss << json_integer_value(value);
|
||||
}
|
||||
else if (json_is_null(value))
|
||||
{
|
||||
ss << "NULL";
|
||||
}
|
||||
else if (json_is_real(value))
|
||||
{
|
||||
ss << json_real_value(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
cout << "Value '" << name << "' is not a primitive type: " << input << endl;
|
||||
}
|
||||
|
||||
m_value = ss.str();
|
||||
}
|
||||
else
|
||||
{
|
||||
cout << "Value '" << name << "' not found" << endl;
|
||||
}
|
||||
|
||||
json_decref(js);
|
||||
}
|
||||
else
|
||||
{
|
||||
cout << "Failed to parse JSON: " << err.text << endl;
|
||||
}
|
||||
}
|
||||
|
||||
const std::string& TestOutput::getValue() const
|
||||
{
|
||||
return m_value;
|
||||
}
|
64
system-test/cdc_datatypes/cdc_result.h
Normal file
64
system-test/cdc_datatypes/cdc_result.h
Normal file
@ -0,0 +1,64 @@
|
||||
#ifndef CDC_RESULT_H
|
||||
#define CDC_RESULT_H
|
||||
|
||||
#include <jansson.h>
|
||||
#include <string>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
class TestOutput
|
||||
{
|
||||
public:
|
||||
TestOutput(const std::string& input, const std::string& name);
|
||||
const std::string& getValue() const;
|
||||
|
||||
private:
|
||||
std::string m_value;
|
||||
};
|
||||
|
||||
class TestInput
|
||||
{
|
||||
public:
|
||||
TestInput(const std::string& value, const std::string& type, const std::string& name = "a");
|
||||
const std::string& getName() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
const std::string& getValue() const
|
||||
{
|
||||
return m_value;
|
||||
}
|
||||
const std::string& getType() const
|
||||
{
|
||||
return m_type;
|
||||
}
|
||||
|
||||
bool operator==(const TestOutput& output) const
|
||||
{
|
||||
return m_value == output.getValue()
|
||||
|| (m_type.find("BLOB") != std::string::npos && output.getValue().length() == 0)
|
||||
|| // A NULL timestamp appears to be inserted as NOW() by default in 10.2, a NULL INT is
|
||||
// inserted as 0 and a NULL string gets converted into an empty string by the CDC system
|
||||
(m_value == "NULL"
|
||||
&& (output.getValue().empty() || m_type == "TIMESTAMP" || output.getValue() == "0"));
|
||||
}
|
||||
|
||||
bool operator!=(const TestOutput& output) const
|
||||
{
|
||||
return !(*this == output);
|
||||
}
|
||||
|
||||
private:
|
||||
std::string m_value;
|
||||
std::string m_type;
|
||||
std::string m_name;
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* CDC_RESULT_H */
|
Reference in New Issue
Block a user