/* * Copyright (c) 2018, 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 #include #include #include "plugin/x/src/admin_cmd_arguments.h" #include "plugin/x/src/index_field.h" #include "plugin/x/src/xpl_error.h" #include "unittest/gunit/xplugin/xpl/assert_error_code.h" #include "unittest/gunit/xplugin/xpl/mock/session.h" #include "unittest/gunit/xplugin/xpl/mysqlx_pb_wrapper.h" #include "unittest/gunit/xplugin/xpl/one_row_resultset.h" namespace xpl { namespace test { using ::testing::_; using ::testing::DoAll; using ::testing::Eq; using ::testing::Return; namespace { #define PATH "$.path" #define PATH_HASH "6EA549FAA434CCD150A7DB5FF9C0AEC77C4F5D25" #define NOT_REQUIRED false #define REQUIRED true #define OPTIONS 42u #define SRID 666u #define DEFAULT_VALUE std::numeric_limits::max() #define SHOW_COLUMNS(field) \ "SHOW COLUMNS FROM `schema`.`collection`" \ " WHERE Field = '" field "'" struct Field_info : public Admin_command_index::Index_field_info { Field_info(const std::string &path, const std::string &type, const bool is_required = NOT_REQUIRED, const uint64_t options = DEFAULT_VALUE, const uint64_t srid = DEFAULT_VALUE) { m_path = path; m_type = type; m_is_required = is_required; m_options = options; m_srid = srid; } }; } // namespace class Index_field_create_test : public ::testing::TestWithParam {}; TEST_P(Index_field_create_test, fail_on_create) { const auto ¶m = GetParam(); ngs::Error_code error; std::unique_ptr field( Index_field::create(true, param, &error)); ASSERT_ERROR_CODE(ER_X_CMD_ARGUMENT_VALUE, error); ASSERT_EQ(nullptr, field.get()); } Field_info fail_on_create_param[] = { {"", "DECIMAL"}, {PATH, ""}, {PATH, "DECIMAL SIGNED"}, {PATH, "tinyint(10,2)"}, {PATH, "tinyint", NOT_REQUIRED, OPTIONS}, {PATH, "tinyint", NOT_REQUIRED, DEFAULT_VALUE, SRID}, {PATH, "smallint(10,2)"}, {PATH, "smallint", NOT_REQUIRED, OPTIONS}, {PATH, "smallint", NOT_REQUIRED, DEFAULT_VALUE, SRID}, {PATH, "mediumint(10,2)"}, {PATH, "mediumint", NOT_REQUIRED, OPTIONS}, {PATH, "mediumint", NOT_REQUIRED, DEFAULT_VALUE, SRID}, {PATH, "int(10,2)"}, {PATH, "int", NOT_REQUIRED, OPTIONS}, {PATH, "int", NOT_REQUIRED, DEFAULT_VALUE, SRID}, {PATH, "integer(10,2)"}, {PATH, "integer", NOT_REQUIRED, OPTIONS}, {PATH, "integer", NOT_REQUIRED, DEFAULT_VALUE, SRID}, {PATH, "bigint(10,2)"}, {PATH, "bigint", NOT_REQUIRED, OPTIONS}, {PATH, "bigint", NOT_REQUIRED, DEFAULT_VALUE, SRID}, {PATH, "real", NOT_REQUIRED, OPTIONS}, {PATH, "real", NOT_REQUIRED, DEFAULT_VALUE, SRID}, {PATH, "float", NOT_REQUIRED, OPTIONS}, {PATH, "float", NOT_REQUIRED, DEFAULT_VALUE, SRID}, {PATH, "double", NOT_REQUIRED, OPTIONS}, {PATH, "double", NOT_REQUIRED, DEFAULT_VALUE, SRID}, {PATH, "numeric", NOT_REQUIRED, OPTIONS}, {PATH, "numeric", NOT_REQUIRED, DEFAULT_VALUE, SRID}, {PATH, "date(10)"}, {PATH, "date(10,2)"}, {PATH, "date unsigned"}, {PATH, "date", NOT_REQUIRED, OPTIONS}, {PATH, "date", NOT_REQUIRED, DEFAULT_VALUE, SRID}, {PATH, "time(10,2)"}, {PATH, "time unsigned"}, {PATH, "time", NOT_REQUIRED, OPTIONS}, {PATH, "time", NOT_REQUIRED, DEFAULT_VALUE, SRID}, {PATH, "timestamp(10,2)"}, {PATH, "timestamp unsigned"}, {PATH, "timestamp", NOT_REQUIRED, OPTIONS}, {PATH, "timestamp", NOT_REQUIRED, DEFAULT_VALUE, SRID}, {PATH, "datetime(10,2)"}, {PATH, "datetime (10)"}, {PATH, "datetime unsigned"}, {PATH, "datetime", NOT_REQUIRED, OPTIONS}, {PATH, "datetime", NOT_REQUIRED, DEFAULT_VALUE, SRID}, {PATH, "year(10,2)"}, {PATH, "year unsigned"}, {PATH, "year", NOT_REQUIRED, OPTIONS}, {PATH, "year", NOT_REQUIRED, DEFAULT_VALUE, SRID}, {PATH, "bit(10,2)"}, {PATH, "bit (10)"}, {PATH, "bit unsigned "}, {PATH, "bit", NOT_REQUIRED, OPTIONS}, {PATH, "bit", NOT_REQUIRED, DEFAULT_VALUE, SRID}, {PATH, "blob(10,2)"}, {PATH, "blob unsigned"}, {PATH, "blob", NOT_REQUIRED, OPTIONS}, {PATH, "blob", NOT_REQUIRED, DEFAULT_VALUE, SRID}, {PATH, "text(10,2)"}, {PATH, "text unsigned"}, {PATH, "text", NOT_REQUIRED, OPTIONS}, {PATH, "text", NOT_REQUIRED, DEFAULT_VALUE, SRID}, {PATH, "geojson(10)"}, {PATH, "geojson(10,2)"}, {PATH, "geojson unsigned"}, {PATH, "fulltext(10)"}, {PATH, "fulltext unsigned"}, {PATH, "fulltext", NOT_REQUIRED, OPTIONS}, {PATH, "fulltext", NOT_REQUIRED, DEFAULT_VALUE, SRID}, {PATH, "char(10,2)"}, }; INSTANTIATE_TEST_CASE_P(fail_on_create_field, Index_field_create_test, ::testing::ValuesIn(fail_on_create_param)); struct Param_index_field_add_field { std::string expect; Field_info info; }; class Index_field_add_field_test : public ::testing::TestWithParam {}; TEST_P(Index_field_add_field_test, add_field) { const auto ¶m = GetParam(); Query_string_builder qb; ngs::Error_code error; std::unique_ptr field( Index_field::create(true, param.info, &error)); ASSERT_ERROR_CODE(ER_X_SUCCESS, error); field->add_field(&qb); ASSERT_STREQ(std::string(param.expect).c_str(), qb.get().c_str()); } Param_index_field_add_field add_field_param[] = { {"`$ix_xd_" PATH_HASH "`", {PATH, "DECIMAL", NOT_REQUIRED}}, {"`$ix_xd_" PATH_HASH "`", {PATH, "decimal", NOT_REQUIRED}}, {"`$ix_xd_" PATH_HASH "`", {PATH, "DEcimAL", NOT_REQUIRED}}, {"`$ix_xd32_" PATH_HASH "`", {PATH, "DECIMAL(32)", NOT_REQUIRED}}, {"`$ix_xd32_16_" PATH_HASH "`", {PATH, "DECIMAL(32,16)", NOT_REQUIRED}}, {"`$ix_xd_16_" PATH_HASH "`", {PATH, "DECIMAL(0,16)", NOT_REQUIRED}}, {"`$ix_xd32_16_u_" PATH_HASH "`", {PATH, "DECIMAL(32,16) UNSIGNED", NOT_REQUIRED}}, {"`$ix_xd32_16_ur_" PATH_HASH "`", {PATH, "DECIMAL(32,16) UNSIGNED", REQUIRED}}, {"`$ix_xd32_16_ur_" PATH_HASH "`", {PATH, "DECIMAL(32,16) UNSIGNED", REQUIRED}}, {"`$ix_xd32_16_r_" PATH_HASH "`", {PATH, "DECIMAL(32,16)", REQUIRED}}, {"`$ix_xd_ur_" PATH_HASH "`", {PATH, "DECIMAL UNSIGNED", REQUIRED}}, {"`$ix_xd_ur_" PATH_HASH "`", {PATH, "DECIMAL unsigned", REQUIRED}}, {"`$ix_xd_ur_" PATH_HASH "`", {PATH, "DECIMAL UNsignED", REQUIRED}}, {"`$ix_xd_ur_" PATH_HASH "`", {PATH, "DECIMAL UNSIGNED", REQUIRED}}, {"`$ix_it_" PATH_HASH "`", {PATH, "tinyint", NOT_REQUIRED}}, {"`$ix_is_" PATH_HASH "`", {PATH, "smallint", NOT_REQUIRED}}, {"`$ix_im_" PATH_HASH "`", {PATH, "mediumint", NOT_REQUIRED}}, {"`$ix_i_" PATH_HASH "`", {PATH, "int", NOT_REQUIRED}}, {"`$ix_i_" PATH_HASH "`", {PATH, "integer", NOT_REQUIRED}}, {"`$ix_ib_" PATH_HASH "`", {PATH, "bigint", NOT_REQUIRED}}, {"`$ix_fr_" PATH_HASH "`", {PATH, "real", NOT_REQUIRED}}, {"`$ix_f_" PATH_HASH "`", {PATH, "float", NOT_REQUIRED}}, {"`$ix_fd_" PATH_HASH "`", {PATH, "double", NOT_REQUIRED}}, {"`$ix_fd32_16_" PATH_HASH "`", {PATH, "double(32, 16)", NOT_REQUIRED}}, {"`$ix_fd32_16_" PATH_HASH "`", {PATH, "double(32 ,16)", NOT_REQUIRED}}, {"`$ix_fd32_16_" PATH_HASH "`", {PATH, "double(32 , 16)", NOT_REQUIRED}}, {"`$ix_xn_" PATH_HASH "`", {PATH, "numeric", NOT_REQUIRED}}, {"`$ix_d_" PATH_HASH "`", {PATH, "date", NOT_REQUIRED}}, {"`$ix_dt_" PATH_HASH "`", {PATH, "time", NOT_REQUIRED}}, {"`$ix_ds_" PATH_HASH "`", {PATH, "timestamp", NOT_REQUIRED}}, {"`$ix_dd_" PATH_HASH "`", {PATH, "datetime", NOT_REQUIRED}}, {"`$ix_dy_" PATH_HASH "`", {PATH, "year", NOT_REQUIRED}}, {"`$ix_t_" PATH_HASH "`", {PATH, "bit", NOT_REQUIRED}}, {"`$ix_bt_" PATH_HASH "`", {PATH, "blob", NOT_REQUIRED}}, {"`$ix_t_" PATH_HASH "`", {PATH, "text", NOT_REQUIRED}}, {"`$ix_gj_" PATH_HASH "`", {PATH, "geojson", NOT_REQUIRED}}, {"`$ix_ft_" PATH_HASH "`", {PATH, "fulltext", NOT_REQUIRED}}, {"`$ix_t32_" PATH_HASH "`(32)", {PATH, "text(32)", NOT_REQUIRED}}, {"`$ix_c_" PATH_HASH "`", {PATH, "char", NOT_REQUIRED}}, {"`$ix_c10_" PATH_HASH "`", {PATH, "char(10)", NOT_REQUIRED}}, {"`$ix_c20_" PATH_HASH "`", {PATH, "char(20) charset latin1", NOT_REQUIRED}}, {"`$ix_c30_" PATH_HASH "`", {PATH, "char(30) collate latin1_bin", NOT_REQUIRED}}, {"`$ix_c40_" PATH_HASH "`", {PATH, "char(40) character set latin1 collate latin1_bin", NOT_REQUIRED}}, {"`$ix_t128_" PATH_HASH "`(128)", {PATH, "text(128) character set latin1 collate latin1_bin", NOT_REQUIRED}}, {"`$ix_c_" PATH_HASH "`", {PATH, "char character set latin1", NOT_REQUIRED}}, }; INSTANTIATE_TEST_CASE_P(get_index_field_name, Index_field_add_field_test, ::testing::ValuesIn(add_field_param)); struct Param_index_field_add_column { std::string expect; bool virtual_supported; Field_info info; }; class Index_field_add_column_test : public ::testing::TestWithParam {}; TEST_P(Index_field_add_column_test, add_column) { const Param_index_field_add_column ¶m = GetParam(); Query_string_builder qb; ngs::Error_code error; std::unique_ptr field( Index_field::create(param.virtual_supported, param.info, &error)); ASSERT_ERROR_CODE(ER_X_SUCCESS, error); field->add_column(&qb); ASSERT_STREQ(param.expect.c_str(), qb.get().c_str()); } Param_index_field_add_column add_column_param[] = { {" ADD COLUMN `$ix_xd_" PATH_HASH "` DECIMAL GENERATED ALWAYS AS (JSON_EXTRACT(doc, '$.path')) VIRTUAL", true, {PATH, "DECIMAL", NOT_REQUIRED}}, {" ADD COLUMN `$ix_xd_" PATH_HASH "` DECIMAL GENERATED ALWAYS AS (JSON_EXTRACT(doc, '$.path')) STORED", false, {PATH, "DECIMAL", NOT_REQUIRED}}, {" ADD COLUMN `$ix_t32_" PATH_HASH "` TEXT GENERATED ALWAYS AS (JSON_UNQUOTE(JSON_EXTRACT(doc, " "'$.path'))) VIRTUAL", true, {PATH, "TEXT(32)", NOT_REQUIRED}}, {" ADD COLUMN `$ix_t32_r_" PATH_HASH "` TEXT GENERATED ALWAYS AS (JSON_UNQUOTE(JSON_EXTRACT(doc, " "'$.path'))) VIRTUAL NOT NULL", true, {PATH, "TEXT(32)", REQUIRED}}, {" ADD COLUMN `$ix_gj_r_" PATH_HASH "` GEOMETRY GENERATED ALWAYS AS (ST_GEOMFROMGEOJSON" "(JSON_EXTRACT(doc, '$.path'),1,4326)) STORED NOT NULL SRID 4326", true, {PATH, "GEOJSON", REQUIRED}}, {" ADD COLUMN `$ix_gj_" PATH_HASH "` GEOMETRY GENERATED ALWAYS AS (ST_GEOMFROMGEOJSON(" "JSON_EXTRACT(doc, '$.path'),42,4326)) STORED SRID 4326", true, {PATH, "GEOJSON", NOT_REQUIRED, OPTIONS}}, {" ADD COLUMN `$ix_gj_" PATH_HASH "` GEOMETRY GENERATED ALWAYS AS (ST_GEOMFROMGEOJSON(" "JSON_EXTRACT(doc, '$.path'),1,666)) STORED SRID 666", false, {PATH, "GEOJSON", NOT_REQUIRED, DEFAULT_VALUE, SRID}}, {" ADD COLUMN `$ix_ft_" PATH_HASH "` TEXT GENERATED ALWAYS AS (JSON_UNQUOTE(" "JSON_EXTRACT(doc, '$.path'))) STORED", false, {PATH, "FULLTEXT", NOT_REQUIRED}}, {" ADD COLUMN `$ix_c20_" PATH_HASH "` CHAR(20) GENERATED ALWAYS AS (JSON_UNQUOTE(" "JSON_EXTRACT(doc, '$.path'))) STORED", false, {PATH, "CHAR(20)", NOT_REQUIRED}}, {" ADD COLUMN `$ix_t64_" PATH_HASH "` TEXT CHARACTER SET latin1 COLLATE latin1_bin" " GENERATED ALWAYS AS (JSON_UNQUOTE(JSON_EXTRACT(doc, '$.path'))) STORED", false, {PATH, "TEXT(64) CHARACTER SET latin1 COLLATE latin1_bin", NOT_REQUIRED}}, }; INSTANTIATE_TEST_CASE_P(add_column, Index_field_add_column_test, ::testing::ValuesIn(add_column_param)); class Index_field_is_column_exists_test : public ::testing::Test { public: void SetUp() { ngs::Error_code error; field.reset( Index_field::create(true, Field_info(PATH, "int", REQUIRED), &error)); ASSERT_ERROR_CODE(ER_X_SUCCESS, error); } using Sql = ngs::PFS_string; ::testing::StrictMock data_context; std::unique_ptr field; }; TEST_F(Index_field_is_column_exists_test, column_is_not_exist) { EXPECT_CALL(data_context, execute(Eq(Sql(SHOW_COLUMNS("$ix_i_r_" PATH_HASH))), _, _)) .WillOnce(Return(ngs::Success())); ngs::Error_code error; ASSERT_FALSE( field->is_column_exists(&data_context, "schema", "collection", &error)); ASSERT_ERROR_CODE(ER_X_SUCCESS, error); } TEST_F(Index_field_is_column_exists_test, column_is_not_exist_error) { EXPECT_CALL(data_context, execute(Eq(Sql(SHOW_COLUMNS("$ix_i_r_" PATH_HASH))), _, _)) .WillOnce(Return(ngs::Error(ER_X_ARTIFICIAL1, "internal error"))); ngs::Error_code error; ASSERT_FALSE( field->is_column_exists(&data_context, "schema", "collection", &error)); ASSERT_ERROR_CODE(ER_X_ARTIFICIAL1, error); } TEST_F(Index_field_is_column_exists_test, column_is_exist) { One_row_resultset data{"anything"}; EXPECT_CALL(data_context, execute(Eq(Sql(SHOW_COLUMNS("$ix_i_r_" PATH_HASH))), _, _)) .WillOnce(DoAll(SetUpResultset(data), Return(ngs::Success()))); ngs::Error_code error; ASSERT_TRUE( field->is_column_exists(&data_context, "schema", "collection", &error)); ASSERT_ERROR_CODE(ER_X_SUCCESS, error); } } // namespace test } // namespace xpl