MXS-1621: Add ALTER TABLE ... [FIRST | AFTER col
] parsing
The parser checks whether the FIRST or AFTER keywords are used and, if AFTER is used, extracts the relevant column name. Added a test case that checks that the parsing works and detects the correct column names.
This commit is contained in:
@ -6,6 +6,10 @@ if(AVRO_FOUND AND JANSSON_FOUND)
|
||||
set_target_properties(avrorouter PROPERTIES LINK_FLAGS -Wl,-z,defs)
|
||||
target_link_libraries(avrorouter maxscale-common ${JANSSON_LIBRARIES} ${AVRO_LIBRARIES} maxavro sqlite3 lzma)
|
||||
install_module(avrorouter core)
|
||||
|
||||
if (BUILD_TESTS)
|
||||
add_subdirectory(test)
|
||||
endif()
|
||||
else()
|
||||
message(STATUS "No Avro C or Jansson libraries found, not building avrorouter.")
|
||||
endif()
|
||||
|
@ -1040,12 +1040,13 @@ void handle_query_event(AVRO_INSTANCE *router, REP_HEADER *hdr, int *pending_tra
|
||||
db[dblen] = 0;
|
||||
|
||||
size_t sqlsz = len, tmpsz = len;
|
||||
char *tmp = MXS_MALLOC(len);
|
||||
char *tmp = MXS_MALLOC(len + 1);
|
||||
MXS_ABORT_IF_NULL(tmp);
|
||||
remove_mysql_comments((const char**)&sql, &sqlsz, &tmp, &tmpsz);
|
||||
sql = tmp;
|
||||
len = tmpsz;
|
||||
unify_whitespace(sql, len);
|
||||
sql[len] = '\0';
|
||||
|
||||
static bool warn_not_row_format = true;
|
||||
|
||||
|
@ -930,7 +930,6 @@ static void remove_extras(char* str)
|
||||
ss_dassert(strlen(str) == len);
|
||||
}
|
||||
|
||||
|
||||
static void remove_backticks(char* src)
|
||||
{
|
||||
char* dest = src;
|
||||
@ -1134,6 +1133,110 @@ static const char* get_tok(const char* sql, int* toklen, const char* end)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void rskip_whitespace(const char* sql, const char** end)
|
||||
{
|
||||
const char* ptr = *end;
|
||||
|
||||
while (ptr > sql && isspace(*ptr))
|
||||
{
|
||||
ptr--;
|
||||
}
|
||||
|
||||
*end = ptr;
|
||||
}
|
||||
|
||||
static void rskip_token(const char* sql, const char** end)
|
||||
{
|
||||
const char* ptr = *end;
|
||||
|
||||
while (ptr > sql && !isspace(*ptr))
|
||||
{
|
||||
ptr--;
|
||||
}
|
||||
|
||||
*end = ptr;
|
||||
}
|
||||
|
||||
static bool get_placement_specifier(const char* sql, const char* end, const char** tgt, int* tgt_len)
|
||||
{
|
||||
bool rval = false;
|
||||
ss_dassert(end > sql);
|
||||
end--;
|
||||
|
||||
*tgt = NULL;
|
||||
*tgt_len = 0;
|
||||
|
||||
// Skip any trailing whitespace
|
||||
rskip_whitespace(sql, &end);
|
||||
|
||||
if (*end == '`')
|
||||
{
|
||||
// Identifier, possibly AFTER `column`
|
||||
const char* id_end = end;
|
||||
end--;
|
||||
|
||||
while (end > sql && *end != '`')
|
||||
{
|
||||
end--;
|
||||
}
|
||||
|
||||
const char* id_start = end + 1;
|
||||
ss_dassert(*end == '`' && *id_end == '`');
|
||||
|
||||
end--;
|
||||
|
||||
rskip_whitespace(sql, &end);
|
||||
rskip_token(sql, &end);
|
||||
|
||||
// end points to the character _before_ the token
|
||||
end++;
|
||||
|
||||
if (strncasecmp(end, "AFTER", 5) == 0)
|
||||
{
|
||||
// This column comes after the specified column
|
||||
rval = true;
|
||||
*tgt = id_start;
|
||||
*tgt_len = id_end - id_start;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Something else, possibly FIRST or un-backtick'd AFTER
|
||||
const char* id_end = end + 1; // Points to either a trailing space or one-after-the-end
|
||||
rskip_token(sql, &end);
|
||||
|
||||
// end points to the character _before_ the token
|
||||
end++;
|
||||
|
||||
if (strncasecmp(end, "FIRST", 5) == 0)
|
||||
{
|
||||
// Put this column first
|
||||
rval = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
const char* id_start = end + 1;
|
||||
|
||||
// Skip the whitespace and until the start of the current token
|
||||
rskip_whitespace(sql, &end);
|
||||
rskip_token(sql, &end);
|
||||
|
||||
// end points to the character _before_ the token
|
||||
end++;
|
||||
|
||||
if (strncasecmp(end, "AFTER", 5) == 0)
|
||||
{
|
||||
// This column comes after the specified column
|
||||
rval = true;
|
||||
*tgt = id_start;
|
||||
*tgt_len = id_end - id_start;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rval;
|
||||
}
|
||||
|
||||
static bool tok_eq(const char *a, const char *b, size_t len)
|
||||
{
|
||||
size_t i = 0;
|
||||
|
3
server/modules/routing/avrorouter/test/CMakeLists.txt
Normal file
3
server/modules/routing/avrorouter/test/CMakeLists.txt
Normal file
@ -0,0 +1,3 @@
|
||||
add_executable(test_alter_parsing test_alter_parsing.c)
|
||||
target_link_libraries(test_alter_parsing maxscale-common ${JANSSON_LIBRARIES} ${AVRO_LIBRARIES} maxavro sqlite3 lzma)
|
||||
add_test(test_alter_parsing test_alter_parsing)
|
99
server/modules/routing/avrorouter/test/test_alter_parsing.c
Normal file
99
server/modules/routing/avrorouter/test/test_alter_parsing.c
Normal file
@ -0,0 +1,99 @@
|
||||
#include "../avro_schema.c"
|
||||
|
||||
static struct
|
||||
{
|
||||
const char* statement;
|
||||
const char* target;
|
||||
bool rval;
|
||||
} data[] =
|
||||
{
|
||||
{"/*!40000 ALTER TABLE `t1` DISABLE KEYS */", NULL, false},
|
||||
{"/*!40000 ALTER TABLE `t1` ENABLE KEYS */", NULL, false},
|
||||
{"ADD COLUMN `a` INT", NULL, false},
|
||||
{"ADD COLUMN `a`", NULL, false},
|
||||
{"ALTER TABLE `t1` ADD `account_id` INT", NULL, false},
|
||||
{"ALTER TABLE `t1` ADD `amount` INT", NULL, false},
|
||||
{"ALTER TABLE `t1` ADD `app_id` VARCHAR(64)", NULL, false},
|
||||
{"ALTER TABLE `t1` ADD `create_time` DATETIME", NULL, false},
|
||||
{"alter TABLE t1 add `end_time` varchar(10) DEFAULT NULL COMMENT 'this is a comment'", NULL, false},
|
||||
{"ALTER TABLE `t1` ADD `expire_time` DATETIME", NULL, false},
|
||||
{"ALTER TABLE `t1` ADD `id_a` VARCHAR(128)", NULL, false},
|
||||
{"ALTER TABLE `t1` ADD `id` BIGINT(20)", NULL, false},
|
||||
{"ALTER TABLE `t1` ADD `id` VARCHAR(64)", NULL, false},
|
||||
{"ALTER TABLE `t1` ADD `node_state` INT(4)", NULL, false},
|
||||
{"ALTER TABLE `t1` ADD `no` INT", NULL, false},
|
||||
{"ALTER TABLE `t1` ADD `order_id` INT", NULL, false},
|
||||
{"alter TABLE t1 add `start_time` varchar(10) DEFAULT NULL COMMENT 'this is a comment'", NULL, false},
|
||||
{"ALTER TABLE `t1` ADD `status` INT", NULL, false},
|
||||
{"ALTER TABLE `t1` ADD `task_id` BIGINT(20)", NULL, false},
|
||||
{"alter TABLE t1 add `undo` int(1) DEFAULT '0' COMMENT 'this is a comment'", NULL, false},
|
||||
{"alter table `t1` add unique (`a`,`id`)", NULL, false},
|
||||
{"alter table `t1` add unique (`a`)", NULL, false},
|
||||
{"alter table `t1` add UNIQUE(`a`)", NULL, false},
|
||||
{"ALTER TABLE `t1` ADD UNIQUE `idx_id` USING BTREE (`id`, `result`)", NULL, false},
|
||||
{"ALTER TABLE `t1` ADD `update_time` INT", NULL, false},
|
||||
{"ALTER TABLE `t1` ADD `username` VARCHAR(16)", NULL, false},
|
||||
{"ALTER TABLE `t1` AUTO_INCREMENT = 1", NULL, false},
|
||||
{"ALTER TABLE `t1` CHANGE `account_id` `account_id` BIGINT(20)", NULL, false},
|
||||
{"ALTER TABLE `t1` CHANGE `amount` `amount` DECIMAL(32,2)", NULL, false},
|
||||
{"ALTER TABLE `t1` CHANGE `app_id` `app_id` VARCHAR(64)", NULL, false},
|
||||
{"ALTER TABLE `t1` CHANGE `business_id` `business_id` VARCHAR(128)", NULL, false},
|
||||
{"ALTER TABLE `t1` CHANGE `business_id` `business_id` VARCHAR(64)", NULL, false},
|
||||
{"ALTER TABLE `t1` CHANGE `business_unique_no` `business_unique_no` VARCHAR(64)", NULL, false},
|
||||
{"ALTER TABLE `t1` CHANGE `expire_time` `expire_time` DATETIME", NULL, false},
|
||||
{"ALTER TABLE `t1` CHANGE `id_a` `id_a` VARCHAR(128)", NULL, false},
|
||||
{"ALTER TABLE `t1` CHANGE `id` `id` BIGINT(20)", NULL, false},
|
||||
{"ALTER TABLE `t1` CHANGE `node_state` `node_state` INT(4)", NULL, false},
|
||||
{"ALTER TABLE `t1` CHANGE `order_id` `order_id` BIGINT(20)", NULL, false},
|
||||
{"ALTER TABLE `t1` CHANGE `status` `status` INT(1)", NULL, false},
|
||||
{"ALTER TABLE `t1` CHANGE `update_time` `update_time` TIMESTAMP", NULL, false},
|
||||
{"ALTER TABLE `t1` CHANGE `username` `username` VARCHAR(16)", NULL, false},
|
||||
{"ALTER TABLE `t1` COMMENT = 'a comment'", NULL, false},
|
||||
{"alter table `t1` drop index a", NULL, false},
|
||||
{"alter table t1 drop index t1_idx", NULL, false},
|
||||
{"alter table t1 index(account_id, business_id)", NULL, false},
|
||||
{"ALTER TABLE `t1` MODIFY COLUMN `expire_time` DATETIME DEFAULT NULL COMMENT 'this is a comment' AFTER `update_time`", "update_time", true},
|
||||
{"ALTER TABLE `t1` MODIFY COLUMN `id_a` VARCHAR(128) CHARACTER SET utf8 COLLATE utf8_general_ci COMMENT 'this is a comment' AFTER `username`", "username", true},
|
||||
{"ALTER TABLE `t1` MODIFY COLUMN `number` VARCHAR(64) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 'this is a comment' AFTER `business_id`", "business_id", true},
|
||||
{"ALTER TABLE `t1` MODIFY COLUMN `task_id` BIGINT(20) DEFAULT NULL COMMENT 'this is a comment' AFTER `business_id`", "business_id", true},
|
||||
{"ALTER TABLE `t1` MODIFY COLUMN `username` VARCHAR(16) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'this is a comment' AFTER `business_id`", "business_id", true},
|
||||
{"ALTER TABLE `t1` RENAME `t2`", NULL, false},
|
||||
{"ALTER TABLE `db1`.`t1` ADD COLUMN `num` varchar(32) COMMENT 'this is a comment' AFTER `bank_name`", "bank_name", true},
|
||||
{"ALTER TABLE `db1`.`t1` ADD INDEX `idx_node_state` USING BTREE (`node_state`) comment ''", NULL, false},
|
||||
{"ALTER TABLE `db1`.`t1` CHANGE COLUMN `num` `code` varchar(32) DEFAULT NULL COMMENT 'this is a comment'", NULL, false},
|
||||
{"ALTER TABLE `db1`.`t1` DROP INDEX `a`, ADD INDEX `a` USING BTREE (`a`) comment ''", NULL, false},
|
||||
{"ALTER TABLE `db1`.`t1` DROP INDEX `a`, ADD INDEX `idx_a` USING BTREE (`a`) comment ''", NULL, false},
|
||||
{"ALTER TABLE `t1` CHANGE COLUMN `a` `c` INT AFTER `b`", "b", true},
|
||||
{"ALTER TABLE `t1` CHANGE COLUMN `a` `c` INT first", NULL, true},
|
||||
{"ALTER TABLE `t1` CHANGE COLUMN `a` `c` INT", NULL, false},
|
||||
{"ALTER TABLE `t1` MODIFY COLUMN `a` INT PRIMARY KEY", NULL, false},
|
||||
{NULL}
|
||||
};
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
int rval = 0;
|
||||
|
||||
for (int i = 0; data[i].statement; i++)
|
||||
{
|
||||
const char* target = NULL;
|
||||
int len = 0;
|
||||
const char* stmt = data[i].statement;
|
||||
const char* end = data[i].statement + strlen(data[i].statement);
|
||||
|
||||
if (get_placement_specifier(stmt, end, &target, &len) != data[i].rval)
|
||||
{
|
||||
const char* a = data[i].rval ? "true" : "false";
|
||||
const char* b = data[i].rval ? "false" : "true";
|
||||
printf("Expected '%s', got '%s' for '%s'\n", a, b, data[i].statement);
|
||||
rval++;
|
||||
}
|
||||
else if (((bool)data[i].target != (bool)target) || (strncmp(target, data[i].target, len) != 0))
|
||||
{
|
||||
printf("Expected '%s', got '%.*s' for '%s'\n", data[i].target, len, target, data[i].statement);
|
||||
rval++;
|
||||
}
|
||||
}
|
||||
|
||||
return rval;
|
||||
}
|
Reference in New Issue
Block a user