409 lines
16 KiB
C++
409 lines
16 KiB
C++
/*
|
|
* Copyright (c) 2015, 2019, 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, version 2.0,
|
|
* as published by the Free Software Foundation.
|
|
*
|
|
* This program is also distributed with certain software (including
|
|
* but not limited to OpenSSL) that is licensed under separate terms,
|
|
* as designated in a particular file or component or in included license
|
|
* documentation. The authors of MySQL hereby grant you an additional
|
|
* permission to link the program and your derivative works with the
|
|
* separately licensed software that they have included with MySQL.
|
|
*
|
|
* 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, version 2.0, for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
#include <gmock/gmock.h>
|
|
#include <gtest/gtest.h>
|
|
|
|
#include "plugin/x/src/insert_statement_builder.h"
|
|
#include "unittest/gunit/xplugin/xpl/mock/session.h"
|
|
#include "unittest/gunit/xplugin/xpl/mysqlx_pb_wrapper.h"
|
|
|
|
namespace xpl {
|
|
namespace test {
|
|
using ::testing::_;
|
|
using ::testing::Return;
|
|
using ::testing::StrictMock;
|
|
|
|
class Insert_statement_builder_stub : public Insert_statement_builder {
|
|
public:
|
|
explicit Insert_statement_builder_stub(Expression_generator *gen)
|
|
: Insert_statement_builder(*gen, &m_id_agg) {}
|
|
using Insert_statement_builder::add_document;
|
|
using Insert_statement_builder::add_documents;
|
|
using Insert_statement_builder::add_projection;
|
|
using Insert_statement_builder::add_row;
|
|
using Insert_statement_builder::add_upsert;
|
|
using Insert_statement_builder::add_values;
|
|
StrictMock<ngs::test::Mock_id_generator> mock_id_generator;
|
|
Document_id_aggregator m_id_agg{&mock_id_generator};
|
|
};
|
|
|
|
using Placeholder_list = Expression_generator::Prep_stmt_placeholder_list;
|
|
|
|
class Insert_statement_builder_test : public ::testing::Test {
|
|
public:
|
|
Insert_statement_builder_stub &builder(
|
|
Expression_generator::Prep_stmt_placeholder_list *ids = nullptr) {
|
|
expr_gen.reset(new Expression_generator(&query, args, schema,
|
|
is_table_data_model(msg)));
|
|
if (ids) expr_gen->set_prep_stmt_placeholder_list(ids);
|
|
stub.reset(new Insert_statement_builder_stub(expr_gen.get()));
|
|
EXPECT_CALL(stub->mock_id_generator, generate(_))
|
|
.WillRepeatedly(Return("0ff0"));
|
|
return *stub;
|
|
}
|
|
|
|
Insert_statement_builder::Insert msg;
|
|
Expression_generator::Arg_list &args = *msg.mutable_args();
|
|
Query_string_builder query;
|
|
std::string schema;
|
|
std::unique_ptr<Expression_generator> expr_gen;
|
|
std::unique_ptr<Insert_statement_builder_stub> stub;
|
|
Placeholder_list placeholders;
|
|
|
|
enum { k_dm_document = 0, k_dm_table = 1 };
|
|
};
|
|
|
|
const char *const k_doc_example1 = R"({"_id":"abc1", "one":1})";
|
|
const char *const k_doc_example2 = R"({"_id":"abc2", "two":2})";
|
|
const char *const k_doc_example_no_id = R"({"three":3})";
|
|
#define EXPECT_DOC_EXAMPLE1 "{\\\"_id\\\":\\\"abc1\\\", \\\"one\\\":1}"
|
|
#define EXPECT_DOC_EXAMPLE2 "{\\\"_id\\\":\\\"abc2\\\", \\\"two\\\":2}"
|
|
#define EXPECT_DOC_EXAMPLE_NO_ID "{\\\"three\\\":3}"
|
|
|
|
TEST_F(Insert_statement_builder_test, add_row_empty_projection_empty_row) {
|
|
ASSERT_THROW(builder().add_row(Field_list(), 0), ngs::Error_code);
|
|
EXPECT_STREQ("", query.get().c_str());
|
|
}
|
|
|
|
TEST_F(Insert_statement_builder_test, add_row_one_projection_empty_row) {
|
|
ASSERT_THROW(builder().add_row(Field_list(), 1), ngs::Error_code);
|
|
EXPECT_STREQ("", query.get().c_str());
|
|
}
|
|
|
|
TEST_F(Insert_statement_builder_test, add_row_full_row_projection_empty) {
|
|
ASSERT_NO_THROW(builder().add_row(Field_list{"one"}, 0));
|
|
EXPECT_STREQ("('one')", query.get().c_str());
|
|
}
|
|
|
|
TEST_F(Insert_statement_builder_test, add_row_half_row_full_projection) {
|
|
ASSERT_THROW(builder().add_row(Field_list{"one"}, 2), ngs::Error_code);
|
|
EXPECT_STREQ("", query.get().c_str());
|
|
}
|
|
|
|
TEST_F(Insert_statement_builder_test, add_row_full_row_full_projection) {
|
|
ASSERT_NO_THROW(builder().add_row(Field_list{"one", "two"}, 2));
|
|
EXPECT_STREQ("('one','two')", query.get().c_str());
|
|
}
|
|
|
|
TEST_F(Insert_statement_builder_test, add_values_empty_list) {
|
|
ASSERT_THROW(builder().add_values(Row_list(), 1), ngs::Error_code);
|
|
EXPECT_STREQ("", query.get().c_str());
|
|
}
|
|
|
|
TEST_F(Insert_statement_builder_test, add_values_one_row) {
|
|
ASSERT_NO_THROW(builder().add_values(Row_list{{"one", "two"}}, 0));
|
|
EXPECT_STREQ(" VALUES ('one','two')", query.get().c_str());
|
|
}
|
|
|
|
TEST_F(Insert_statement_builder_test, add_values_one_row_with_arg) {
|
|
*args.Add() = Scalar("two");
|
|
|
|
ASSERT_NO_THROW(builder().add_values(Row_list{{"one", Placeholder(0)}}, 0));
|
|
EXPECT_STREQ(" VALUES ('one','two')", query.get().c_str());
|
|
}
|
|
|
|
TEST_F(Insert_statement_builder_test, add_values_one_row_missing_arg) {
|
|
EXPECT_THROW(builder().add_values(Row_list{{"one", Placeholder(0)}}, 0),
|
|
Expression_generator::Error);
|
|
}
|
|
|
|
TEST_F(Insert_statement_builder_test, add_values_two_rows) {
|
|
Row_list values{{"one", "two"}, {"three", "four"}};
|
|
ASSERT_NO_THROW(builder().add_values(values, values.size()));
|
|
EXPECT_STREQ(" VALUES ('one','two'),('three','four')", query.get().c_str());
|
|
}
|
|
|
|
TEST_F(Insert_statement_builder_test, add_values_two_rows_with_args) {
|
|
*args.Add() = Scalar("two");
|
|
*args.Add() = Scalar("four");
|
|
|
|
Row_list values{{"one", Placeholder(0)}, {"three", Placeholder(1)}};
|
|
ASSERT_NO_THROW(builder().add_values(values, values.size()));
|
|
EXPECT_STREQ(" VALUES ('one','two'),('three','four')", query.get().c_str());
|
|
}
|
|
|
|
TEST_F(Insert_statement_builder_test, add_projection_tabel_empty) {
|
|
ASSERT_NO_THROW(
|
|
builder().add_projection(Column_projection_list(), k_dm_table));
|
|
EXPECT_STREQ("", query.get().c_str());
|
|
}
|
|
|
|
TEST_F(Insert_statement_builder_test, add_projection_tabel_one_item) {
|
|
ASSERT_NO_THROW(builder().add_projection(
|
|
Column_projection_list{Column("first")}, k_dm_table));
|
|
EXPECT_STREQ(" (`first`)", query.get().c_str());
|
|
}
|
|
|
|
TEST_F(Insert_statement_builder_test, add_projection_tabel_two_items) {
|
|
ASSERT_NO_THROW(builder().add_projection(
|
|
Column_projection_list{Column("first"), Column("second")}, k_dm_table));
|
|
EXPECT_STREQ(" (`first`,`second`)", query.get().c_str());
|
|
}
|
|
|
|
TEST_F(Insert_statement_builder_test, add_projection_document_empty) {
|
|
ASSERT_NO_THROW(
|
|
builder().add_projection(Column_projection_list(), k_dm_document));
|
|
EXPECT_STREQ(" (doc)", query.get().c_str());
|
|
}
|
|
|
|
TEST_F(Insert_statement_builder_test, add_projection_document_one_item) {
|
|
ASSERT_THROW(builder().add_projection(Column_projection_list{Column("first")},
|
|
k_dm_document),
|
|
ngs::Error_code);
|
|
}
|
|
|
|
TEST_F(Insert_statement_builder_test, add_upsert) {
|
|
ASSERT_NO_THROW(builder().add_upsert(k_dm_document));
|
|
EXPECT_STREQ(
|
|
" ON DUPLICATE KEY UPDATE"
|
|
" doc = IF(JSON_UNQUOTE(JSON_EXTRACT(doc, '$._id'))"
|
|
" = JSON_UNQUOTE(JSON_EXTRACT(VALUES(doc), '$._id')),"
|
|
" VALUES(doc), MYSQLX_ERROR(5018))",
|
|
query.get().c_str());
|
|
ASSERT_THROW(builder().add_upsert(k_dm_table), ngs::Error_code);
|
|
}
|
|
|
|
TEST_F(Insert_statement_builder_test, build_document) {
|
|
msg = Insert({"xcoll", "xtest"}).row({{k_doc_example1}, {k_doc_example2}});
|
|
ASSERT_NO_THROW(builder().build(msg));
|
|
EXPECT_STREQ(
|
|
"INSERT INTO `xtest`.`xcoll` (doc) "
|
|
"VALUES ('" EXPECT_DOC_EXAMPLE1 "'),('" EXPECT_DOC_EXAMPLE2 "')",
|
|
query.get().c_str());
|
|
}
|
|
|
|
TEST_F(Insert_statement_builder_test, build_table) {
|
|
msg = Insert({"xtable", "xtest"}, Mysqlx::Crud::TABLE)
|
|
.projection({{"one"}, {"two"}})
|
|
.row({{"first", "second"}});
|
|
ASSERT_NO_THROW(builder().build(msg));
|
|
EXPECT_STREQ(
|
|
"INSERT INTO `xtest`.`xtable` (`one`,`two`) "
|
|
"VALUES ('first','second')",
|
|
query.get().c_str());
|
|
}
|
|
|
|
TEST_F(Insert_statement_builder_test, build_document_upsert) {
|
|
msg = Insert({"xcoll", "xtest"})
|
|
.upsert(true)
|
|
.row({{k_doc_example1}, {k_doc_example2}});
|
|
ASSERT_NO_THROW(builder().build(msg));
|
|
EXPECT_STREQ(
|
|
"INSERT INTO `xtest`.`xcoll` (doc) VALUES "
|
|
"('" EXPECT_DOC_EXAMPLE1 "'),('" EXPECT_DOC_EXAMPLE2
|
|
"') ON DUPLICATE KEY UPDATE"
|
|
" doc = IF(JSON_UNQUOTE(JSON_EXTRACT(doc, '$._id'))"
|
|
" = JSON_UNQUOTE(JSON_EXTRACT(VALUES(doc), '$._id')),"
|
|
" VALUES(doc), MYSQLX_ERROR(5018))",
|
|
query.get().c_str());
|
|
}
|
|
|
|
TEST_F(Insert_statement_builder_test, build_table_upsert) {
|
|
msg = Insert({"xtable", "xtest"}, Mysqlx::Crud::TABLE)
|
|
.upsert(true)
|
|
.row({{"first"}, {"second"}});
|
|
ASSERT_THROW(builder().build(msg), ngs::Error_code);
|
|
}
|
|
|
|
TEST_F(Insert_statement_builder_test, add_documents_empty_list) {
|
|
ASSERT_THROW(builder().add_documents(Row_list()), ngs::Error_code);
|
|
EXPECT_STREQ("", query.get().c_str());
|
|
}
|
|
|
|
TEST_F(Insert_statement_builder_test, add_documents_one_row) {
|
|
ASSERT_NO_THROW(builder().add_documents(Row_list{{k_doc_example1}}));
|
|
EXPECT_STREQ(" VALUES ('" EXPECT_DOC_EXAMPLE1 "')", query.get().c_str());
|
|
}
|
|
|
|
TEST_F(Insert_statement_builder_test, add_documents_one_row_with_arg) {
|
|
*args.Add() = Scalar(k_doc_example2);
|
|
|
|
ASSERT_NO_THROW(builder().add_documents(Row_list{{Placeholder(0)}}));
|
|
EXPECT_STREQ(" VALUES ('" EXPECT_DOC_EXAMPLE2 "')", query.get().c_str());
|
|
}
|
|
|
|
TEST_F(Insert_statement_builder_test, add_documents_one_row_missing_arg) {
|
|
EXPECT_THROW(builder().add_documents(Row_list{{Placeholder(0)}}),
|
|
Expression_generator::Error);
|
|
}
|
|
|
|
TEST_F(Insert_statement_builder_test, add_documents_two_rows) {
|
|
ASSERT_NO_THROW(
|
|
builder().add_documents(Row_list{{k_doc_example1}, {k_doc_example2}}));
|
|
EXPECT_STREQ(" VALUES ('" EXPECT_DOC_EXAMPLE1 "'),('" EXPECT_DOC_EXAMPLE2
|
|
"')",
|
|
query.get().c_str());
|
|
}
|
|
|
|
TEST_F(Insert_statement_builder_test, add_document_empty_row) {
|
|
ASSERT_THROW(builder().add_document(Field_list()), ngs::Error_code);
|
|
EXPECT_STREQ("", query.get().c_str());
|
|
}
|
|
|
|
TEST_F(Insert_statement_builder_test, add_document_two_docs) {
|
|
ASSERT_THROW(
|
|
builder().add_document(Field_list{k_doc_example1, k_doc_example2}),
|
|
ngs::Error_code);
|
|
EXPECT_STREQ("", query.get().c_str());
|
|
}
|
|
|
|
TEST_F(Insert_statement_builder_test, add_document_placeholder) {
|
|
*args.Add() = Scalar(k_doc_example1);
|
|
ASSERT_NO_THROW(builder().add_document(Field_list{Placeholder(0)}));
|
|
EXPECT_STREQ("('" EXPECT_DOC_EXAMPLE1 "')", query.get().c_str());
|
|
}
|
|
|
|
TEST_F(Insert_statement_builder_test, add_document_placeholder_wrong_type) {
|
|
*args.Add() = Scalar(3.14);
|
|
ASSERT_NO_THROW(builder().add_document(Field_list{Placeholder(0)}));
|
|
EXPECT_STREQ("(3.14)", query.get().c_str());
|
|
}
|
|
|
|
struct Param_add_document {
|
|
std::string expect;
|
|
Expr fields;
|
|
};
|
|
|
|
class Add_document_param_test
|
|
: public Insert_statement_builder_test,
|
|
public ::testing::WithParamInterface<Param_add_document> {};
|
|
|
|
TEST_P(Add_document_param_test, add_document) {
|
|
const Param_add_document ¶m = GetParam();
|
|
ASSERT_NO_THROW(builder().add_document(Field_list{param.fields}));
|
|
EXPECT_STREQ(param.expect.c_str(), query.get().c_str());
|
|
}
|
|
|
|
using Octets = Scalar::Octets;
|
|
|
|
Param_add_document add_document_param[] = {
|
|
{"('" EXPECT_DOC_EXAMPLE1 "')", k_doc_example1},
|
|
{"(3.14)", 3.14},
|
|
{"(JSON_OBJECT('_id','abc1','one',1))",
|
|
Object{{"_id", "abc1"}, {"one", 1}}},
|
|
{"('" EXPECT_DOC_EXAMPLE1 "')",
|
|
Octets{k_doc_example1, Octets::Content_type::k_plain}},
|
|
{"('" EXPECT_DOC_EXAMPLE1 "')",
|
|
Octets{k_doc_example1, Octets::Content_type::k_json}},
|
|
{"('abc')", Octets{"abc", Octets::Content_type::k_xml}},
|
|
{"(JSON_SET('" EXPECT_DOC_EXAMPLE_NO_ID "', '$._id', '0ff0'))",
|
|
k_doc_example_no_id},
|
|
{"(JSON_SET('{}', '$._id', '0ff0'))", "{}"},
|
|
{"(JSON_SET(JSON_OBJECT('tree',3), '$._id', '0ff0'))", Object{{"tree", 3}}},
|
|
{"(JSON_SET(JSON_OBJECT(), '$._id', '0ff0'))", Object{}},
|
|
{"(JSON_SET(JSON_OBJECT('extra',"
|
|
"JSON_OBJECT('_id','abc1','one',1)), '$._id', '0ff0'))",
|
|
Object{{"extra", Object{{"_id", "abc1"}, {"one", 1}}}}},
|
|
{"(JSON_SET(JSON_OBJECT('extra','" EXPECT_DOC_EXAMPLE1
|
|
"'), '$._id', '0ff0'))",
|
|
Object{{"extra", k_doc_example1}}},
|
|
{"(JSON_SET('{\\\"extra\\\":" EXPECT_DOC_EXAMPLE2 "}', '$._id', '0ff0'))",
|
|
Scalar::String(std::string(R"({"extra":)") + k_doc_example2 + "}")},
|
|
{"('{\\\"_id\\\":\\\"abc3\\\","
|
|
" \\\"extra\\\":" EXPECT_DOC_EXAMPLE2 "}')",
|
|
Scalar::String(std::string(R"({"_id":"abc3", "extra":)") + k_doc_example2 +
|
|
"}")},
|
|
{"('{\\\"extra\\\":" EXPECT_DOC_EXAMPLE2 ", \\\"_id\\\":\\\"abc3\\\"}')",
|
|
Scalar::String(std::string(R"({"extra":)") + k_doc_example2 +
|
|
R"(, "_id":"abc3"})")}};
|
|
|
|
INSTANTIATE_TEST_CASE_P(Insert_statement_builder_add_document,
|
|
Add_document_param_test,
|
|
testing::ValuesIn(add_document_param));
|
|
|
|
struct Param_add_prep_stmt_document {
|
|
std::string expect_query;
|
|
Placeholder_list expect_placeholders;
|
|
Expr fields;
|
|
};
|
|
|
|
class Add_prep_stmt_document_param_test
|
|
: public Insert_statement_builder_test,
|
|
public ::testing::WithParamInterface<Param_add_prep_stmt_document> {};
|
|
|
|
TEST_P(Add_prep_stmt_document_param_test, add_prep_stmt_document) {
|
|
const ParamType ¶m = GetParam();
|
|
ASSERT_NO_THROW(
|
|
builder(&placeholders).add_document(Field_list{param.fields}));
|
|
EXPECT_STREQ(param.expect_query.c_str(), query.get().c_str());
|
|
EXPECT_EQ(param.expect_placeholders, placeholders);
|
|
}
|
|
|
|
#define EXPECT_VALUE(val) \
|
|
"((SELECT JSON_INSERT(`_DERIVED_TABLE_`.`value`,'$._id'," \
|
|
"CONVERT(MYSQLX_GENERATE_DOCUMENT_ID(@@AUTO_INCREMENT_OFFSET," \
|
|
"@@AUTO_INCREMENT_INCREMENT,JSON_CONTAINS_PATH(`_DERIVED_TABLE_`.`value`," \
|
|
"'one','$._id')) USING utf8mb4)) " \
|
|
"FROM (SELECT " val " AS `value`) AS `_DERIVED_TABLE_`))"
|
|
|
|
Param_add_prep_stmt_document add_prep_stmt_document_param[] = {
|
|
{EXPECT_VALUE("'" EXPECT_DOC_EXAMPLE1 "'"), {}, k_doc_example1},
|
|
{EXPECT_VALUE("3.14"), {}, 3.14},
|
|
{EXPECT_VALUE("JSON_OBJECT('_id','abc1','one',1)"),
|
|
{},
|
|
Object{{"_id", "abc1"}, {"one", 1}}},
|
|
{EXPECT_VALUE("'" EXPECT_DOC_EXAMPLE1 "'"),
|
|
{},
|
|
Octets{k_doc_example1, Octets::Content_type::k_plain}},
|
|
{EXPECT_VALUE("CAST('" EXPECT_DOC_EXAMPLE1 "' AS JSON)"),
|
|
{},
|
|
Octets{k_doc_example1, Octets::Content_type::k_json}},
|
|
{EXPECT_VALUE("'abc'"), {}, Octets{"abc", Octets::Content_type::k_xml}},
|
|
{EXPECT_VALUE("'" EXPECT_DOC_EXAMPLE_NO_ID "'"), {}, k_doc_example_no_id},
|
|
{EXPECT_VALUE("'{}'"), {}, "{}"},
|
|
{EXPECT_VALUE("JSON_OBJECT('tree',3)"), {}, Object{{"tree", 3}}},
|
|
{EXPECT_VALUE("JSON_OBJECT()"), {}, Object{}},
|
|
{EXPECT_VALUE("JSON_OBJECT('extra',JSON_OBJECT('_id','abc1','one',1))"),
|
|
{},
|
|
Object{{"extra", Object{{"_id", "abc1"}, {"one", 1}}}}},
|
|
{EXPECT_VALUE("JSON_OBJECT('extra','" EXPECT_DOC_EXAMPLE1 "')"),
|
|
{},
|
|
Object{{"extra", k_doc_example1}}},
|
|
{EXPECT_VALUE("'{\\\"extra\\\":" EXPECT_DOC_EXAMPLE2 "}'"),
|
|
{},
|
|
Scalar::String(std::string(R"({"extra":)") + k_doc_example2 + "}")},
|
|
{EXPECT_VALUE("'{\\\"_id\\\":\\\"abc3\\\","
|
|
" \\\"extra\\\":" EXPECT_DOC_EXAMPLE2 "}'"),
|
|
{},
|
|
Scalar::String(std::string(R"({"_id":"abc3", "extra":)") + k_doc_example2 +
|
|
"}")},
|
|
{EXPECT_VALUE("'{\\\"extra\\\":" EXPECT_DOC_EXAMPLE2
|
|
", \\\"_id\\\":\\\"abc3\\\"}'"),
|
|
{},
|
|
Scalar::String(std::string(R"({"extra":)") + k_doc_example2 +
|
|
R"(, "_id":"abc3"})")},
|
|
{EXPECT_VALUE("?"), {0}, Placeholder(0)},
|
|
{EXPECT_VALUE("JSON_OBJECT('tree',?)"),
|
|
{0},
|
|
Object{{"tree", Placeholder(0)}}},
|
|
};
|
|
|
|
INSTANTIATE_TEST_CASE_P(Insert_statement_builder_add_prep_stmt_document,
|
|
Add_prep_stmt_document_param_test,
|
|
testing::ValuesIn(add_prep_stmt_document_param));
|
|
|
|
} // namespace test
|
|
} // namespace xpl
|