From bd1da82c961eb997503457e54e4b6f211fb248a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Fri, 23 Jun 2017 11:10:41 +0300 Subject: [PATCH 001/138] Fix cdc_schema.py There's no need to use Python 3 for the script and using it introduces problems due to the poor availability of MySQL Connector/Python for Python3. ENUM, SET and DECIMAL values should have a length of -1 as the length is meaningless for these types. --- server/modules/protocol/examples/cdc_schema.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/modules/protocol/examples/cdc_schema.py b/server/modules/protocol/examples/cdc_schema.py index 1f4c1bd84..7e4f15068 100755 --- a/server/modules/protocol/examples/cdc_schema.py +++ b/server/modules/protocol/examples/cdc_schema.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python # Copyright (c) 2016 MariaDB Corporation Ab # @@ -38,7 +38,7 @@ def parse_field(row): res["real_type"] = name - if len(parts) > 1: + if len(parts) > 1 and name not in ["enum", "set", "decimal"]: res["length"] = int(parts[1].split(')')[0]) else: res["length"] = -1 From cd09e657140df7c09e13ec58795c9560d571446d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Fri, 23 Jun 2017 14:15:23 +0300 Subject: [PATCH 002/138] MXS-1296: Always use a case-insensitive parser The two cases where the case-sensitive parser functions were used don't appear to hold any special meaning. The case-insensitive function should be used as it implements a superset of functionality compared to the case-sensitive version. --- server/core/maxscale/trxboundaryparser.hh | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/server/core/maxscale/trxboundaryparser.hh b/server/core/maxscale/trxboundaryparser.hh index a311f2699..dcd9bd063 100644 --- a/server/core/maxscale/trxboundaryparser.hh +++ b/server/core/maxscale/trxboundaryparser.hh @@ -557,11 +557,6 @@ private: ((*(m_pI + offset) == uc) || (*(m_pI + offset) == lc)); } - bool is_next_char(char c, int offset = 1) const - { - return ((m_pI + offset) < m_pEnd) && (*(m_pI + offset) == c); - } - bool peek_next_char(char* pC) const { bool rc = (m_pI + 1 < m_pEnd); @@ -730,7 +725,7 @@ private: } else if (is_next_alpha('N')) { - if (is_next_char('L', 2)) + if (is_next_alpha('L', 2)) { token = expect_token(TBP_EXPECT_TOKEN("ONLY"), TK_ONLY); } @@ -770,7 +765,7 @@ private: { token = expect_token(TBP_EXPECT_TOKEN("SNAPSHOT"), TK_SNAPSHOT); } - else if (is_next_char('T')) + else if (is_next_alpha('T')) { token = expect_token(TBP_EXPECT_TOKEN("START"), TK_START); } From dc849d1c0fa574a3bfb5ddb4edc8513acf70ddce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Tue, 27 Jun 2017 21:27:20 +0300 Subject: [PATCH 003/138] MXS-1291: Attempt to bind on 0.0.0.0 when :: fails If binding on the IPv6 all interfaces address fails, MaxScale will attempt to bind on the IPv4 address. --- server/core/dcb.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/server/core/dcb.c b/server/core/dcb.c index d14b60534..e5d4c26c0 100644 --- a/server/core/dcb.c +++ b/server/core/dcb.c @@ -3067,6 +3067,15 @@ int dcb_listen(DCB *listener, const char *config, const char *protocol_name) else if (port > 0) { listener_socket = dcb_listen_create_socket_inet(host, port); + + if (listener_socket == -1 && strcmp(host, "::") == 0) + { + /** Attempt to bind to the IPv4 if the default IPv6 one is used */ + MXS_WARNING("Failed to bind on default IPv6 host '::', attempting " + "to bind on IPv4 version '0.0.0.0'"); + strcpy(host, "0.0.0.0"); + listener_socket = dcb_listen_create_socket_inet(host, port); + } } else { From 7bb76f52e5a56249ab058738504c091ca1095d24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Wed, 28 Jun 2017 08:10:49 +0300 Subject: [PATCH 004/138] Disable ssl_load_galera test The normal replication version of the test is disabled so there is no reason to have the Galera version enabled. --- maxscale-system-test/CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/maxscale-system-test/CMakeLists.txt b/maxscale-system-test/CMakeLists.txt index a66c4dfc5..10c6b0096 100644 --- a/maxscale-system-test/CMakeLists.txt +++ b/maxscale-system-test/CMakeLists.txt @@ -592,7 +592,9 @@ add_test_script(ssl sql_queries ssl LABELS maxscale readwritesplit REPL_BACKEND) #add_test_script(ssl_load load_balancing ssl_load LABELS maxscale readwritesplit REPL_BACKEND) # Check load balancing, client ssl is ON, Galera backend -add_test_script(ssl_load_galera load_balancing_galera ssl_load_galera LABELS maxscale readwritesplit GALERA_BACKEND) + +# Galera version is also disabled due to C/C 3.0 strangeness +#add_test_script(ssl_load_galera load_balancing_galera ssl_load_galera LABELS maxscale readwritesplit GALERA_BACKEND) # Testing slaves who have lost their master and how MaxScale works with them add_test_executable(stale_slaves.cpp stale_slaves replication LABELS mysqlmon REPL_BACKEND) From 9b0631f30bea2e6f33df16ba702a57d69dd8d81a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Wed, 28 Jun 2017 08:49:57 +0300 Subject: [PATCH 005/138] MXS-1289: Fix crash on TABLE_MAP with ID > 1024 The active table map was acquired with a modulo operation on the size of the array instead of the number of elements. --- server/modules/routing/avro/avro_rbr.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/server/modules/routing/avro/avro_rbr.c b/server/modules/routing/avro/avro_rbr.c index 5d684d159..f6c6f2b05 100644 --- a/server/modules/routing/avro/avro_rbr.c +++ b/server/modules/routing/avro/avro_rbr.c @@ -112,13 +112,13 @@ bool handle_table_map_event(AVRO_INSTANCE *router, REP_HEADER *hdr, uint8_t *ptr if (old) { - router->active_maps[old->id % sizeof(router->active_maps)] = NULL; + router->active_maps[old->id % MAX_MAPPED_TABLES] = NULL; } hashtable_delete(router->table_maps, table_ident); hashtable_add(router->table_maps, (void*) table_ident, map); hashtable_add(router->open_tables, table_ident, avro_table); save_avro_schema(router->avrodir, json_schema, map); - router->active_maps[map->id % sizeof(router->active_maps)] = map; + router->active_maps[map->id % MAX_MAPPED_TABLES] = map; MXS_DEBUG("Table %s mapped to %lu", table_ident, map->id); rval = true; @@ -145,10 +145,10 @@ bool handle_table_map_event(AVRO_INSTANCE *router, REP_HEADER *hdr, uint8_t *ptr } else { - ss_dassert(router->active_maps[old->id % sizeof(router->active_maps)] == old); - router->active_maps[old->id % sizeof(router->active_maps)] = NULL; + ss_dassert(router->active_maps[old->id % MAX_MAPPED_TABLES] == old); + router->active_maps[old->id % MAX_MAPPED_TABLES] = NULL; table_map_remap(ptr, ev_len, old); - router->active_maps[old->id % sizeof(router->active_maps)] = old; + router->active_maps[old->id % MAX_MAPPED_TABLES] = old; MXS_DEBUG("Table %s re-mapped to %lu", table_ident, old->id); /** No changes in the schema */ rval = true; @@ -275,7 +275,7 @@ bool handle_row_event(AVRO_INSTANCE *router, REP_HEADER *hdr, uint8_t *ptr) /** There should always be a table map event prior to a row event. * TODO: Make the active_maps dynamic */ - TABLE_MAP *map = router->active_maps[table_id % sizeof(router->active_maps)]; + TABLE_MAP *map = router->active_maps[table_id % MAX_MAPPED_TABLES]; if (map) { From 40b173924919303f8f1dd2cfc8c0e72f76d59314 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Wed, 28 Jun 2017 11:25:57 +0300 Subject: [PATCH 006/138] MXS-1299: Fix CREATE TABLE t1 LIKE t2 processing The creation of tables from other tables was not working even though the information was available inside the avrorouter. --- server/modules/routing/avrorouter/avro_file.c | 21 +- .../modules/routing/avrorouter/avro_schema.c | 221 ++++++++++++++++++ .../modules/routing/avrorouter/avrorouter.h | 1 + 3 files changed, 241 insertions(+), 2 deletions(-) diff --git a/server/modules/routing/avrorouter/avro_file.c b/server/modules/routing/avrorouter/avro_file.c index 3602c9fd9..7a4dd827c 100644 --- a/server/modules/routing/avrorouter/avro_file.c +++ b/server/modules/routing/avrorouter/avro_file.c @@ -914,6 +914,15 @@ bool is_create_table_statement(AVRO_INSTANCE *router, char* ptr, size_t len) return rc > 0; } +bool is_create_like_statement(const char* ptr, size_t len) +{ + char sql[len + 1]; + memcpy(sql, ptr, len); + sql[len] = '\0'; + + // This is not pretty but it should work + return strcasestr(sql, " like ") || strcasestr(sql, "(like "); +} /** * @brief Detection of table alteration statements @@ -1023,7 +1032,16 @@ void handle_query_event(AVRO_INSTANCE *router, REP_HEADER *hdr, int *pending_tra if (is_create_table_statement(router, sql, len)) { - TABLE_CREATE *created = table_create_alloc(sql, db); + TABLE_CREATE *created = NULL; + + if (is_create_like_statement(sql, len)) + { + created = table_create_copy(router, sql, len, db); + } + else + { + created = table_create_alloc(sql, db); + } if (created && !save_and_replace_table_create(router, created)) { @@ -1056,7 +1074,6 @@ void handle_query_event(AVRO_INSTANCE *router, REP_HEADER *hdr, int *pending_tra strcat(full_ident, ident); TABLE_CREATE *created = hashtable_fetch(router->created_tables, full_ident); - ss_dassert(created); if (created) { diff --git a/server/modules/routing/avrorouter/avro_schema.c b/server/modules/routing/avrorouter/avro_schema.c index 0150f2e5a..c9d9239c3 100644 --- a/server/modules/routing/avrorouter/avro_schema.c +++ b/server/modules/routing/avrorouter/avro_schema.c @@ -771,6 +771,227 @@ TABLE_CREATE* table_create_alloc(const char* sql, const char* event_db) return rval; } +static const char* TOK_CREATE[] = +{ + "CREATE", + NULL +}; + +static const char* TOK_TABLE[] = +{ + "TABLE", + NULL +}; + +static const char* TOK_GROUP_REPLACE[] = +{ + "OR", + "REPLACE", + NULL +}; + +static const char* TOK_GROUP_EXISTS[] = +{ + "IF", + "NOT", + "EXISTS", + NULL +}; + +/** + * Read one token (i.e. SQL keyword) + */ +static const char* get_token(const char* ptr, const char* end, char* dest) +{ + while (ptr < end && isspace(*ptr)) + { + ptr++; + } + + const char* start = ptr; + + while (ptr < end && !isspace(*ptr)) + { + ptr++; + } + + size_t len = ptr - start; + memcpy(dest, start, len); + dest[len] = '\0'; + + return ptr; +} + +/** + * Consume one token + */ +static bool chomp_one_token(const char* expected, const char** ptr, const char* end, char* buf) +{ + bool rval = false; + const char* next = get_token(*ptr, end, buf); + + if (strcasecmp(buf, expected) == 0) + { + rval = true; + *ptr = next; + } + + return rval; +} + +/** + * Consume all tokens in a group + */ +static bool chomp_tokens(const char** tokens, const char** ptr, const char* end, char* buf) +{ + bool next = true; + bool rval = false; + + do + { + next = false; + + for (int i = 0; tokens[i]; i++) + { + if (chomp_one_token(tokens[i], ptr, end, buf)) + { + rval = true; + next = true; + break; + } + } + } + while (next); + + return rval; +} + +/** + * Remove any extra characters from a string + */ +static void remove_extras(char* str) +{ + char* end = strchr(str, '\0') - 1; + + while (end > str && (*end == '`' || *end == ')' || *end == '(')) + { + *end-- = '\0'; + } + + char* start = str; + + while (start < end && (*start == '`' || *start == ')' || *start == '(')) + { + start++; + } + + size_t len = strlen(start); + + memmove(str, start, len); + str[len] = '\0'; + + ss_dassert(strlen(str) == len); +} + +/** + * Extract both tables from a `CREATE TABLE t1 LIKE t2` statement + */ +static bool extract_create_like_identifier(const char* sql, size_t len, char* target, char* source) +{ + bool rval = false; + char buffer[len + 1]; + buffer[0] = '\0'; + const char* ptr = sql; + const char* end = ptr + sizeof(buffer); + + if (chomp_tokens(TOK_CREATE, &ptr, end, buffer)) + { + chomp_tokens(TOK_GROUP_REPLACE, &ptr, end, buffer); + + if (chomp_tokens(TOK_TABLE, &ptr, end, buffer)) + { + chomp_tokens(TOK_GROUP_EXISTS, &ptr, end, buffer); + + // Read the target table name + ptr = get_token(ptr, end, buffer); + strcpy(target, buffer); + + // Skip the LIKE token + ptr = get_token(ptr, end, buffer); + + // Read the source table name + ptr = get_token(ptr, end, buffer); + remove_extras(buffer); + strcpy(source, buffer); + rval = true; + } + } + + return rval; +} + +/** + * Create a table from another table + */ +TABLE_CREATE* table_create_copy(AVRO_INSTANCE *router, const char* sql, size_t len, const char* db) +{ + TABLE_CREATE* rval = NULL; + char target[MYSQL_TABLE_MAXLEN + 1] = ""; + char source[MYSQL_TABLE_MAXLEN + 1] = ""; + + if (extract_create_like_identifier(sql, len, target, source)) + { + char table_ident[MYSQL_TABLE_MAXLEN + MYSQL_DATABASE_MAXLEN + 2] = ""; + + if (strchr(source, '.') == NULL) + { + strcpy(table_ident, db); + strcat(table_ident, "."); + } + + strcat(table_ident, source); + + TABLE_CREATE *old = hashtable_fetch(router->created_tables, table_ident); + + if (old) + { + int n = old->columns; + char** names = MXS_MALLOC(sizeof(char*) * n); + char** types = MXS_MALLOC(sizeof(char*) * n); + int* lengths = MXS_MALLOC(sizeof(int) * n); + rval = MXS_MALLOC(sizeof(TABLE_CREATE)); + + MXS_ABORT_IF_FALSE(names && types && lengths && rval); + + for (uint64_t i = 0; i < old->columns; i++) + { + names[i] = MXS_STRDUP_A(old->column_names[i]); + types[i] = MXS_STRDUP_A(old->column_types[i]); + lengths[i] = old->column_lengths[i]; + } + + rval->version = 1; + rval->was_used = false; + rval->column_names = names; + rval->column_lengths = lengths; + rval->column_types = types; + rval->columns = old->columns; + rval->database = MXS_STRDUP_A(db); + + char* table = strchr(target, '.'); + table = table ? table + 1 : target; + rval->table = MXS_STRDUP_A(table); + } + else + { + MXS_ERROR("Could not find table '%s' that '%s' is being created from: %.*s", + table_ident, target, (int)len, sql); + } + } + + return rval; +} + /** * Free a TABLE_CREATE structure * @param value Value to free diff --git a/server/modules/routing/avrorouter/avrorouter.h b/server/modules/routing/avrorouter/avrorouter.h index ec9f0f268..ec41c50fc 100644 --- a/server/modules/routing/avrorouter/avrorouter.h +++ b/server/modules/routing/avrorouter/avrorouter.h @@ -302,6 +302,7 @@ extern void read_table_info(uint8_t *ptr, uint8_t post_header_len, uint64_t *tab extern TABLE_MAP *table_map_alloc(uint8_t *ptr, uint8_t hdr_len, TABLE_CREATE* create); extern void table_map_free(TABLE_MAP *map); extern TABLE_CREATE* table_create_alloc(const char* sql, const char* db); +extern TABLE_CREATE* table_create_copy(AVRO_INSTANCE *router, const char* sql, size_t len, const char* db); extern void table_create_free(TABLE_CREATE* value); extern bool table_create_save(TABLE_CREATE *create, const char *filename); extern bool table_create_alter(TABLE_CREATE *create, const char *sql, const char *end); From 2bc5188dae9528aaaf3ee418b6981d94f08cc086 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Wed, 28 Jun 2017 11:36:17 +0300 Subject: [PATCH 007/138] MXS-1304: Don't null-terminate buffers in gw_str_xor The gw_str_xor function is used only for doing XOR operations on binary buffers. --- server/core/utils.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/server/core/utils.c b/server/core/utils.c index 57fe2d9af..b119b897f 100644 --- a/server/core/utils.c +++ b/server/core/utils.c @@ -223,8 +223,6 @@ void gw_str_xor(uint8_t *output, const uint8_t *input1, const uint8_t *input2, u { *output++ = *input1++ ^ *input2++; } - - *output = '\0'; } /********************************************************** From 8e3d4d9dd29ba5ad81a0f3bcd3c052b7fdd4621f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Wed, 28 Jun 2017 11:52:50 +0300 Subject: [PATCH 008/138] MXS-1298: Fix typo in avro error message Changed 'numer' to 'number'. --- avro/maxavro_record.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/avro/maxavro_record.c b/avro/maxavro_record.c index cf461187a..9433ca722 100644 --- a/avro/maxavro_record.c +++ b/avro/maxavro_record.c @@ -183,7 +183,7 @@ json_t* maxavro_record_read_json(MAXAVRO_FILE *file) { long pos = ftell(file->file); MXS_ERROR("Failed to read field value '%s', type '%s' at " - "file offset %ld, record numer %lu.", + "file offset %ld, record number %lu.", file->schema->fields[i].name, type_to_string(file->schema->fields[i].type), pos, file->records_read); From 92d9356968f1ffd349dce37ef71931d47559b30a Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Wed, 28 Jun 2017 11:48:41 +0200 Subject: [PATCH 009/138] Update release notes, change log and upgrading --- Documentation/Changelog.md | 1 + .../MaxScale-2.1.4-Release-Notes.md | 65 +++++++++++++++++++ .../Upgrading/Upgrading-To-MaxScale-2.1.md | 3 +- 3 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 Documentation/Release-Notes/MaxScale-2.1.4-Release-Notes.md diff --git a/Documentation/Changelog.md b/Documentation/Changelog.md index 20b4d173e..34a68e631 100644 --- a/Documentation/Changelog.md +++ b/Documentation/Changelog.md @@ -21,6 +21,7 @@ * MaxScale now supports IPv6 For more details, please refer to: +* [MariaDB MaxScale 2.1.4 Release Notes](Release-Notes/MaxScale-2.1.4-Release-Notes.md) * [MariaDB MaxScale 2.1.3 Release Notes](Release-Notes/MaxScale-2.1.3-Release-Notes.md) * [MariaDB MaxScale 2.1.2 Release Notes](Release-Notes/MaxScale-2.1.2-Release-Notes.md) * [MariaDB MaxScale 2.1.1 Release Notes](Release-Notes/MaxScale-2.1.1-Release-Notes.md) diff --git a/Documentation/Release-Notes/MaxScale-2.1.4-Release-Notes.md b/Documentation/Release-Notes/MaxScale-2.1.4-Release-Notes.md new file mode 100644 index 000000000..1fa22e4aa --- /dev/null +++ b/Documentation/Release-Notes/MaxScale-2.1.4-Release-Notes.md @@ -0,0 +1,65 @@ +# MariaDB MaxScale 2.1.4 Release Notes + +Release 2.1.4 is a GA release. + +This document describes the changes in release 2.1.4, when compared to +release [2.1.3](MaxScale-2.1.3-Release-Notes.md). + +If you are upgrading from release 2.0, please also read the following +release notes: +[2.1.3](./MaxScale-2.1.3-Release-Notes.md) +[2.1.2](./MaxScale-2.1.2-Release-Notes.md) +[2.1.1](./MaxScale-2.1.1-Release-Notes.md) +[2.1.0](./MaxScale-2.1.0-Release-Notes.md) + +For any problems you encounter, please consider submitting a bug +report at [Jira](https://jira.mariadb.org). + +## Changed Features + +### Masking + +* The masking filter now has a default fill character `X`, which + is used if only a _value_ has been specified and the length of + the value does not match the length of the value received from + the server. + + Please refer to the + [masking documentation](../Filters/Masking.md) + for details. + +### maxadmin + +* Error message for failed login attempt has been improved. + +## Bug fixes + +[Here is a list of bugs fixed since the release of MaxScale 2.1.3.](https://jira.mariadb.org/issues/?jql=project%20%3D%20MXS%20AND%20issuetype%20%3D%20Bug%20AND%20status%20%3D%20Closed%20AND%20fixVersion%20%3D%202.1.4) + +* [MXS-1304](https://jira.mariadb.org/browse/MXS-1304) Invalid write in gw_str_xor +* [MXS-1299](https://jira.mariadb.org/browse/MXS-1299) CREATE TABLE LIKE fails with avrorouter +* [MXS-1296](https://jira.mariadb.org/browse/MXS-1296) Lowercase start transaction is not detected +* [MXS-1294](https://jira.mariadb.org/browse/MXS-1294) cdc_schema.py uses Python 3 +* [MXS-1287](https://jira.mariadb.org/browse/MXS-1287) Slaves of external servers can't be used as slaves +* [MXS-1279](https://jira.mariadb.org/browse/MXS-1279) Runtime changes to monitor credentials expect wrong parameter names +* [MXS-1271](https://jira.mariadb.org/browse/MXS-1271) cdc.py consuming 100% of CPU and never sending to kafka + +## Known Issues and Limitations + +There are some limitations and known issues within this version of MaxScale. +For more information, please refer to the [Limitations](../About/Limitations.md) document. + +## Packaging + +RPM and Debian packages are provided for the Linux distributions supported +by MariaDB Enterprise. + +Packages can be downloaded [here](https://mariadb.com/resources/downloads). + +## Source Code + +The source code of MaxScale is tagged at GitHub with a tag, which is identical +with the version of MaxScale. For instance, the tag of version X.Y.Z of MaxScale +is X.Y.Z. + +The source code is available [here](https://github.com/mariadb-corporation/MaxScale). diff --git a/Documentation/Upgrading/Upgrading-To-MaxScale-2.1.md b/Documentation/Upgrading/Upgrading-To-MaxScale-2.1.md index 539adb261..aee13f8dc 100644 --- a/Documentation/Upgrading/Upgrading-To-MaxScale-2.1.md +++ b/Documentation/Upgrading/Upgrading-To-MaxScale-2.1.md @@ -6,7 +6,8 @@ MariaDB MaxScale from version 2.0 to 2.1. For more information about MariaDB MaxScale 2.1, please refer to the [ChangeLog](../Changelog.md). -For a complete list of changes in MaxScale 2.1.0, refer to the +For a complete list of changes in MaxScale 2.1, refer to the +[MaxScale 2.1.4 Release Notes](../Release-Notes/MaxScale-2.1.4-Release-Notes.md). [MaxScale 2.1.3 Release Notes](../Release-Notes/MaxScale-2.1.3-Release-Notes.md). [MaxScale 2.1.2 Release Notes](../Release-Notes/MaxScale-2.1.2-Release-Notes.md). [MaxScale 2.1.1 Release Notes](../Release-Notes/MaxScale-2.1.1-Release-Notes.md). From 49897a22ad8812324e1bebed255b8a969177ae59 Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Mon, 15 May 2017 10:06:30 +0300 Subject: [PATCH 010/138] MXS-1196: Make operation constants sequential The operations do not form a bitmask. Consequently their values can be sequential. --- include/maxscale/query_classifier.h | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/include/maxscale/query_classifier.h b/include/maxscale/query_classifier.h index d886208d5..85f0f54e9 100644 --- a/include/maxscale/query_classifier.h +++ b/include/maxscale/query_classifier.h @@ -82,19 +82,19 @@ typedef enum qc_query_type */ typedef enum qc_query_op { - QUERY_OP_UNDEFINED = 0, - QUERY_OP_SELECT = (1 << 0), - QUERY_OP_UPDATE = (1 << 1), - QUERY_OP_INSERT = (1 << 2), - QUERY_OP_DELETE = (1 << 3), - QUERY_OP_TRUNCATE = (1 << 4), - QUERY_OP_ALTER = (1 << 5), - QUERY_OP_CREATE = (1 << 6), - QUERY_OP_DROP = (1 << 7), - QUERY_OP_CHANGE_DB = (1 << 8), - QUERY_OP_LOAD = (1 << 9), - QUERY_OP_GRANT = (1 << 10), - QUERY_OP_REVOKE = (1 << 11) + QUERY_OP_UNDEFINED = 0, + QUERY_OP_SELECT, + QUERY_OP_UPDATE, + QUERY_OP_INSERT, + QUERY_OP_DELETE, + QUERY_OP_TRUNCATE, + QUERY_OP_ALTER, + QUERY_OP_CREATE, + QUERY_OP_DROP, + QUERY_OP_CHANGE_DB, + QUERY_OP_LOAD, + QUERY_OP_GRANT, + QUERY_OP_REVOKE } qc_query_op_t; /** From 951c96383c3e5cecbc166065e01c29e480a91cf3 Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Mon, 15 May 2017 10:12:36 +0300 Subject: [PATCH 011/138] MXS-1196: Sort operation constants --- include/maxscale/query_classifier.h | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/include/maxscale/query_classifier.h b/include/maxscale/query_classifier.h index 85f0f54e9..d8a0920c5 100644 --- a/include/maxscale/query_classifier.h +++ b/include/maxscale/query_classifier.h @@ -83,18 +83,19 @@ typedef enum qc_query_type typedef enum qc_query_op { QUERY_OP_UNDEFINED = 0, - QUERY_OP_SELECT, - QUERY_OP_UPDATE, - QUERY_OP_INSERT, - QUERY_OP_DELETE, - QUERY_OP_TRUNCATE, + QUERY_OP_ALTER, - QUERY_OP_CREATE, - QUERY_OP_DROP, QUERY_OP_CHANGE_DB, - QUERY_OP_LOAD, + QUERY_OP_CREATE, + QUERY_OP_DELETE, + QUERY_OP_DROP, QUERY_OP_GRANT, - QUERY_OP_REVOKE + QUERY_OP_INSERT, + QUERY_OP_LOAD, + QUERY_OP_REVOKE, + QUERY_OP_SELECT, + QUERY_OP_TRUNCATE, + QUERY_OP_UPDATE, } qc_query_op_t; /** From 43ab0f036e6b67fab9b97a1bb87c9d21db34aee1 Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Mon, 15 May 2017 13:28:29 +0300 Subject: [PATCH 012/138] MXS-1196: Do not parse EXPLAIN statements EXPLAIN statements are no longer parsed completely as doing so makes it hard to modify the grammar for the needs or Oracle SQL. Consequently, for an EXPLAIN statement you now bascially only get the type and the operation (the newly added QUERY_OP_EXPLAIN and QUERY_OP_SHOW). The other information is not interesting and is related to information_schema and similar tables. --- include/maxscale/query_classifier.h | 2 + .../qc_mysqlembedded/qc_mysqlembedded.cc | 266 ++++++++---- query_classifier/qc_sqlite/qc_sqlite.c | 386 +++++++----------- .../qc_sqlite/sqlite-src-3110100/src/parse.y | 29 +- server/core/query_classifier.cc | 52 +-- 5 files changed, 377 insertions(+), 358 deletions(-) diff --git a/include/maxscale/query_classifier.h b/include/maxscale/query_classifier.h index d8a0920c5..e804abcc5 100644 --- a/include/maxscale/query_classifier.h +++ b/include/maxscale/query_classifier.h @@ -89,11 +89,13 @@ typedef enum qc_query_op QUERY_OP_CREATE, QUERY_OP_DELETE, QUERY_OP_DROP, + QUERY_OP_EXPLAIN, QUERY_OP_GRANT, QUERY_OP_INSERT, QUERY_OP_LOAD, QUERY_OP_REVOKE, QUERY_OP_SELECT, + QUERY_OP_SHOW, QUERY_OP_TRUNCATE, QUERY_OP_UPDATE, } qc_query_op_t; diff --git a/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc b/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc index ee7dd83bb..cdd738559 100644 --- a/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc +++ b/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc @@ -593,6 +593,12 @@ static uint32_t resolve_query_type(parsing_info_t *pi, THD* thd) goto return_qtype; } + if (lex->describe) + { + type = QUERY_TYPE_READ; + goto return_qtype; + } + if (skygw_stmt_causes_implicit_commit(lex, &set_autocommit_stmt)) { if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO)) @@ -708,6 +714,10 @@ static uint32_t resolve_query_type(parsing_info_t *pi, THD* thd) } } } + else + { + type |= QUERY_TYPE_READ; + } goto return_qtype; } @@ -798,6 +808,11 @@ static uint32_t resolve_query_type(parsing_info_t *pi, THD* thd) goto return_qtype; break; + case SQLCOM_SHOW_CREATE: + type |= QUERY_TYPE_READ; + goto return_qtype; + break; + case SQLCOM_SHOW_DATABASES: type |= QUERY_TYPE_SHOW_DATABASES; goto return_qtype; @@ -805,6 +820,12 @@ static uint32_t resolve_query_type(parsing_info_t *pi, THD* thd) case SQLCOM_SHOW_FIELDS: type |= QUERY_TYPE_READ; + goto return_qtype; + break; + + case SQLCOM_SHOW_STATUS: + type |= QUERY_TYPE_READ; + goto return_qtype; break; case SQLCOM_SHOW_TABLES: @@ -1265,6 +1286,33 @@ static TABLE_LIST* skygw_get_affected_tables(void* lexptr) return tbl; } +static bool is_show_command(int sql_command) +{ + bool rv = false; + + switch (sql_command) + { + case SQLCOM_SHOW_CREATE: + case SQLCOM_SHOW_DATABASES: + case SQLCOM_SHOW_FIELDS: + case SQLCOM_SHOW_KEYS: + case SQLCOM_SHOW_MASTER_STAT: + case SQLCOM_SHOW_SLAVE_STAT: + case SQLCOM_SHOW_STATUS: + case SQLCOM_SHOW_TABLES: + case SQLCOM_SHOW_TABLE_STATUS: + case SQLCOM_SHOW_VARIABLES: + case SQLCOM_SHOW_WARNS: + rv = true; + break; + + default: + break; + } + + return rv; +} + int32_t qc_mysql_get_table_names(GWBUF* querybuf, int32_t fullnames, char*** tablesp, int32_t* tblsize) { LEX* lex; @@ -1287,6 +1335,11 @@ int32_t qc_mysql_get_table_names(GWBUF* querybuf, int32_t fullnames, char*** tab goto retblock; } + if (lex->describe || is_show_command(lex->sql_command)) + { + goto retblock; + } + lex->current_select = lex->all_selects_list; while (lex->current_select) @@ -1420,16 +1473,19 @@ int32_t qc_mysql_query_has_clause(GWBUF* buf, int32_t* has_clause) if (lex) { - SELECT_LEX* current = lex->all_selects_list; - - while (current && !*has_clause) + if (!lex->describe && !is_show_command(lex->sql_command)) { - if (current->where || current->having) - { - *has_clause = true; - } + SELECT_LEX* current = lex->all_selects_list; - current = current->next_select_in_list(); + while (current && !*has_clause) + { + if (current->where || current->having) + { + *has_clause = true; + } + + current = current->next_select_in_list(); + } } } } @@ -1592,6 +1648,11 @@ int32_t qc_mysql_get_database_names(GWBUF* querybuf, char*** databasesp, int* si goto retblock; } + if (lex->describe || is_show_command(lex->sql_command)) + { + goto retblock; + } + lex->current_select = lex->all_selects_list; while (lex->current_select) @@ -1655,90 +1716,111 @@ int32_t qc_mysql_get_operation(GWBUF* querybuf, int32_t* operation) if (lex) { - switch (lex->sql_command) + if (lex->describe) { - case SQLCOM_SELECT: - *operation = QUERY_OP_SELECT; - break; + *operation = QUERY_OP_EXPLAIN; + } + else + { + switch (lex->sql_command) + { + case SQLCOM_SELECT: + *operation = QUERY_OP_SELECT; + break; - case SQLCOM_CREATE_DB: - case SQLCOM_CREATE_EVENT: - case SQLCOM_CREATE_FUNCTION: - case SQLCOM_CREATE_INDEX: - case SQLCOM_CREATE_PROCEDURE: - case SQLCOM_CREATE_SERVER: - case SQLCOM_CREATE_SPFUNCTION: - case SQLCOM_CREATE_TABLE: - case SQLCOM_CREATE_TRIGGER: - case SQLCOM_CREATE_USER: - case SQLCOM_CREATE_VIEW: - *operation = QUERY_OP_CREATE; - break; + case SQLCOM_CREATE_DB: + case SQLCOM_CREATE_EVENT: + case SQLCOM_CREATE_FUNCTION: + case SQLCOM_CREATE_INDEX: + case SQLCOM_CREATE_PROCEDURE: + case SQLCOM_CREATE_SERVER: + case SQLCOM_CREATE_SPFUNCTION: + case SQLCOM_CREATE_TABLE: + case SQLCOM_CREATE_TRIGGER: + case SQLCOM_CREATE_USER: + case SQLCOM_CREATE_VIEW: + *operation = QUERY_OP_CREATE; + break; - case SQLCOM_ALTER_DB: - case SQLCOM_ALTER_DB_UPGRADE: - case SQLCOM_ALTER_EVENT: - case SQLCOM_ALTER_FUNCTION: - case SQLCOM_ALTER_PROCEDURE: - case SQLCOM_ALTER_SERVER: - case SQLCOM_ALTER_TABLE: - case SQLCOM_ALTER_TABLESPACE: - *operation = QUERY_OP_ALTER; - break; + case SQLCOM_ALTER_DB: + case SQLCOM_ALTER_DB_UPGRADE: + case SQLCOM_ALTER_EVENT: + case SQLCOM_ALTER_FUNCTION: + case SQLCOM_ALTER_PROCEDURE: + case SQLCOM_ALTER_SERVER: + case SQLCOM_ALTER_TABLE: + case SQLCOM_ALTER_TABLESPACE: + *operation = QUERY_OP_ALTER; + break; - case SQLCOM_UPDATE: - case SQLCOM_UPDATE_MULTI: - *operation = QUERY_OP_UPDATE; - break; + case SQLCOM_UPDATE: + case SQLCOM_UPDATE_MULTI: + *operation = QUERY_OP_UPDATE; + break; - case SQLCOM_INSERT: - case SQLCOM_INSERT_SELECT: - case SQLCOM_REPLACE: - case SQLCOM_REPLACE_SELECT: - *operation = QUERY_OP_INSERT; - break; + case SQLCOM_INSERT: + case SQLCOM_INSERT_SELECT: + case SQLCOM_REPLACE: + case SQLCOM_REPLACE_SELECT: + *operation = QUERY_OP_INSERT; + break; - case SQLCOM_DELETE: - case SQLCOM_DELETE_MULTI: - *operation = QUERY_OP_DELETE; - break; + case SQLCOM_DELETE: + case SQLCOM_DELETE_MULTI: + *operation = QUERY_OP_DELETE; + break; - case SQLCOM_TRUNCATE: - *operation = QUERY_OP_TRUNCATE; - break; + case SQLCOM_TRUNCATE: + *operation = QUERY_OP_TRUNCATE; + break; - case SQLCOM_DROP_DB: - case SQLCOM_DROP_EVENT: - case SQLCOM_DROP_FUNCTION: - case SQLCOM_DROP_INDEX: - case SQLCOM_DROP_PROCEDURE: - case SQLCOM_DROP_SERVER: - case SQLCOM_DROP_TABLE: - case SQLCOM_DROP_TRIGGER: - case SQLCOM_DROP_USER: - case SQLCOM_DROP_VIEW: - *operation = QUERY_OP_DROP; - break; + case SQLCOM_DROP_DB: + case SQLCOM_DROP_EVENT: + case SQLCOM_DROP_FUNCTION: + case SQLCOM_DROP_INDEX: + case SQLCOM_DROP_PROCEDURE: + case SQLCOM_DROP_SERVER: + case SQLCOM_DROP_TABLE: + case SQLCOM_DROP_TRIGGER: + case SQLCOM_DROP_USER: + case SQLCOM_DROP_VIEW: + *operation = QUERY_OP_DROP; + break; - case SQLCOM_CHANGE_DB: - *operation = QUERY_OP_CHANGE_DB; - break; + case SQLCOM_CHANGE_DB: + *operation = QUERY_OP_CHANGE_DB; + break; - case SQLCOM_LOAD: - *operation = QUERY_OP_LOAD; - break; + case SQLCOM_LOAD: + *operation = QUERY_OP_LOAD; + break; - case SQLCOM_GRANT: - *operation = QUERY_OP_GRANT; - break; + case SQLCOM_GRANT: + *operation = QUERY_OP_GRANT; + break; - case SQLCOM_REVOKE: - case SQLCOM_REVOKE_ALL: - *operation = QUERY_OP_REVOKE; - break; + case SQLCOM_REVOKE: + case SQLCOM_REVOKE_ALL: + *operation = QUERY_OP_REVOKE; + break; - default: - *operation = QUERY_OP_UNDEFINED; + case SQLCOM_SHOW_CREATE: + case SQLCOM_SHOW_DATABASES: + case SQLCOM_SHOW_FIELDS: + case SQLCOM_SHOW_KEYS: + case SQLCOM_SHOW_MASTER_STAT: + case SQLCOM_SHOW_SLAVE_STAT: + case SQLCOM_SHOW_STATUS: + case SQLCOM_SHOW_TABLES: + case SQLCOM_SHOW_TABLE_STATUS: + case SQLCOM_SHOW_VARIABLES: + case SQLCOM_SHOW_WARNS: + *operation = QUERY_OP_SHOW; + break; + + default: + *operation = QUERY_OP_UNDEFINED; + } } } } @@ -1757,15 +1839,18 @@ int32_t qc_mysql_get_prepare_name(GWBUF* stmt, char** namep) { LEX* lex = get_lex(stmt); - if ((lex->sql_command == SQLCOM_PREPARE) || - (lex->sql_command == SQLCOM_EXECUTE) || - (lex->sql_command == SQLCOM_DEALLOCATE_PREPARE)) + if (!lex->describe) { - name = (char*)malloc(lex->prepared_stmt_name.length + 1); - if (name) + if ((lex->sql_command == SQLCOM_PREPARE) || + (lex->sql_command == SQLCOM_EXECUTE) || + (lex->sql_command == SQLCOM_DEALLOCATE_PREPARE)) { - memcpy(name, lex->prepared_stmt_name.str, lex->prepared_stmt_name.length); - name[lex->prepared_stmt_name.length] = 0; + name = (char*)malloc(lex->prepared_stmt_name.length + 1); + if (name) + { + memcpy(name, lex->prepared_stmt_name.str, lex->prepared_stmt_name.length); + name[lex->prepared_stmt_name.length] = 0; + } } } } @@ -1784,7 +1869,7 @@ int32_t qc_mysql_get_preparable_stmt(GWBUF* stmt, GWBUF** preparable_stmt) { LEX* lex = get_lex(stmt); - if (lex->sql_command == SQLCOM_PREPARE) + if ((lex->sql_command == SQLCOM_PREPARE) && !lex->describe) { parsing_info_t* pi = get_pinfo(stmt); @@ -2483,6 +2568,13 @@ int32_t qc_mysql_get_field_info(GWBUF* buf, const QC_FIELD_INFO** infos, uint32_ return QC_RESULT_ERROR; } + if (lex->describe || is_show_command(lex->sql_command)) + { + *infos = NULL; + *n_infos = 0; + return QC_RESULT_OK; + } + uint32_t usage = 0; switch (lex->sql_command) diff --git a/query_classifier/qc_sqlite/qc_sqlite.c b/query_classifier/qc_sqlite/qc_sqlite.c index 0c15dc531..e5cbe0bfc 100644 --- a/query_classifier/qc_sqlite/qc_sqlite.c +++ b/query_classifier/qc_sqlite/qc_sqlite.c @@ -411,6 +411,11 @@ static void parse_query_string(const char* query, size_t len) const char* suffix = (len > max_len ? "..." : ""); const char* format; + if (this_thread.info->operation == QUERY_OP_EXPLAIN) + { + this_thread.info->status = QC_QUERY_PARSED; + } + if (rc != SQLITE_OK) { if (qc_info_was_tokenized(this_thread.info->status)) @@ -1524,60 +1529,64 @@ void mxs_sqlite3DeleteFrom(Parse* pParse, SrcList* pTabList, Expr* pWhere, SrcLi ss_dassert(info); info->status = QC_QUERY_PARSED; - info->type_mask = QUERY_TYPE_WRITE; - info->operation = QUERY_OP_DELETE; - info->has_clause = pWhere ? true : false; - if (pUsing) + if (info->operation != QUERY_OP_EXPLAIN) { - // Walk through the using declaration and update - // table and database names. - for (int i = 0; i < pUsing->nSrc; ++i) + info->type_mask = QUERY_TYPE_WRITE; + info->operation = QUERY_OP_DELETE; + info->has_clause = pWhere ? true : false; + + if (pUsing) { - struct SrcList_item* pItem = &pUsing->a[i]; - - update_names(info, pItem->zDatabase, pItem->zName); - } - - // Walk through the tablenames while excluding alias - // names from the using declaration. - for (int i = 0; i < pTabList->nSrc; ++i) - { - const struct SrcList_item* pTable = &pTabList->a[i]; - ss_dassert(pTable->zName); - int j = 0; - bool isSame = false; - - do + // Walk through the using declaration and update + // table and database names. + for (int i = 0; i < pUsing->nSrc; ++i) { - struct SrcList_item* pItem = &pUsing->a[j++]; + struct SrcList_item* pItem = &pUsing->a[i]; - if (strcasecmp(pTable->zName, pItem->zName) == 0) + update_names(info, pItem->zDatabase, pItem->zName); + } + + // Walk through the tablenames while excluding alias + // names from the using declaration. + for (int i = 0; i < pTabList->nSrc; ++i) + { + const struct SrcList_item* pTable = &pTabList->a[i]; + ss_dassert(pTable->zName); + int j = 0; + bool isSame = false; + + do { - isSame = true; + struct SrcList_item* pItem = &pUsing->a[j++]; + + if (strcasecmp(pTable->zName, pItem->zName) == 0) + { + isSame = true; + } + else if (pItem->zAlias && (strcasecmp(pTable->zName, pItem->zAlias) == 0)) + { + isSame = true; + } } - else if (pItem->zAlias && (strcasecmp(pTable->zName, pItem->zAlias) == 0)) + while (!isSame && (j < pUsing->nSrc)); + + if (!isSame) { - isSame = true; + // No alias name, update the table name. + update_names(info, pTable->zDatabase, pTable->zName); } } - while (!isSame && (j < pUsing->nSrc)); - - if (!isSame) - { - // No alias name, update the table name. - update_names(info, pTable->zDatabase, pTable->zName); - } } - } - else - { - update_names_from_srclist(info, pTabList); - } + else + { + update_names_from_srclist(info, pTabList); + } - if (pWhere) - { - update_field_infos(info, 0, pWhere, QC_USED_IN_WHERE, QC_TOKEN_MIDDLE, 0); + if (pWhere) + { + update_field_infos(info, 0, pWhere, QC_USED_IN_WHERE, QC_TOKEN_MIDDLE, 0); + } } exposed_sqlite3ExprDelete(pParse->db, pWhere); @@ -1677,36 +1686,40 @@ void mxs_sqlite3Insert(Parse* pParse, ss_dassert(info); info->status = QC_QUERY_PARSED; - info->type_mask = QUERY_TYPE_WRITE; - info->operation = QUERY_OP_INSERT; - ss_dassert(pTabList); - ss_dassert(pTabList->nSrc >= 1); - update_names_from_srclist(info, pTabList); - if (pColumns) + if (info->operation != QUERY_OP_EXPLAIN) { - update_field_infos_from_idlist(info, pColumns, 0, NULL); - } + info->type_mask = QUERY_TYPE_WRITE; + info->operation = QUERY_OP_INSERT; + ss_dassert(pTabList); + ss_dassert(pTabList->nSrc >= 1); + update_names_from_srclist(info, pTabList); - if (pSelect) - { - uint32_t usage; - - if (pSelect->selFlags & SF_Values) // Synthesized from VALUES clause + if (pColumns) { - usage = 0; - } - else - { - usage = QC_USED_IN_SELECT; + update_field_infos_from_idlist(info, pColumns, 0, NULL); } - update_field_infos_from_select(info, pSelect, usage, NULL); - } + if (pSelect) + { + uint32_t usage; - if (pSet) - { - update_field_infos_from_exprlist(info, pSet, 0, NULL); + if (pSelect->selFlags & SF_Values) // Synthesized from VALUES clause + { + usage = 0; + } + else + { + usage = QC_USED_IN_SELECT; + } + + update_field_infos_from_select(info, pSelect, usage, NULL); + } + + if (pSet) + { + update_field_infos_from_exprlist(info, pSet, 0, NULL); + } } exposed_sqlite3SrcListDelete(pParse->db, pTabList); @@ -1737,9 +1750,13 @@ int mxs_sqlite3Select(Parse* pParse, Select* p, SelectDest* pDest) if (!info->initializing) { info->status = QC_QUERY_PARSED; - info->operation = QUERY_OP_SELECT; - maxscaleCollectInfoFromSelect(pParse, p, 0); + if (info->operation != QUERY_OP_EXPLAIN) + { + info->operation = QUERY_OP_SELECT; + + maxscaleCollectInfoFromSelect(pParse, p, 0); + } // NOTE: By convention, the select is deleted in parse.y. } else @@ -1828,24 +1845,28 @@ void mxs_sqlite3Update(Parse* pParse, SrcList* pTabList, ExprList* pChanges, Exp ss_dassert(info); info->status = QC_QUERY_PARSED; - info->type_mask = QUERY_TYPE_WRITE; - info->operation = QUERY_OP_UPDATE; - update_names_from_srclist(info, pTabList); - info->has_clause = (pWhere ? true : false); - if (pChanges) + if (info->operation != QUERY_OP_EXPLAIN) { - for (int i = 0; i < pChanges->nExpr; ++i) + info->type_mask = QUERY_TYPE_WRITE; + info->operation = QUERY_OP_UPDATE; + update_names_from_srclist(info, pTabList); + info->has_clause = (pWhere ? true : false); + + if (pChanges) { - struct ExprList_item* pItem = &pChanges->a[i]; + for (int i = 0; i < pChanges->nExpr; ++i) + { + struct ExprList_item* pItem = &pChanges->a[i]; - update_field_infos(info, 0, pItem->pExpr, QC_USED_IN_SET, QC_TOKEN_MIDDLE, NULL); + update_field_infos(info, 0, pItem->pExpr, QC_USED_IN_SET, QC_TOKEN_MIDDLE, NULL); + } } - } - if (pWhere) - { - update_field_infos(info, 0, pWhere, QC_USED_IN_WHERE, QC_TOKEN_MIDDLE, pChanges); + if (pWhere) + { + update_field_infos(info, 0, pWhere, QC_USED_IN_WHERE, QC_TOKEN_MIDDLE, pChanges); + } } exposed_sqlite3SrcListDelete(pParse->db, pTabList); @@ -2045,7 +2066,7 @@ void maxscaleExecute(Parse* pParse, Token* pName) } } -void maxscaleExplain(Parse* pParse, SrcList* pName) +void maxscaleExplain(Parse* pParse, Token* pNext) { QC_TRACE(); @@ -2054,16 +2075,28 @@ void maxscaleExplain(Parse* pParse, SrcList* pName) info->status = QC_QUERY_PARSED; info->type_mask = QUERY_TYPE_READ; - update_names(info, pName->a[0].zDatabase, pName->a[0].zName); - uint32_t u = QC_USED_IN_SELECT; - update_field_info(info, "information_schema", "COLUMNS", "COLUMN_DEFAULT", u, NULL); - update_field_info(info, "information_schema", "COLUMNS", "COLUMN_KEY", u, NULL); - update_field_info(info, "information_schema", "COLUMNS", "COLUMN_NAME", u, NULL); - update_field_info(info, "information_schema", "COLUMNS", "COLUMN_TYPE", u, NULL); - update_field_info(info, "information_schema", "COLUMNS", "EXTRA", u, NULL); - update_field_info(info, "information_schema", "COLUMNS", "IS_NULLABLE", u, NULL); + info->operation = QUERY_OP_SHOW; - exposed_sqlite3SrcListDelete(pParse->db, pName); + if (pNext) + { + if (pNext->z) + { + const char EXTENDED[] = "EXTENDED"; + const char PARTITIONS[] = "PARTITIONS"; + const char FORMAT[] = "FORMAT"; + const char FOR[] = "FOR"; + +#define MATCHES_KEYWORD(t, k) ((t->n == sizeof(k) - 1) && (strncasecmp(t->z, k, t->n) == 0)) + + if (MATCHES_KEYWORD(pNext, EXTENDED) || + MATCHES_KEYWORD(pNext, PARTITIONS) || + MATCHES_KEYWORD(pNext, FORMAT) || + MATCHES_KEYWORD(pNext, FOR)) + { + info->operation = QUERY_OP_EXPLAIN; + } + } + } } void maxscaleFlush(Parse* pParse, Token* pWhat) @@ -2203,6 +2236,7 @@ void maxscaleKeyword(int token) case TK_DESC: info->status = QC_QUERY_TOKENIZED; info->type_mask = QUERY_TYPE_READ; + info->operation = QUERY_OP_EXPLAIN; break; case TK_DROP: @@ -2219,6 +2253,7 @@ void maxscaleKeyword(int token) case TK_EXPLAIN: info->status = QC_QUERY_TOKENIZED; info->type_mask = QUERY_TYPE_READ; + info->operation = QUERY_OP_EXPLAIN; break; case TK_GRANT: @@ -2656,188 +2691,83 @@ extern void maxscaleShow(Parse* pParse, MxsShow* pShow) ss_dassert(info); info->status = QC_QUERY_PARSED; - - char* zDatabase = NULL; - char* zName = NULL; - - char database[pShow->pDatabase ? pShow->pDatabase->n + 1 : 0]; - if (pShow->pDatabase) - { - strncpy(database, pShow->pDatabase->z, pShow->pDatabase->n); - database[pShow->pDatabase->n] = 0; - zDatabase = database; - } - - char name[pShow->pName ? pShow->pName->n + 1 : 0]; - if (pShow->pName) - { - strncpy(name, pShow->pName->z, pShow->pName->n); - name[pShow->pName->n] = 0; - zName = name; - } + info->operation = QUERY_OP_SHOW; uint32_t u = QC_USED_IN_SELECT; switch (pShow->what) { case MXS_SHOW_COLUMNS: - { - info->type_mask = QUERY_TYPE_READ; - update_names(info, zDatabase, zName); - if (pShow->data == MXS_SHOW_COLUMNS_FULL) - { - update_field_info(info, "information_schema", "COLUMNS", "COLLATION_NAME", u, NULL); - update_field_info(info, "information_schema", "COLUMNS", "COLUMN_COMMENT", u, NULL); - update_field_info(info, "information_schema", "COLUMNS", "COLUMN_DEFAULT", u, NULL); - update_field_info(info, "information_schema", "COLUMNS", "COLUMN_KEY", u, NULL); - update_field_info(info, "information_schema", "COLUMNS", "COLUMN_NAME", u, NULL); - update_field_info(info, "information_schema", "COLUMNS", "COLUMN_TYPE", u, NULL); - update_field_info(info, "information_schema", "COLUMNS", "EXTRA", u, NULL); - update_field_info(info, "information_schema", "COLUMNS", "IS_NULLABLE", u, NULL); - update_field_info(info, "information_schema", "COLUMNS", "PRIVILEGES", u, NULL); - } - else - { - update_field_info(info, "information_schema", "COLUMNS", "COLUMN_DEFAULT", u, NULL); - update_field_info(info, "information_schema", "COLUMNS", "COLUMN_KEY", u, NULL); - update_field_info(info, "information_schema", "COLUMNS", "COLUMN_NAME", u, NULL); - update_field_info(info, "information_schema", "COLUMNS", "COLUMN_TYPE", u, NULL); - update_field_info(info, "information_schema", "COLUMNS", "EXTRA", u, NULL); - update_field_info(info, "information_schema", "COLUMNS", "IS_NULLABLE", u, NULL); - } - } + info->type_mask = QUERY_TYPE_READ; break; case MXS_SHOW_CREATE_VIEW: - { - info->type_mask = QUERY_TYPE_WRITE; - update_names(info, zDatabase, zName); - } + info->type_mask = QUERY_TYPE_READ; break; case MXS_SHOW_CREATE_TABLE: - { - info->type_mask = QUERY_TYPE_WRITE; - update_names(info, zDatabase, zName); - } + info->type_mask = QUERY_TYPE_READ; break; case MXS_SHOW_DATABASES: - { - info->type_mask = QUERY_TYPE_SHOW_DATABASES; - update_names(info, "information_schema", "SCHEMATA"); - update_field_info(info, "information_schema", "SCHEMATA", "SCHEMA_NAME", u, NULL); - } + info->type_mask = QUERY_TYPE_SHOW_DATABASES; break; case MXS_SHOW_INDEX: case MXS_SHOW_INDEXES: case MXS_SHOW_KEYS: - { - info->type_mask = QUERY_TYPE_WRITE; - update_names(info, "information_schema", "STATISTICS"); - update_field_info(info, "information_schema", "STATISTICS", "CARDINALITY", u, NULL); - update_field_info(info, "information_schema", "STATISTICS", "COLLATION", u, NULL); - update_field_info(info, "information_schema", "STATISTICS", "COLUMN_NAME", u, NULL); - update_field_info(info, "information_schema", "STATISTICS", "COMMENT", u, NULL); - update_field_info(info, "information_schema", "STATISTICS", "INDEX_COMMENT", u, NULL); - update_field_info(info, "information_schema", "STATISTICS", "INDEX_NAME", u, NULL); - update_field_info(info, "information_schema", "STATISTICS", "INDEX_TYPE", u, NULL); - update_field_info(info, "information_schema", "STATISTICS", "NON_UNIQUE", u, NULL); - update_field_info(info, "information_schema", "STATISTICS", "NULLABLE", u, NULL); - update_field_info(info, "information_schema", "STATISTICS", "PACKED", u, NULL); - update_field_info(info, "information_schema", "STATISTICS", "SEQ_IN_INDEX", u, NULL); - update_field_info(info, "information_schema", "STATISTICS", "SUB_PART", u, NULL); - update_field_info(info, "information_schema", "STATISTICS", "TABLE_NAME", u, NULL); - } + info->type_mask = QUERY_TYPE_WRITE; break; case MXS_SHOW_TABLE_STATUS: - { - info->type_mask = QUERY_TYPE_WRITE; - update_names(info, "information_schema", "TABLES"); - update_field_info(info, "information_schema", "TABLES", "AUTO_INCREMENT", u, NULL); - update_field_info(info, "information_schema", "TABLES", "AVG_ROW_LENGTH", u, NULL); - update_field_info(info, "information_schema", "TABLES", "CHECKSUM", u, NULL); - update_field_info(info, "information_schema", "TABLES", "CHECK_TIME", u, NULL); - update_field_info(info, "information_schema", "TABLES", "CREATE_OPTIONS", u, NULL); - update_field_info(info, "information_schema", "TABLES", "CREATE_TIME", u, NULL); - update_field_info(info, "information_schema", "TABLES", "DATA_FREE", u, NULL); - update_field_info(info, "information_schema", "TABLES", "DATA_LENGTH", u, NULL); - update_field_info(info, "information_schema", "TABLES", "ENGINE", u, NULL); - update_field_info(info, "information_schema", "TABLES", "INDEX_LENGTH", u, NULL); - update_field_info(info, "information_schema", "TABLES", "MAX_DATA_LENGTH", u, NULL); - update_field_info(info, "information_schema", "TABLES", "ROW_FORMAT", u, NULL); - update_field_info(info, "information_schema", "TABLES", "TABLE_COLLATION", u, NULL); - update_field_info(info, "information_schema", "TABLES", "TABLE_COMMENT", u, NULL); - update_field_info(info, "information_schema", "TABLES", "TABLE_NAME", u, NULL); - update_field_info(info, "information_schema", "TABLES", "TABLE_ROWS", u, NULL); - update_field_info(info, "information_schema", "TABLES", "UPDATE_TIME", u, NULL); - update_field_info(info, "information_schema", "TABLES", "VERSION", u, NULL); - } + info->type_mask = QUERY_TYPE_WRITE; break; case MXS_SHOW_STATUS: + switch (pShow->data) { - switch (pShow->data) - { - case MXS_SHOW_VARIABLES_GLOBAL: - case MXS_SHOW_VARIABLES_SESSION: - case MXS_SHOW_VARIABLES_UNSPECIFIED: - // TODO: qc_mysqlembedded does not set the type bit. - info->type_mask = QUERY_TYPE_UNKNOWN; - update_names(info, "information_schema", "SESSION_STATUS"); - update_field_info(info, "information_schema", "SESSION_STATUS", "VARIABLE_NAME", u, NULL); - update_field_info(info, "information_schema", "SESSION_STATUS", "VARIABLE_VALUE", u, NULL); - break; + case MXS_SHOW_VARIABLES_GLOBAL: + case MXS_SHOW_VARIABLES_SESSION: + case MXS_SHOW_VARIABLES_UNSPECIFIED: + info->type_mask = QUERY_TYPE_READ; + break; - case MXS_SHOW_STATUS_MASTER: - info->type_mask = QUERY_TYPE_WRITE; - break; + case MXS_SHOW_STATUS_MASTER: + info->type_mask = QUERY_TYPE_WRITE; + break; - case MXS_SHOW_STATUS_SLAVE: - info->type_mask = QUERY_TYPE_READ; - break; + case MXS_SHOW_STATUS_SLAVE: + info->type_mask = QUERY_TYPE_READ; + break; - case MXS_SHOW_STATUS_ALL_SLAVES: - info->type_mask = QUERY_TYPE_READ; - break; + case MXS_SHOW_STATUS_ALL_SLAVES: + info->type_mask = QUERY_TYPE_READ; + break; - default: - break; - } + default: + info->type_mask = QUERY_TYPE_READ; + break; } break; case MXS_SHOW_TABLES: - { - info->type_mask = QUERY_TYPE_SHOW_TABLES; - update_names(info, "information_schema", "TABLE_NAMES"); - update_field_info(info, "information_schema", "TABLE_NAMES", "TABLE_NAME", u, NULL); - } + info->type_mask = QUERY_TYPE_SHOW_TABLES; break; case MXS_SHOW_VARIABLES: + if (pShow->data == MXS_SHOW_VARIABLES_GLOBAL) { - if (pShow->data == MXS_SHOW_VARIABLES_GLOBAL) - { - info->type_mask = QUERY_TYPE_GSYSVAR_READ; - } - else - { - info->type_mask = QUERY_TYPE_SYSVAR_READ; - } - update_names(info, "information_schema", "SESSION_VARIABLES"); - update_field_info(info, "information_schema", "SESSION_STATUS", "VARIABLE_NAME", u, NULL); - update_field_info(info, "information_schema", "SESSION_STATUS", "VARIABLE_VALUE", u, NULL); + info->type_mask = QUERY_TYPE_GSYSVAR_READ; + } + else + { + info->type_mask = QUERY_TYPE_SYSVAR_READ; } break; case MXS_SHOW_WARNINGS: - { - // qc_mysqliembedded claims this. - info->type_mask = QUERY_TYPE_WRITE; - } + // qc_mysqliembedded claims this. + info->type_mask = QUERY_TYPE_WRITE; break; default: diff --git a/query_classifier/qc_sqlite/sqlite-src-3110100/src/parse.y b/query_classifier/qc_sqlite/sqlite-src-3110100/src/parse.y index 1f24bbfde..d11269e59 100644 --- a/query_classifier/qc_sqlite/sqlite-src-3110100/src/parse.y +++ b/query_classifier/qc_sqlite/sqlite-src-3110100/src/parse.y @@ -113,7 +113,7 @@ extern void maxscaleDeallocate(Parse*, Token* pName); extern void maxscaleDo(Parse*, ExprList* pEList); extern void maxscaleDrop(Parse*, MxsDrop* pDrop); extern void maxscaleExecute(Parse*, Token* pName); -extern void maxscaleExplain(Parse*, SrcList* pName); +extern void maxscaleExplain(Parse*, Token* pNext); extern void maxscaleFlush(Parse*, Token* pWhat); extern void maxscaleHandler(Parse*, mxs_handler_t, SrcList* pFullName, Token* pName); extern void maxscaleLoadData(Parse*, SrcList* pFullName); @@ -276,33 +276,22 @@ input ::= cmdlist. cmdlist ::= cmdlist ecmd. cmdlist ::= ecmd. ecmd ::= SEMI. -ecmd ::= explain cmdx SEMI. +ecmd ::= explain SEMI. +ecmd ::= cmdx SEMI. %ifdef MAXSCALE explain_kw ::= EXPLAIN. // Also covers DESCRIBE explain_kw ::= DESC. -ecmd ::= explain_kw fullname(X) SEMI. { - pParse->explain = 1; - maxscaleExplain(pParse, X); -} +explain ::= explain_kw. { pParse->explain = 1; } // deferred_id is defined later, after the id token_class has been defined. -ecmd ::= explain FOR deferred_id INTEGER SEMI. { // FOR CONNECTION connection_id +explain ::= explain_kw deferred_id(A). { maxscaleExplain(pParse, &A); } +explain ::= explain_kw deferred_id(A) DOT deferred_id. { maxscaleExplain(pParse, &A); } +ecmd ::= explain FOR(A) deferred_id INTEGER SEMI. { // FOR CONNECTION connection_id pParse->explain = 1; - maxscaleExplain(pParse, 0); + maxscaleExplain(pParse, &A); } %endif -explain ::= . %ifndef SQLITE_OMIT_EXPLAIN -%ifdef MAXSCALE -explain_type_opt ::= . -explain_type_opt ::= deferred_id. // EXTENDED | PARTITIONS -explain_type_opt ::= deferred_id eq deferred_id. // FORMAT = {TRADITIONAL|JSON} - -explain ::= explain_kw explain_type_opt. { pParse->explain = 1; } -%endif -%ifndef MAXSCALE -explain ::= EXPLAIN. { pParse->explain = 1; } -%endif %ifndef MAXSCALE explain ::= EXPLAIN QUERY PLAN. { pParse->explain = 2; } %endif @@ -2675,7 +2664,7 @@ type_options(A) ::= type_options CHARACTER SET ids. {A|=8;} type_options(A) ::= type_options CHARSET ids. {A|=8;} // deferred_id is used instead of id before the token_class id has been defined. -deferred_id ::= id. +deferred_id(A) ::= id(X). {A=X;} as_opt ::= . as_opt ::= AS. diff --git a/server/core/query_classifier.cc b/server/core/query_classifier.cc index 17b8c41fa..d708f26b3 100644 --- a/server/core/query_classifier.cc +++ b/server/core/query_classifier.cc @@ -494,42 +494,48 @@ const char* qc_op_to_string(qc_query_op_t op) case QUERY_OP_UNDEFINED: return "QUERY_OP_UNDEFINED"; - case QUERY_OP_SELECT: - return "QUERY_OP_SELECT"; - - case QUERY_OP_UPDATE: - return "QUERY_OP_UPDATE"; - - case QUERY_OP_INSERT: - return "QUERY_OP_INSERT"; - - case QUERY_OP_DELETE: - return "QUERY_OP_DELETE"; - - case QUERY_OP_TRUNCATE: - return "QUERY_OP_TRUNCATE"; - case QUERY_OP_ALTER: return "QUERY_OP_ALTER"; - case QUERY_OP_CREATE: - return "QUERY_OP_CREATE"; - - case QUERY_OP_DROP: - return "QUERY_OP_DROP"; - case QUERY_OP_CHANGE_DB: return "QUERY_OP_CHANGE_DB"; - case QUERY_OP_LOAD: - return "QUERY_OP_LOAD"; + case QUERY_OP_CREATE: + return "QUERY_OP_CREATE"; + + case QUERY_OP_DELETE: + return "QUERY_OP_DELETE"; + + case QUERY_OP_DROP: + return "QUERY_OP_DROP"; + + case QUERY_OP_EXPLAIN: + return "QUERY_OP_EXPLAIN"; case QUERY_OP_GRANT: return "QUERY_OP_GRANT"; + case QUERY_OP_INSERT: + return "QUERY_OP_INSERT"; + + case QUERY_OP_LOAD: + return "QUERY_OP_LOAD"; + case QUERY_OP_REVOKE: return "QUERY_OP_REVOKE"; + case QUERY_OP_SELECT: + return "QUERY_OP_SELECT"; + + case QUERY_OP_SHOW: + return "QUERY_OP_SHOW"; + + case QUERY_OP_TRUNCATE: + return "QUERY_OP_TRUNCATE"; + + case QUERY_OP_UPDATE: + return "QUERY_OP_UPDATE"; + default: return "UNKNOWN_QUERY_OP"; } From b191e6ecda1de0526eace58d7762298da9b098fd Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Tue, 25 Apr 2017 13:00:16 +0300 Subject: [PATCH 013/138] MXS-1196: qc_mysqlembedded will not always claim a stmt was parsed When developing the oracle related parser extensions, it makes things simpler if also qc_mysqlembedded properly reports when it cannot parse a statement. Note, although this change is marked for 2.1, it will not be merged into the first 2.1 GA release. --- .../qc_mysqlembedded/qc_mysqlembedded.cc | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc b/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc index cdd738559..6304bf543 100644 --- a/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc +++ b/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc @@ -89,6 +89,7 @@ typedef struct parsing_info_st size_t function_infos_len; size_t function_infos_capacity; GWBUF* preparable_stmt; + qc_parse_result_t result; #if defined(SS_DEBUG) skygw_chk_t pi_chk_tail; #endif @@ -148,7 +149,11 @@ int32_t qc_mysql_parse(GWBUF* querybuf, uint32_t collect, int32_t* result) if (parsed) { - *result = QC_QUERY_PARSED; + parsing_info_t* pi = (parsing_info_t*) gwbuf_get_buffer_object_data(querybuf, + GWBUF_PARSING_INFO); + ss_dassert(pi); + + *result = pi->result; } else { @@ -270,7 +275,11 @@ static bool parse_query(GWBUF* querybuf) * Create parse_tree inside thd. * thd and lex are readable even if creating parse tree fails. */ - create_parse_tree(thd); + if (create_parse_tree(thd)) + { + pi->result = QC_QUERY_PARSED; + } + /** Add complete parsing info struct to the query buffer */ gwbuf_add_buffer_object(querybuf, GWBUF_PARSING_INFO, @@ -454,7 +463,7 @@ static bool create_parse_tree(THD* thd) } return_here: - return failp; + return !failp; } /** @@ -833,6 +842,10 @@ static uint32_t resolve_query_type(parsing_info_t *pi, THD* thd) goto return_qtype; break; + case SQLCOM_END: + goto return_qtype; + break; + default: type |= QUERY_TYPE_WRITE; break; @@ -1546,6 +1559,7 @@ static parsing_info_t* parsing_info_init(void (*donefun)(void *)) /** Set handle and free function to parsing info struct */ pi->pi_handle = mysql; pi->pi_done_fp = donefun; + pi->result = QC_QUERY_INVALID; retblock: return pi; From 13fad9933c232b2458e99bb4b95ba92cb76abb31 Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Fri, 28 Apr 2017 16:35:12 +0300 Subject: [PATCH 014/138] MXS-1196: Make qc_mysqlembedded 10.3 compatible. - Strings no longer null-terminated but pointer + length. - Preparable statements NOT yet handled. --- .../qc_mysqlembedded/qc_mysqlembedded.cc | 68 ++++++++++++++++--- 1 file changed, 57 insertions(+), 11 deletions(-) diff --git a/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc b/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc index 6304bf543..341557d69 100644 --- a/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc +++ b/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc @@ -113,6 +113,19 @@ static bool ensure_query_is_parsed(GWBUF* query); static bool parse_query(GWBUF* querybuf); static bool query_is_parsed(GWBUF* buf); +#if MYSQL_VERSION_MINOR >= 3 +inline void get_string_and_length(const LEX_CSTRING& ls, const char** s, size_t* length) +{ + *s = ls.str; + *length = ls.length; +} +#else +inline void get_string_and_length(const char* cs, const char** s, size_t* length) +{ + *s = cs; + *length = cs ? strlen(cs) : 0; +} +#endif /** * Ensures that the query is parsed. If it is not already parsed, it @@ -883,8 +896,6 @@ static uint32_t resolve_query_type(parsing_info_t *pi, THD* thd) Item::Type itype; itype = item->type(); - MXS_DEBUG("%lu [resolve_query_type] Item %s:%s", - pthread_self(), item->name, STRITEMTYPE(itype)); if (itype == Item::SUBSELECT_ITEM) { @@ -978,10 +989,18 @@ static uint32_t resolve_query_type(parsing_info_t *pi, THD* thd) /** System session variable */ case Item_func::GSYSVAR_FUNC: { - const char* name = item->name; + const char* name; + size_t length; + get_string_and_length(item->name, &name, &length); + + const char last_insert_id[] = "@@last_insert_id"; + const char identity[] = "@@identity"; + if (name && - ((strcasecmp(name, "@@last_insert_id") == 0) || - (strcasecmp(name, "@@identity") == 0))) + (((length == sizeof(last_insert_id) - 1) && + (strcasecmp(name, last_insert_id) == 0)) || + ((length == sizeof(identity) - 1) && + (strcasecmp(name, identity) == 0)))) { func_qtype |= QUERY_TYPE_MASTER_READ; } @@ -1889,6 +1908,9 @@ int32_t qc_mysql_get_preparable_stmt(GWBUF* stmt, GWBUF** preparable_stmt) if (!pi->preparable_stmt) { +#if MYSQL_VERSION_MINOR >= 3 + ss_info_dassert(!true, "Preparable statements in MariaDB 10.3 not yet handled."); +#else const char* preparable_stmt = lex->prepared_stmt_code.str; size_t preparable_stmt_len = lex->prepared_stmt_code.length; size_t payload_len = preparable_stmt_len + 1; @@ -1930,6 +1952,7 @@ int32_t qc_mysql_get_preparable_stmt(GWBUF* stmt, GWBUF** preparable_stmt) } pi->preparable_stmt = preperable_packet; +#endif } *preparable_stmt = pi->preparable_stmt; @@ -1948,9 +1971,13 @@ static bool should_exclude(const char* name, List* excludep) while (!exclude && (exclude_item = ilist++)) { - const char* exclude_name = exclude_item->name; + const char* exclude_name; + size_t length; + get_string_and_length(exclude_item->name, &exclude_name, &length); - if (exclude_name && (strcasecmp(name, exclude_name) == 0)) + if (exclude_name && + (strlen(name) == length) && + (strcasecmp(name, exclude_name) == 0)) { exclude = true; } @@ -2123,7 +2150,12 @@ static void add_field_info(parsing_info_t* pi, Item_field* item, uint32_t usage, { const char* database = item->db_name; const char* table = item->table_name; - const char* column = item->field_name; + const char* s; + size_t l; + get_string_and_length(item->field_name, &s, &l); + char column[l + 1]; + strncpy(column, s, l); + column[l] = 0; LEX* lex = get_lex(pi); @@ -2212,7 +2244,12 @@ static void add_field_info(parsing_info_t* pi, Item* item, uint32_t usage, List< { const char* database = NULL; const char* table = NULL; - const char* column = item->name; + const char* s; + size_t l; + get_string_and_length(item->name, &s, &l); + char column[l + 1]; + strncpy(column, s, l); + column[l] = 0; add_field_info(pi, database, table, column, usage, excludep); } @@ -2382,7 +2419,10 @@ static void update_field_infos(parsing_info_t* pi, // Embedded library silently changes "mod" into "%". We need to check // what it originally was, so that the result agrees with that of // qc_sqlite. - if (func_item->name && (strncasecmp(func_item->name, "mod", 3) == 0)) + const char* s; + size_t l; + get_string_and_length(func_item->name, &s, &l); + if (s && (strncasecmp(s, "mod", 3) == 0)) { strcpy(func_name, "mod"); } @@ -2398,7 +2438,10 @@ static void update_field_infos(parsing_info_t* pi, // Embedded library silently changes "substring" into "substr". We need // to check what it originally was, so that the result agrees with // that of qc_sqlite. We reserved space for this above. - if (func_item->name && (strncasecmp(func_item->name, "substring", 9) == 0)) + const char* s; + size_t l; + get_string_and_length(func_item->name, &s, &l); + if (s && (strncasecmp(s, "substring", 9) == 0)) { strcpy(func_name, "substring"); } @@ -2706,7 +2749,10 @@ const char* server_options[] = "--no-defaults", "--datadir=", "--language=", +#if MYSQL_VERSION_MINOR < 3 + // TODO: 10.3 understands neither "--skip-innodb" or "--innodb=OFF", although it should. "--skip-innodb", +#endif "--default-storage-engine=myisam", NULL }; From 9ae3ab522d98b045a9ce44c7f35d76bdc0b63439 Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Fri, 28 Apr 2017 16:41:47 +0300 Subject: [PATCH 015/138] MXS-1196: Turn on Oracle compatibility --- query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc b/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc index 341557d69..c57ae97a8 100644 --- a/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc +++ b/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc @@ -2838,6 +2838,10 @@ int32_t qc_mysql_process_init(void) #endif MXS_NOTICE("Query classifier initialized."); inited = true; + +#if MYSQL_VERSION_MINOR >= 3 + global_system_variables.sql_mode |= MODE_ORACLE; +#endif } } From 298d5642f648a66b3e7f8a90312a1d4eb5c194f5 Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Fri, 28 Apr 2017 16:44:18 +0300 Subject: [PATCH 016/138] MXS-1196: Accept top-level variable assignments In Oracle you can write set autocommit=1 or autocommit:=1 --- .../qc_sqlite/sqlite-src-3110100/src/parse.y | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/query_classifier/qc_sqlite/sqlite-src-3110100/src/parse.y b/query_classifier/qc_sqlite/sqlite-src-3110100/src/parse.y index d11269e59..574e6236d 100644 --- a/query_classifier/qc_sqlite/sqlite-src-3110100/src/parse.y +++ b/query_classifier/qc_sqlite/sqlite-src-3110100/src/parse.y @@ -278,6 +278,8 @@ cmdlist ::= ecmd. ecmd ::= SEMI. ecmd ::= explain SEMI. ecmd ::= cmdx SEMI. +ecmd ::= oracle_variable_assignment SEMI. +ecmd ::= explain cmdx SEMI. %ifdef MAXSCALE explain_kw ::= EXPLAIN. // Also covers DESCRIBE explain_kw ::= DESC. @@ -3232,4 +3234,13 @@ cmd ::= TRUNCATE table_opt nm(X) dbnm(Y). { maxscaleTruncate(pParse, pDatabase, pName); } +//////////////////////// ORACLE //////////////////////////////////// +// +oracle_variable_assignment ::= id(X) EQ expr(Y). { + Expr* pX = sqlite3PExpr(pParse, TK_ID, 0, 0, &X); + Expr* pExpr = sqlite3PExpr(pParse, TK_EQ, pX, Y.pExpr, 0); + ExprList* pExprList = sqlite3ExprListAppend(pParse, 0, pExpr); + maxscaleSet(pParse, 0, MXS_SET_VARIABLES, pExprList); +} + %endif From 518f7e81cad0e6c629979538057eb2a7b12fdcc2 Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Tue, 2 May 2017 14:26:35 +0300 Subject: [PATCH 017/138] MXS-1196: Handle preparable statements when db is 10.3 --- query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc b/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc index c57ae97a8..683ccde8b 100644 --- a/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc +++ b/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc @@ -1908,11 +1908,15 @@ int32_t qc_mysql_get_preparable_stmt(GWBUF* stmt, GWBUF** preparable_stmt) if (!pi->preparable_stmt) { + const char* preparable_stmt; + size_t preparable_stmt_len; #if MYSQL_VERSION_MINOR >= 3 - ss_info_dassert(!true, "Preparable statements in MariaDB 10.3 not yet handled."); + preparable_stmt = lex->prepared_stmt_code->name.str; + preparable_stmt_len = lex->prepared_stmt_code->name.length; #else - const char* preparable_stmt = lex->prepared_stmt_code.str; - size_t preparable_stmt_len = lex->prepared_stmt_code.length; + preparable_stmt = lex->prepared_stmt_code.str; + preparable_stmt_len = lex->prepared_stmt_code.length; +#endif size_t payload_len = preparable_stmt_len + 1; size_t packet_len = MYSQL_HEADER_LEN + payload_len; @@ -1952,7 +1956,6 @@ int32_t qc_mysql_get_preparable_stmt(GWBUF* stmt, GWBUF** preparable_stmt) } pi->preparable_stmt = preperable_packet; -#endif } *preparable_stmt = pi->preparable_stmt; From da0900786aaa3f11ffbc552e85fa65f2903c4120 Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Tue, 2 May 2017 15:55:34 +0300 Subject: [PATCH 018/138] MXS-1196: Accept UNIQUE as synonym for DISTINCT --- query_classifier/qc_sqlite/sqlite-src-3110100/src/parse.y | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/query_classifier/qc_sqlite/sqlite-src-3110100/src/parse.y b/query_classifier/qc_sqlite/sqlite-src-3110100/src/parse.y index 574e6236d..04893a5f6 100644 --- a/query_classifier/qc_sqlite/sqlite-src-3110100/src/parse.y +++ b/query_classifier/qc_sqlite/sqlite-src-3110100/src/parse.y @@ -1113,6 +1113,7 @@ select_into(A) ::= INTO OUTFILE STRING. {A = sqlite3ExprListAppend(pParse, 0, 0) %type select_options {int} select_options(A) ::= . {A = 0;} select_options(A) ::= select_options DISTINCT. {A = SF_Distinct;} +select_options(A) ::= select_options UNIQUE. {A = SF_Distinct;} select_options(A) ::= select_options ALL. {A = SF_All;} select_options(A) ::= select_options(X) HIGH_PRIORITY. {A = X;} select_options(A) ::= select_options(X) SELECT_OPTIONS_KW. {A = X;} @@ -1122,6 +1123,9 @@ select_options(A) ::= select_options(X) STRAIGHT_JOIN. {A = X;} // present and false (0) if it is not. // %type distinct {int} +%ifdef MAXSCALE +distinct(A) ::= UNIQUE. {A = SF_Distinct;} +%endif distinct(A) ::= DISTINCT. {A = SF_Distinct;} distinct(A) ::= ALL. {A = SF_All;} distinct(A) ::= . {A = 0;} From 62a64cba31a066edc21e3c77eb748346884ddf47 Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Tue, 2 May 2017 15:59:43 +0300 Subject: [PATCH 019/138] MXS-1196: Report user var read when prepared statements --- query_classifier/qc_sqlite/qc_sqlite.c | 4 ++-- .../qc_sqlite/sqlite-src-3110100/src/parse.y | 14 +++++++++----- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/query_classifier/qc_sqlite/qc_sqlite.c b/query_classifier/qc_sqlite/qc_sqlite.c index e5cbe0bfc..3a3d97ff2 100644 --- a/query_classifier/qc_sqlite/qc_sqlite.c +++ b/query_classifier/qc_sqlite/qc_sqlite.c @@ -2038,7 +2038,7 @@ void maxscaleDrop(Parse* pParse, MxsDrop* pDrop) info->operation = QUERY_OP_DROP; } -void maxscaleExecute(Parse* pParse, Token* pName) +void maxscaleExecute(Parse* pParse, Token* pName, int type_mask) { QC_TRACE(); @@ -2046,7 +2046,7 @@ void maxscaleExecute(Parse* pParse, Token* pName) ss_dassert(info); info->status = QC_QUERY_PARSED; - info->type_mask = QUERY_TYPE_WRITE; + info->type_mask = (QUERY_TYPE_WRITE | type_mask); // If information is collected in several passes, then we may // this information already. diff --git a/query_classifier/qc_sqlite/sqlite-src-3110100/src/parse.y b/query_classifier/qc_sqlite/sqlite-src-3110100/src/parse.y index 04893a5f6..407290fa8 100644 --- a/query_classifier/qc_sqlite/sqlite-src-3110100/src/parse.y +++ b/query_classifier/qc_sqlite/sqlite-src-3110100/src/parse.y @@ -64,6 +64,7 @@ enum { QUERY_TYPE_READ = 0x000002, /*< Read database data:any */ QUERY_TYPE_WRITE = 0x000004, /*< Master data will be modified:master */ + QUERY_TYPE_USERVAR_READ = 0x000040, /*< Read a user variable:master or any */ }; typedef enum qc_field_usage @@ -112,7 +113,8 @@ extern void maxscaleCheckTable(Parse*, SrcList* pTables); extern void maxscaleDeallocate(Parse*, Token* pName); extern void maxscaleDo(Parse*, ExprList* pEList); extern void maxscaleDrop(Parse*, MxsDrop* pDrop); -extern void maxscaleExecute(Parse*, Token* pName); +extern void maxscaleExecute(Parse*, Token* pName, int type_mask); +extern void maxscaleExplain(Parse*, SrcList* pName); extern void maxscaleExplain(Parse*, Token* pNext); extern void maxscaleFlush(Parse*, Token* pWhat); extern void maxscaleHandler(Parse*, mxs_handler_t, SrcList* pFullName, Token* pName); @@ -2848,11 +2850,13 @@ prepare ::= PREPARE nm(X) FROM STRING(Y). execute_variables ::= VARIABLE. execute_variables ::= execute_variables COMMA VARIABLE. -execute_variables_opt ::= . -execute_variables_opt ::= USING execute_variables. +%type execute_variables_opt {int} -execute ::= EXECUTE nm(X) execute_variables_opt. { - maxscaleExecute(pParse, &X); +execute_variables_opt(A) ::= . { A = 0; } +execute_variables_opt(A) ::= USING execute_variables. { A = QUERY_TYPE_USERVAR_READ; } + +execute ::= EXECUTE nm(X) execute_variables_opt(Y). { + maxscaleExecute(pParse, &X, Y); } dod ::= DEALLOCATE. From cd6e9fa44bd77eace4411f6ab68c59f5186cc437 Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Tue, 2 May 2017 16:04:41 +0300 Subject: [PATCH 020/138] MXS-1196: Extend flags of compare Sometimes you want to know whether the parsing using different queryclassifiers differs, irrespective of whether they agree upon the outcome or not. With -R it is now possible to cause a difference in the return value of qc_parse() to be printed. --- query_classifier/test/compare.cc | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/query_classifier/test/compare.cc b/query_classifier/test/compare.cc index 7b6fc611e..226d47677 100644 --- a/query_classifier/test/compare.cc +++ b/query_classifier/test/compare.cc @@ -53,6 +53,7 @@ char USAGE[] = "-B arguments for the second classifier\n" "-s compare single statement\n" "-S strict, also require that the parse result is identical\n" + "-R strict reporting, report if parse result is different\n" "-v 0, only return code\n" " 1, query and result for failed cases\n" " 2, all queries, and result for failed cases\n" @@ -75,6 +76,7 @@ struct State bool result_printed; bool stop_at_error; bool strict; + bool strict_reporting; size_t line; size_t n_statements; size_t n_errors; @@ -87,6 +89,7 @@ struct State false, // result_printed true, // stop_at_error false, // strict + false, // strict reporting 0, // line 0, // n_statements 0, // n_errors @@ -339,7 +342,10 @@ bool compare_parse(QUERY_CLASSIFIER* pClassifier1, GWBUF* pCopy1, else { ss << "INF: "; - success = true; + if (!global.strict_reporting) + { + success = true; + } } ss << static_cast(rv1) << " != " << static_cast(rv2); @@ -1317,7 +1323,7 @@ int main(int argc, char* argv[]) size_t rounds = 1; int v = VERBOSITY_NORMAL; int c; - while ((c = getopt(argc, argv, "r:d1:2:v:A:B:s:S")) != -1) + while ((c = getopt(argc, argv, "r:d1:2:v:A:B:s:SR")) != -1) { switch (c) { @@ -1357,6 +1363,10 @@ int main(int argc, char* argv[]) global.strict = true; break; + case 'R': + global.strict_reporting = true; + break; + default: rc = EXIT_FAILURE; break; From 53dfa818e312f0a03195982f16c54c60bc8ed6c5 Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Wed, 3 May 2017 14:36:48 +0300 Subject: [PATCH 021/138] MXS-1196: Make MODE_ORACLE optional Turned on by providing an argument when loading the query classifier. --- .../qc_mysqlembedded/qc_mysqlembedded.cc | 44 ++++++++++++++++--- 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc b/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc index 683ccde8b..6c2114164 100644 --- a/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc +++ b/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc @@ -2801,12 +2801,46 @@ void configure_options(const char* datadir, const char* langdir) } -int32_t qc_mysql_setup(const char* args) +int32_t qc_mysql_setup(const char* zArgs) { - if (args) + if (zArgs) { +#if MYSQL_VERSION_MINOR >= 3 + char args[strlen(zArgs) + 1]; + strcpy(args, zArgs); + + char *p1; + char *token = strtok_r(args, ";", &p1); + + while (token) + { + char *p2; + char* key = trim(strtok_r(token, "=", &p2)); + + if (strcmp(key, "sql_mode") == 0) + { + char* value = trim(p2); + + if (strcmp(value, "MODE_ORACLE") == 0) + { + global_system_variables.sql_mode |= MODE_ORACLE; + } + else + { + MXS_WARNING("Unknown value \"%s\" for key \"%s\"", value, key); + } + } + else + { + MXS_WARNING("Unknown argument \"%s\".", key); + } + + token = strtok_r(NULL, ";", &p1); + } +#else MXS_WARNING("'%s' provided as arguments, " - "even though no arguments are supported.", args); + "even though no arguments are supported.", zArgs); +#endif } return QC_RESULT_OK; @@ -2841,10 +2875,6 @@ int32_t qc_mysql_process_init(void) #endif MXS_NOTICE("Query classifier initialized."); inited = true; - -#if MYSQL_VERSION_MINOR >= 3 - global_system_variables.sql_mode |= MODE_ORACLE; -#endif } } From 8a0455c2fe626c6480d2cc7841c38b886b56ddfc Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Wed, 3 May 2017 15:46:02 +0300 Subject: [PATCH 022/138] MXS-1196: Allow delimiters to be multi-character In some new test files, the delimiter is 2 characters. --- query_classifier/test/testreader.cc | 15 ++++++++++----- query_classifier/test/testreader.hh | 2 +- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/query_classifier/test/testreader.cc b/query_classifier/test/testreader.cc index b7aadc057..49fba60a7 100644 --- a/query_classifier/test/testreader.cc +++ b/query_classifier/test/testreader.cc @@ -193,7 +193,7 @@ TestReader::TestReader(istream& in, size_t line) : m_in(in) , m_line(line) - , m_delimiter(';') + , m_delimiter(";") { init(); } @@ -244,7 +244,7 @@ TestReader::result_t TestReader::get_statement(std::string& stmt) trim(line); if (line.length() > 0) { - m_delimiter = line.at(0); + m_delimiter = line; } continue; @@ -268,15 +268,20 @@ TestReader::result_t TestReader::get_statement(std::string& stmt) stmt += line; - char c = line.at(line.length() - 1); + string c; + + if (line.length() >= m_delimiter.length()) + { + c = line.substr(line.length() - m_delimiter.length()); + } if (c == m_delimiter) { - if (c != ';') + if (c != ";") { // If the delimiter was something else but ';' we need to // remove that before giving the line to the classifiers. - stmt.erase(stmt.length() - 1); + stmt.erase(stmt.length() - m_delimiter.length()); } if (!skip) diff --git a/query_classifier/test/testreader.hh b/query_classifier/test/testreader.hh index 68b930a5c..224f92260 100644 --- a/query_classifier/test/testreader.hh +++ b/query_classifier/test/testreader.hh @@ -76,7 +76,7 @@ private: private: std::istream& m_in; /*< The stream we are using. */ size_t m_line; /*< The current line. */ - char m_delimiter; /*< The current delimiter. */ + std::string m_delimiter; /*< The current delimiter. */ }; }; From e0c78d42425e150815d5437238e8962ec11d2fbd Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Thu, 4 May 2017 11:19:43 +0300 Subject: [PATCH 023/138] MXS-1196: Qc arguments are separated by ",", not by ";" --- query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc b/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc index 6c2114164..e02789254 100644 --- a/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc +++ b/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc @@ -2810,7 +2810,7 @@ int32_t qc_mysql_setup(const char* zArgs) strcpy(args, zArgs); char *p1; - char *token = strtok_r(args, ";", &p1); + char *token = strtok_r(args, ",", &p1); while (token) { @@ -2835,7 +2835,7 @@ int32_t qc_mysql_setup(const char* zArgs) MXS_WARNING("Unknown argument \"%s\".", key); } - token = strtok_r(NULL, ";", &p1); + token = strtok_r(NULL, ",", &p1); } #else MXS_WARNING("'%s' provided as arguments, " From 0c51dd75fec6c62f7897b36fe6848306c737f11a Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Thu, 4 May 2017 11:22:16 +0300 Subject: [PATCH 024/138] MXS-1196: Pre 10.3 or 10.3 mode selected with option The embedded parser of 10.3 parses things slightly differently than how the embedded parser of earlier releases does. When comparing the output of different query classifiers you need to be able to specify with what qc_sqlite should be compatible with. --- query_classifier/qc_sqlite/qc_sqlite.c | 121 ++++++++++++++++++++----- 1 file changed, 99 insertions(+), 22 deletions(-) diff --git a/query_classifier/qc_sqlite/qc_sqlite.c b/query_classifier/qc_sqlite/qc_sqlite.c index 3a3d97ff2..39b55607a 100644 --- a/query_classifier/qc_sqlite/qc_sqlite.c +++ b/query_classifier/qc_sqlite/qc_sqlite.c @@ -95,6 +95,31 @@ typedef enum qc_log_level QC_LOG_NON_TOKENIZED, } qc_log_level_t; +typedef enum qc_parse_as +{ + QC_PARSE_AS_DEFAULT, // Parse as embedded lib does before 10.3 + QC_PARSE_AS_103 // Parse as embedded lib does in 10.3 +} qc_parse_as_t; + +/** + * Defines what a particular name should be mapped to. + */ +typedef struct qc_name_mapping +{ + const char* from; + const char* to; +} QC_NAME_MAPPING; + +static QC_NAME_MAPPING function_name_mappings_default[] = +{ + { NULL, NULL } +}; + +static QC_NAME_MAPPING function_name_mappings_103[] = +{ + { "now", "current_timestamp" }, + { NULL, NULL } +}; /** * The state of qc_sqlite. @@ -104,6 +129,8 @@ static struct bool initialized; bool setup; qc_log_level_t log_level; + qc_parse_as_t parse_as; + QC_NAME_MAPPING* function_name_mappings; } this_unit; /** @@ -139,6 +166,7 @@ static void info_finish(QC_SQLITE_INFO* info); static void info_free(QC_SQLITE_INFO* info); static QC_SQLITE_INFO* info_init(QC_SQLITE_INFO* info, uint32_t collect); static void log_invalid_data(GWBUF* query, const char* message); +static const char* map_function_name(const char* name); static bool parse_query(GWBUF* query, uint32_t collect); static void parse_query_string(const char* query, size_t len); static bool query_is_parsed(GWBUF* query, uint32_t collect); @@ -642,6 +670,26 @@ static void log_invalid_data(GWBUF* query, const char* message) } } +static const char* map_function_name(const char* from) +{ + QC_NAME_MAPPING* map = this_unit.function_name_mappings; + const char* to = NULL; + + while (!to && map->from) + { + if (strcasecmp(from, map->from) == 0) + { + to = map->to; + } + else + { + ++map; + } + } + + return to ? to : from; +} + static bool should_exclude(const char* zName, const ExprList* pExclude) { int i; @@ -795,6 +843,8 @@ static void update_function_info(QC_SQLITE_INFO* info, return; } + name = map_function_name(name); + QC_FUNCTION_INFO item = { (char*)name, usage }; int i; @@ -2853,54 +2903,81 @@ static bool get_key_and_value(char* arg, const char** pkey, const char** pvalue) return p != NULL; } -static char ARG_LOG_UNRECOGNIZED_STATEMENTS[] = "log_unrecognized_statements"; +static const char ARG_LOG_UNRECOGNIZED_STATEMENTS[] = "log_unrecognized_statements"; +static const char ARG_PARSE_AS[] = "parse_as"; -static int32_t qc_sqlite_setup(const char* args) +static int32_t qc_sqlite_setup(const char* cargs) { QC_TRACE(); assert(!this_unit.setup); qc_log_level_t log_level = QC_LOG_NOTHING; + qc_parse_as_t parse_as = QC_PARSE_AS_DEFAULT; + QC_NAME_MAPPING* function_name_mappings = function_name_mappings_default; - if (args) + if (cargs) { - char arg[strlen(args) + 1]; - strcpy(arg, args); + char args[strlen(cargs) + 1]; + strcpy(args, cargs); - const char* key; - const char* value; + char *p1; + char *token = strtok_r(args, ",", &p1); - if (get_key_and_value(arg, &key, &value)) + while (token) { - if (strcmp(key, ARG_LOG_UNRECOGNIZED_STATEMENTS) == 0) + const char* key; + const char* value; + + if (get_key_and_value(token, &key, &value)) { - char *end; - - long l = strtol(value, &end, 0); - - if ((*end == 0) && (l >= QC_LOG_NOTHING) && (l <= QC_LOG_NON_TOKENIZED)) + if (strcmp(key, ARG_LOG_UNRECOGNIZED_STATEMENTS) == 0) { - log_level = l; + char *end; + + long l = strtol(value, &end, 0); + + if ((*end == 0) && (l >= QC_LOG_NOTHING) && (l <= QC_LOG_NON_TOKENIZED)) + { + log_level = l; + } + else + { + MXS_WARNING("'%s' is not a number between %d and %d.", + value, QC_LOG_NOTHING, QC_LOG_NON_TOKENIZED); + } + } + else if (strcmp(key, ARG_PARSE_AS) == 0) + { + if (strcmp(value, "10.3") == 0) + { + parse_as = QC_PARSE_AS_103; + function_name_mappings = function_name_mappings_103; + MXS_NOTICE("Parsing as 10.3"); + } + else + { + MXS_WARNING("'%s' is not a recognized value for '%s'. " + "Parsing as pre-10.3.", value, key); + } } else { - MXS_WARNING("'%s' is not a number between %d and %d.", - value, QC_LOG_NOTHING, QC_LOG_NON_TOKENIZED); + MXS_WARNING("'%s' is not a recognized argument.", key); } } else { - MXS_WARNING("'%s' is not a recognized argument.", key); + MXS_WARNING("'%s' is not a recognized argument string.", args); } - } - else - { - MXS_WARNING("'%s' is not a recognized argument string.", args); + + token = strtok_r(NULL, ",", &p1); } } this_unit.setup = true; this_unit.log_level = log_level; + this_unit.parse_as = parse_as; + this_unit.function_name_mappings = function_name_mappings; return this_unit.setup ? QC_RESULT_OK : QC_RESULT_ERROR; } From 422ea7f912f617a70cae1a7d066e069574505d2c Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Thu, 4 May 2017 12:40:10 +0300 Subject: [PATCH 025/138] MXS-1196: In 10.3 a unary minus is not a function --- query_classifier/qc_sqlite/qc_sqlite.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/query_classifier/qc_sqlite/qc_sqlite.c b/query_classifier/qc_sqlite/qc_sqlite.c index 39b55607a..366e8e203 100644 --- a/query_classifier/qc_sqlite/qc_sqlite.c +++ b/query_classifier/qc_sqlite/qc_sqlite.c @@ -1146,10 +1146,25 @@ static void update_field_infos(QC_SQLITE_INFO* info, case TK_REM: case TK_SLASH: case TK_STAR: - case TK_UMINUS: update_function_info(info, get_token_symbol(pExpr->op), usage); break; + case TK_UMINUS: + switch (this_unit.parse_as) + { + case QC_PARSE_AS_DEFAULT: + update_function_info(info, get_token_symbol(pExpr->op), usage); + break; + + case QC_PARSE_AS_103: + // In MariaDB 10.3 a unary minus is not considered a function. + break; + + default: + ss_dassert(!true); + } + break; + case TK_FUNCTION: if (zToken) { From 9ea380c456a7de741c39c36590c67733c45254a0 Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Thu, 4 May 2017 14:46:51 +0300 Subject: [PATCH 026/138] MXS-1196: Turn on 10.3 mode for qc_sqlite Turn on 10.3 mode for qc_sqlite if qc_mysqlembedded is built using 10.3. --- query_classifier/test/compare.cc | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/query_classifier/test/compare.cc b/query_classifier/test/compare.cc index 226d47677..9f0ab2cca 100644 --- a/query_classifier/test/compare.cc +++ b/query_classifier/test/compare.cc @@ -20,6 +20,7 @@ #include #include #include +#include #define MYSQL_COM_QUIT COM_QUIT #define MYSQL_COM_INIT_DB COM_INIT_DB #define MYSQL_COM_CHANGE_USER COM_CHANGE_USER @@ -39,6 +40,12 @@ using std::ostream; using std::string; using std::stringstream; +#if MYSQL_VERSION_MAJOR == 10 && MYSQL_VERSION_MINOR == 3 +#define USING_MARIADB_103 +#else +#undef USING_MARIADB_103 +#endif + namespace { @@ -1317,7 +1324,11 @@ int main(int argc, char* argv[]) const char* zClassifier1 = "qc_mysqlembedded"; const char* zClassifier2 = "qc_sqlite"; const char* zClassifier1Args = NULL; +#if defined(USING_MARIADB_103) + const char* zClassifier2Args = "parse_as=10.3,log_unrecognized_statements=1"; +#else const char* zClassifier2Args = "log_unrecognized_statements=1"; +#endif const char* zStatement = NULL; size_t rounds = 1; From 32d4a6a8c75434c604aeaf3cd99afa631c2dd56c Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Wed, 10 May 2017 10:06:02 +0300 Subject: [PATCH 027/138] MXS-1196: Oracle mode needs to be turned on repeatedly Seems it not sufficient to turn it on when the qc module is loaded. --- query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc b/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc index e02789254..69dc531da 100644 --- a/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc +++ b/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc @@ -127,6 +127,8 @@ inline void get_string_and_length(const char* cs, const char** s, size_t* length } #endif +static uint32_t sql_mode = 0; + /** * Ensures that the query is parsed. If it is not already parsed, it * will be parsed. @@ -139,6 +141,7 @@ bool ensure_query_is_parsed(GWBUF* query) if (!parsed) { + global_system_variables.sql_mode |= sql_mode; parsed = parse_query(query); if (!parsed) @@ -2823,7 +2826,7 @@ int32_t qc_mysql_setup(const char* zArgs) if (strcmp(value, "MODE_ORACLE") == 0) { - global_system_variables.sql_mode |= MODE_ORACLE; + sql_mode = MODE_ORACLE; } else { From 0de99dc686d21c6a944978e6647b8193b2c6b44d Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Wed, 10 May 2017 15:52:29 +0300 Subject: [PATCH 028/138] MXS-1196: Use a syntactically correct stmt for init --- query_classifier/qc_sqlite/qc_sqlite.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/query_classifier/qc_sqlite/qc_sqlite.c b/query_classifier/qc_sqlite/qc_sqlite.c index 366e8e203..29a08b46f 100644 --- a/query_classifier/qc_sqlite/qc_sqlite.c +++ b/query_classifier/qc_sqlite/qc_sqlite.c @@ -3087,7 +3087,7 @@ static int32_t qc_sqlite_thread_init(void) // With this statement we cause sqlite3 to initialize itself, so that it // is not done as part of the actual classification of data. - const char* s = "CREATE TABLE __maxscale__internal__ (int field UNIQUE)"; + const char* s = "CREATE TABLE __maxscale__internal__ (field int UNIQUE)"; size_t len = strlen(s); this_thread.info->query = s; From 593070b865baaaf4f67382248dcbd67ba3c6e5ce Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Thu, 11 May 2017 14:59:35 +0300 Subject: [PATCH 029/138] MXS-1196: Tentative commit --- .../qc_sqlite/sqlite-src-3110100/src/parse.y | 22 ++++++++++++------- .../sqlite-src-3110100/tool/mkkeywordhash.c | 3 +++ 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/query_classifier/qc_sqlite/sqlite-src-3110100/src/parse.y b/query_classifier/qc_sqlite/sqlite-src-3110100/src/parse.y index 407290fa8..d780ef3f8 100644 --- a/query_classifier/qc_sqlite/sqlite-src-3110100/src/parse.y +++ b/query_classifier/qc_sqlite/sqlite-src-3110100/src/parse.y @@ -114,7 +114,6 @@ extern void maxscaleDeallocate(Parse*, Token* pName); extern void maxscaleDo(Parse*, ExprList* pEList); extern void maxscaleDrop(Parse*, MxsDrop* pDrop); extern void maxscaleExecute(Parse*, Token* pName, int type_mask); -extern void maxscaleExplain(Parse*, SrcList* pName); extern void maxscaleExplain(Parse*, Token* pNext); extern void maxscaleFlush(Parse*, Token* pWhat); extern void maxscaleHandler(Parse*, mxs_handler_t, SrcList* pFullName, Token* pName); @@ -280,8 +279,7 @@ cmdlist ::= ecmd. ecmd ::= SEMI. ecmd ::= explain SEMI. ecmd ::= cmdx SEMI. -ecmd ::= oracle_variable_assignment SEMI. -ecmd ::= explain cmdx SEMI. +ecmd ::= oracle_assignment SEMI. %ifdef MAXSCALE explain_kw ::= EXPLAIN. // Also covers DESCRIBE explain_kw ::= DESC. @@ -583,14 +581,14 @@ columnid(A) ::= nm(X). { %endif %ifdef MAXSCALE /*ABORT*/ ACTION AFTER ALGORITHM /*ANALYZE*/ /*ASC*/ /*ATTACH*/ - /*BEFORE*/ BEGIN BY + /*BEFORE*/ /*BEGIN*/ BY // TODO: BINARY is a reserved word and should not automatically convert into an identifer. // TODO: However, if not here then rules such as CAST need to be modified. BINARY /*CASCADE*/ CAST CLOSE COLUMNKW COLUMNS COMMENT CONCURRENT /*CONFLICT*/ DATA /*DATABASE*/ DEALLOCATE DEFERRED /*DESC*/ /*DETACH*/ DUMPFILE /*EACH*/ END ENUM EXCLUSIVE /*EXPLAIN*/ - FIRST FLUSH /*FOR*/ + FIRST FLUSH /*FOR*/ FORMAT GLOBAL // TODO: IF is a reserved word and should not automatically convert into an identifer. IF IMMEDIATE INITIALLY INSTEAD @@ -601,9 +599,9 @@ columnid(A) ::= nm(X). { OF OFFSET OPEN QUICK RAISE RECURSIVE /*REINDEX*/ RELEASE /*RENAME*/ REPLACE RESTRICT ROLLBACK ROLLUP ROW - SAVEPOINT SELECT_OPTIONS_KW SLAVE START STATUS + SAVEPOINT SELECT_OPTIONS_KW SLAVE /*START*/ STATUS TABLES TEMP TEMPTABLE /*TRIGGER*/ - TRUNCATE + /*TRUNCATE*/ // TODO: UNSIGNED is a reserved word and should not automatically convert into an identifer. // TODO: However, if not here then rules such as CAST need to be modified. UNSIGNED @@ -656,6 +654,9 @@ eq ::= EQ. nm(A) ::= id(X). {A = X;} nm(A) ::= STRING(X). {A = X;} nm(A) ::= JOIN_KW(X). {A = X;} +nm(A) ::= START(X). {A = X;} +nm(A) ::= TRUNCATE(X). {A = X;} +nm(A) ::= BEGIN(X). {A = X;} // A typetoken is really one or more tokens that form a type name such // as can be found after the column name in a CREATE TABLE statement. @@ -1769,6 +1770,9 @@ term(A) ::= DEFAULT(X). {spanExpr(&A, pParse, @X, &X);} term(A) ::= NULL(X). {spanExpr(&A, pParse, @X, &X);} expr(A) ::= id(X). {spanExpr(&A, pParse, TK_ID, &X);} expr(A) ::= JOIN_KW(X). {spanExpr(&A, pParse, TK_ID, &X);} +expr(A) ::= START(X). {spanExpr(&A, pParse, TK_ID, &X);} +expr(A) ::= TRUNCATE(X). {spanExpr(&A, pParse, TK_ID, &X);} +expr(A) ::= BEGIN(X). {spanExpr(&A, pParse, TK_ID, &X);} expr(A) ::= nm(X) DOT nm(Y). { Expr *temp1 = sqlite3PExpr(pParse, TK_ID, 0, 0, &X); Expr *temp2 = sqlite3PExpr(pParse, TK_ID, 0, 0, &Y); @@ -3244,7 +3248,9 @@ cmd ::= TRUNCATE table_opt nm(X) dbnm(Y). { //////////////////////// ORACLE //////////////////////////////////// // -oracle_variable_assignment ::= id(X) EQ expr(Y). { +//ecmd ::= oracle_assignment SEMI. +//cmd ::= oracle_assignment. +oracle_assignment ::= ID(X) EQ expr(Y). { Expr* pX = sqlite3PExpr(pParse, TK_ID, 0, 0, &X); Expr* pExpr = sqlite3PExpr(pParse, TK_EQ, pX, Y.pExpr, 0); ExprList* pExprList = sqlite3ExprListAppend(pParse, 0, pExpr); diff --git a/query_classifier/qc_sqlite/sqlite-src-3110100/tool/mkkeywordhash.c b/query_classifier/qc_sqlite/sqlite-src-3110100/tool/mkkeywordhash.c index 7a6ce5782..7895d3371 100644 --- a/query_classifier/qc_sqlite/sqlite-src-3110100/tool/mkkeywordhash.c +++ b/query_classifier/qc_sqlite/sqlite-src-3110100/tool/mkkeywordhash.c @@ -268,6 +268,9 @@ static Keyword aKeywordTable[] = { { "FORCE", "TK_FORCE", ALWAYS }, #endif { "FOREIGN", "TK_FOREIGN", FKEY }, +#ifdef MAXSCALE + { "FORMAT", "TK_FORMAT", ALWAYS }, +#endif { "FROM", "TK_FROM", ALWAYS }, { "FULL", "TK_JOIN_KW", ALWAYS }, #ifdef MAXSCALE From e7ef6c855f5b6f3bfb50669ccdb8deb694a3bf3f Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Tue, 16 May 2017 11:14:51 +0300 Subject: [PATCH 030/138] MXS-1196: Register if variables are used in function calls --- query_classifier/qc_sqlite/qc_sqlite.c | 6 +++- .../qc_sqlite/sqlite-src-3110100/src/parse.y | 29 ++++++++++--------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/query_classifier/qc_sqlite/qc_sqlite.c b/query_classifier/qc_sqlite/qc_sqlite.c index 29a08b46f..89a5e3e94 100644 --- a/query_classifier/qc_sqlite/qc_sqlite.c +++ b/query_classifier/qc_sqlite/qc_sqlite.c @@ -2008,7 +2008,7 @@ void maxscaleAlterTable(Parse *pParse, /* Parser context. */ exposed_sqlite3SrcListDelete(pParse->db, pSrc); } -void maxscaleCall(Parse* pParse, SrcList* pName) +void maxscaleCall(Parse* pParse, SrcList* pName, int uses_variables) { QC_TRACE(); @@ -2017,6 +2017,10 @@ void maxscaleCall(Parse* pParse, SrcList* pName) info->status = QC_QUERY_PARSED; info->type_mask = QUERY_TYPE_WRITE; + if (uses_variables) + { + info->type_mask |= QUERY_TYPE_USERVAR_READ; + } exposed_sqlite3SrcListDelete(pParse->db, pName); } diff --git a/query_classifier/qc_sqlite/sqlite-src-3110100/src/parse.y b/query_classifier/qc_sqlite/sqlite-src-3110100/src/parse.y index d780ef3f8..5bc0a5e1d 100644 --- a/query_classifier/qc_sqlite/sqlite-src-3110100/src/parse.y +++ b/query_classifier/qc_sqlite/sqlite-src-3110100/src/parse.y @@ -108,7 +108,7 @@ extern void mxs_sqlite3Update(Parse*, SrcList*, ExprList*, Expr*, int); extern void maxscaleCollectInfoFromSelect(Parse*, Select*, int); extern void maxscaleAlterTable(Parse*, mxs_alter_t command, SrcList*, Token*); -extern void maxscaleCall(Parse*, SrcList* pName); +extern void maxscaleCall(Parse*, SrcList* pName, int uses_variables); extern void maxscaleCheckTable(Parse*, SrcList* pTables); extern void maxscaleDeallocate(Parse*, Token* pName); extern void maxscaleDo(Parse*, ExprList* pEList); @@ -2691,21 +2691,24 @@ default_opt ::= DEFAULT. // cmd ::= call. -call_arg ::= INTEGER. -call_arg ::= FLOAT. -call_arg ::= STRING. -call_arg ::= id. -call_arg ::= VARIABLE. +%type call_arg {int} +call_arg(A) ::= INTEGER. {A=0;} +call_arg(A) ::= FLOAT. {A=0;} +call_arg(A) ::= STRING. {A=0;} +call_arg(A) ::= id. {A=0;} +call_arg(A) ::= VARIABLE. {A=1;} -call_args ::= call_arg. -call_args ::= call_args COMMA call_arg. +%type call_args {int} +call_args(A) ::= call_arg(X). {A=X;} +call_args(A) ::= call_args(X) COMMA call_arg(Y). {A=X|Y;} -call_args_opt ::= . -call_args_opt ::= LP RP. -call_args_opt ::= LP call_args RP. +%type call_args_opt {int} +call_args_opt(A) ::= . {A=0;} +call_args_opt(A) ::= LP RP. {A=0;} +call_args_opt(A) ::= LP call_args(X) RP. {A=X;} -call ::= CALL fullname(X) call_args_opt. { - maxscaleCall(pParse, X); +call ::= CALL fullname(X) call_args_opt(Y). { + maxscaleCall(pParse, X, Y); } //////////////////////// DROP FUNCTION statement //////////////////////////////////// From ceaf2110abd861a739d22b8035630a19a8a8c902 Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Tue, 16 May 2017 12:11:35 +0300 Subject: [PATCH 031/138] MXS-1196: Add smarter delimiter handling DELIMITER $$; --delimiter $$ --- query_classifier/test/testreader.cc | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/query_classifier/test/testreader.cc b/query_classifier/test/testreader.cc index 49fba60a7..d06911e0f 100644 --- a/query_classifier/test/testreader.cc +++ b/query_classifier/test/testreader.cc @@ -14,6 +14,7 @@ #include "testreader.hh" #include #include +#include using std::istream; using std::string; @@ -244,7 +245,21 @@ TestReader::result_t TestReader::get_statement(std::string& stmt) trim(line); if (line.length() > 0) { - m_delimiter = line; + if (line.length() >= m_delimiter.length()) + { + if (line.substr(line.length() - m_delimiter.length()) == m_delimiter) + { + m_delimiter = line.substr(0, line.length() - m_delimiter.length()); + } + else + { + m_delimiter = line; + } + } + else + { + m_delimiter = line; + } } continue; From 8c1d53192c5fd4f87d0f1922cf5578bb2a724c59 Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Tue, 16 May 2017 12:34:30 +0300 Subject: [PATCH 032/138] MXS-1196: Handle REPLACE explicitly Allowing REPLACE to implicitly decay into an identifier does not work. --- query_classifier/qc_sqlite/sqlite-src-3110100/src/parse.y | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/query_classifier/qc_sqlite/sqlite-src-3110100/src/parse.y b/query_classifier/qc_sqlite/sqlite-src-3110100/src/parse.y index 5bc0a5e1d..ac8b81703 100644 --- a/query_classifier/qc_sqlite/sqlite-src-3110100/src/parse.y +++ b/query_classifier/qc_sqlite/sqlite-src-3110100/src/parse.y @@ -598,7 +598,7 @@ columnid(A) ::= nm(X). { NO OF OFFSET OPEN QUICK - RAISE RECURSIVE /*REINDEX*/ RELEASE /*RENAME*/ REPLACE RESTRICT ROLLBACK ROLLUP ROW + RAISE RECURSIVE /*REINDEX*/ RELEASE /*RENAME*/ /*REPLACE*/ RESTRICT ROLLBACK ROLLUP ROW SAVEPOINT SELECT_OPTIONS_KW SLAVE /*START*/ STATUS TABLES TEMP TEMPTABLE /*TRIGGER*/ /*TRUNCATE*/ @@ -657,6 +657,7 @@ nm(A) ::= JOIN_KW(X). {A = X;} nm(A) ::= START(X). {A = X;} nm(A) ::= TRUNCATE(X). {A = X;} nm(A) ::= BEGIN(X). {A = X;} +nm(A) ::= REPLACE(X). {A = X;} // A typetoken is really one or more tokens that form a type name such // as can be found after the column name in a CREATE TABLE statement. @@ -1773,6 +1774,7 @@ expr(A) ::= JOIN_KW(X). {spanExpr(&A, pParse, TK_ID, &X);} expr(A) ::= START(X). {spanExpr(&A, pParse, TK_ID, &X);} expr(A) ::= TRUNCATE(X). {spanExpr(&A, pParse, TK_ID, &X);} expr(A) ::= BEGIN(X). {spanExpr(&A, pParse, TK_ID, &X);} +expr(A) ::= REPLACE(X). {spanExpr(&A, pParse, TK_ID, &X);} expr(A) ::= nm(X) DOT nm(Y). { Expr *temp1 = sqlite3PExpr(pParse, TK_ID, 0, 0, &X); Expr *temp2 = sqlite3PExpr(pParse, TK_ID, 0, 0, &Y); From 50413c51953ce7cc54bebde6e90e970903c37e81 Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Tue, 16 May 2017 12:37:22 +0300 Subject: [PATCH 033/138] MXS-1196: Add Oracle specific tests Copied from .../server/mysql-test/suite/compat/oracle/t Will be used as a baseline and edited as needed. --- .../test/oracle/binlog_stm_ps.test | 37 + .../test/oracle/binlog_stm_sp.test | 196 ++ query_classifier/test/oracle/exception.test | 457 ++++ query_classifier/test/oracle/func_case.test | 9 + query_classifier/test/oracle/func_concat.test | 116 + query_classifier/test/oracle/func_decode.test | 21 + query_classifier/test/oracle/func_misc.test | 331 +++ query_classifier/test/oracle/misc.test | 10 + query_classifier/test/oracle/ps.test | 266 ++ query_classifier/test/oracle/sequence.test | 43 + .../test/oracle/sp-anonymous.test | 244 ++ query_classifier/test/oracle/sp-code.test | 1057 ++++++++ .../test/oracle/sp-cursor-decl.test | 274 ++ .../test/oracle/sp-cursor-rowtype.test | 1424 ++++++++++ query_classifier/test/oracle/sp-cursor.test | 931 +++++++ query_classifier/test/oracle/sp-goto.test | 872 +++++++ query_classifier/test/oracle/sp-param.inc | 9 + query_classifier/test/oracle/sp-param.test | 37 + .../test/oracle/sp-row-vs-var.inc | 6 + query_classifier/test/oracle/sp-row.test | 2290 +++++++++++++++++ query_classifier/test/oracle/sp-security.test | 345 +++ query_classifier/test/oracle/sp.test | 2122 +++++++++++++++ query_classifier/test/oracle/trigger.test | 106 + query_classifier/test/oracle/truncate.test | 16 + query_classifier/test/oracle/type_blob.test | 4 + query_classifier/test/oracle/type_clob.test | 10 + query_classifier/test/oracle/type_date.test | 4 + query_classifier/test/oracle/type_number.test | 9 + query_classifier/test/oracle/type_raw.test | 10 + .../test/oracle/type_varchar.test | 9 + .../test/oracle/type_varchar2.test | 19 + query_classifier/test/oracle/variables.test | 38 + 32 files changed, 11322 insertions(+) create mode 100644 query_classifier/test/oracle/binlog_stm_ps.test create mode 100644 query_classifier/test/oracle/binlog_stm_sp.test create mode 100644 query_classifier/test/oracle/exception.test create mode 100644 query_classifier/test/oracle/func_case.test create mode 100644 query_classifier/test/oracle/func_concat.test create mode 100644 query_classifier/test/oracle/func_decode.test create mode 100644 query_classifier/test/oracle/func_misc.test create mode 100644 query_classifier/test/oracle/misc.test create mode 100644 query_classifier/test/oracle/ps.test create mode 100644 query_classifier/test/oracle/sequence.test create mode 100644 query_classifier/test/oracle/sp-anonymous.test create mode 100644 query_classifier/test/oracle/sp-code.test create mode 100644 query_classifier/test/oracle/sp-cursor-decl.test create mode 100644 query_classifier/test/oracle/sp-cursor-rowtype.test create mode 100644 query_classifier/test/oracle/sp-cursor.test create mode 100644 query_classifier/test/oracle/sp-goto.test create mode 100644 query_classifier/test/oracle/sp-param.inc create mode 100644 query_classifier/test/oracle/sp-param.test create mode 100644 query_classifier/test/oracle/sp-row-vs-var.inc create mode 100644 query_classifier/test/oracle/sp-row.test create mode 100644 query_classifier/test/oracle/sp-security.test create mode 100644 query_classifier/test/oracle/sp.test create mode 100644 query_classifier/test/oracle/trigger.test create mode 100644 query_classifier/test/oracle/truncate.test create mode 100644 query_classifier/test/oracle/type_blob.test create mode 100644 query_classifier/test/oracle/type_clob.test create mode 100644 query_classifier/test/oracle/type_date.test create mode 100644 query_classifier/test/oracle/type_number.test create mode 100644 query_classifier/test/oracle/type_raw.test create mode 100644 query_classifier/test/oracle/type_varchar.test create mode 100644 query_classifier/test/oracle/type_varchar2.test create mode 100644 query_classifier/test/oracle/variables.test diff --git a/query_classifier/test/oracle/binlog_stm_ps.test b/query_classifier/test/oracle/binlog_stm_ps.test new file mode 100644 index 000000000..996ef5744 --- /dev/null +++ b/query_classifier/test/oracle/binlog_stm_ps.test @@ -0,0 +1,37 @@ +--source include/not_embedded.inc +--source include/have_binlog_format_statement.inc + +--disable_query_log +reset master; # get rid of previous tests binlog +--enable_query_log + +SET sql_mode=ORACLE; + +--echo # +--echo # MDEV-10801 sql_mode: dynamic SQL placeholders +--echo # + +CREATE TABLE t1 (a INT, b INT); +SET @a=10, @b=20; +PREPARE stmt FROM 'INSERT INTO t1 VALUES (?,?)'; +EXECUTE stmt USING @a, @b; +PREPARE stmt FROM 'INSERT INTO t1 VALUES (:a,:b)'; +EXECUTE stmt USING @a, @b; +PREPARE stmt FROM 'INSERT INTO t1 VALUES (:aaa,:bbb)'; +EXECUTE stmt USING @a, @b; +PREPARE stmt FROM 'INSERT INTO t1 VALUES (:"a",:"b")'; +EXECUTE stmt USING @a, @b; +PREPARE stmt FROM 'INSERT INTO t1 VALUES (:"aaa",:"bbb")'; +EXECUTE stmt USING @a, @b; +PREPARE stmt FROM 'INSERT INTO t1 VALUES (:1,:2)'; +EXECUTE stmt USING @a, @b; +PREPARE stmt FROM 'INSERT INTO t1 VALUES (:222,:111)'; +EXECUTE stmt USING @a, @b; +PREPARE stmt FROM 'INSERT INTO t1 VALUES (:0,:65535)'; +EXECUTE stmt USING @a, @b; +PREPARE stmt FROM 'INSERT INTO t1 VALUES (:65535,:0)'; +EXECUTE stmt USING @a, @b; +SELECT * FROM t1; +--let $binlog_file = LAST +source include/show_binlog_events.inc; +DROP TABLE t1; diff --git a/query_classifier/test/oracle/binlog_stm_sp.test b/query_classifier/test/oracle/binlog_stm_sp.test new file mode 100644 index 000000000..065c43eb2 --- /dev/null +++ b/query_classifier/test/oracle/binlog_stm_sp.test @@ -0,0 +1,196 @@ +--source include/not_embedded.inc +--source include/have_binlog_format_statement.inc + +--disable_query_log +call mtr.add_suppression("Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT"); +reset master; # get rid of previous tests binlog +--enable_query_log + + +SET sql_mode=ORACLE; + +--echo # +--echo # MDEV-10914 ROW data type for stored routine variables +--echo # + +CREATE TABLE t1 (a INT, b INT); +DELIMITER $$; +CREATE PROCEDURE p1 +AS + rec ROW(a INT,b INT); +BEGIN + rec.a:=100; + rec.b:=200; + INSERT INTO t1 VALUES (rec.a,rec.b); + INSERT INTO t1 VALUES (10, rec=ROW(100,200)); + INSERT INTO t1 VALUES (10, ROW(100,200)=rec); + INSERT INTO t1 SELECT 10, 20 FROM DUAL WHERE rec=ROW(100,200); + INSERT INTO t1 SELECT 10, 21 FROM DUAL WHERE ROW(100,200)=rec; + rec.a:=NULL; + INSERT INTO t1 VALUES (11, rec=ROW(100,200)); + INSERT INTO t1 VALUES (11, rec=ROW(100,201)); + INSERT INTO t1 VALUES (11, ROW(100,200)=rec); + INSERT INTO t1 VALUES (11, ROW(100,201)=rec); + INSERT INTO t1 SELECT 11, 20 FROM DUAL WHERE rec=ROW(100,200); + INSERT INTO t1 SELECT 11, 21 FROM DUAL WHERE ROW(100,200)=rec; + rec.b:=NULL; + INSERT INTO t1 VALUES (12, rec=ROW(100,200)); + INSERT INTO t1 VALUES (12, ROW(100,200)=rec); + INSERT INTO t1 SELECT 12, 20 FROM DUAL WHERE rec=ROW(100,200); + INSERT INTO t1 SELECT 12, 21 FROM DUAL WHERE ROW(100,200)=rec; +END; +$$ +DELIMITER ;$$ +CALL p1(); +SELECT * FROM t1; +DROP TABLE t1; +DROP PROCEDURE p1; +--let $binlog_file = LAST +source include/show_binlog_events.inc; + + +--echo # +--echo # Testing ROW fields in LIMIT +--echo # + +FLUSH LOGS; +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (10),(10); +CREATE TABLE t2 (a INT); +DELIMITER $$; +CREATE PROCEDURE p1() +AS + a INT:= 1; + rec ROW(a INT); +BEGIN + rec.a:= 1; + INSERT INTO t2 SELECT 1 FROM t1 LIMIT a; + INSERT INTO t2 SELECT 2 FROM t1 LIMIT rec.a; +END; +$$ +DELIMITER ;$$ +CALL p1(); +DROP TABLE t1,t2; +DROP PROCEDURE p1; +--let $binlog_file = LAST +source include/show_binlog_events.inc; + + +--echo # +--echo # End of MDEV-10914 ROW data type for stored routine variables +--echo # + + +--echo # +--echo # MDEV-12133 sql_mode=ORACLE: table%ROWTYPE in variable declarations +--echo # + +CREATE TABLE t1 (a INT, b INT); +DELIMITER $$; +CREATE PROCEDURE p1 +AS + rec t1%ROWTYPE; +BEGIN + rec.a:=100; + rec.b:=200; + SELECT rec=ROW(100,200) AS true1, ROW(100,200)=rec AS true2; + INSERT INTO t1 VALUES (rec.a,rec.b); + INSERT INTO t1 VALUES (10, rec=ROW(100,200)); + INSERT INTO t1 VALUES (10, ROW(100,200)=rec); + INSERT INTO t1 SELECT 10, 20 FROM DUAL WHERE rec=ROW(100,200); + INSERT INTO t1 SELECT 10, 21 FROM DUAL WHERE ROW(100,200)=rec; + rec.a:=NULL; + INSERT INTO t1 VALUES (11, rec=ROW(100,200)); + INSERT INTO t1 VALUES (11, rec=ROW(100,201)); + INSERT INTO t1 VALUES (11, ROW(100,200)=rec); + INSERT INTO t1 VALUES (11, ROW(100,201)=rec); + INSERT INTO t1 SELECT 11, 20 FROM DUAL WHERE rec=ROW(100,200); + INSERT INTO t1 SELECT 11, 21 FROM DUAL WHERE ROW(100,200)=rec; + rec.b:=NULL; + INSERT INTO t1 VALUES (12, rec=ROW(100,200)); + INSERT INTO t1 VALUES (12, ROW(100,200)=rec); + INSERT INTO t1 SELECT 12, 20 FROM DUAL WHERE rec=ROW(100,200); + INSERT INTO t1 SELECT 12, 21 FROM DUAL WHERE ROW(100,200)=rec; +END; +$$ +DELIMITER ;$$ +CALL p1(); +SELECT * FROM t1; +DROP TABLE t1; +DROP PROCEDURE p1; +--let $binlog_file = LAST +source include/show_binlog_events.inc; + + +--echo # +--echo # MDEV-12291 Allow ROW variables as SELECT INTO targets +--echo # + +FLUSH LOGS; +CREATE TABLE t1 (a INT, b VARCHAR(32)); +INSERT INTO t1 VALUES (10, 'b10'); +CREATE TABLE t2 LIKE t1; +DELIMITER $$; +CREATE PROCEDURE p1 +AS + rec1 ROW(a INT, b VARCHAR(32)); +BEGIN + SELECT * INTO rec1 FROM t1; + INSERT INTO t2 VALUES (rec1.a, rec1.b); +END; +$$ +DELIMITER ;$$ +CALL p1(); +SELECT * FROM t1; +DROP TABLE t1; +DROP TABLE t2; +DROP PROCEDURE p1; +--let $binlog_file = LAST +source include/show_binlog_events.inc; + + +FLUSH LOGS; +CREATE TABLE t1 (a INT, b VARCHAR(32)); +INSERT INTO t1 VALUES (10, 'b10'); +CREATE TABLE t2 LIKE t1; +DELIMITER $$; +CREATE PROCEDURE p1 +AS + rec1 t1%ROWTYPE; +BEGIN + SELECT * INTO rec1 FROM t1; + INSERT INTO t2 VALUES (rec1.a, rec1.b); +END; +$$ +DELIMITER ;$$ +CALL p1(); +SELECT * FROM t1; +DROP TABLE t1; +DROP TABLE t2; +DROP PROCEDURE p1; +--let $binlog_file = LAST +source include/show_binlog_events.inc; + + +FLUSH LOGS; +CREATE TABLE t1 (a INT, b VARCHAR(32)); +INSERT INTO t1 VALUES (10, 'b10'); +CREATE TABLE t2 LIKE t1; +DELIMITER $$; +CREATE PROCEDURE p1 +AS + CURSOR cur1 IS SELECT * FROM t1; + rec1 cur1%ROWTYPE; +BEGIN + SELECT * INTO rec1 FROM t1; + INSERT INTO t2 VALUES (rec1.a, rec1.b); +END; +$$ +DELIMITER ;$$ +CALL p1(); +SELECT * FROM t1; +DROP TABLE t1; +DROP TABLE t2; +DROP PROCEDURE p1; +--let $binlog_file = LAST +source include/show_binlog_events.inc; diff --git a/query_classifier/test/oracle/exception.test b/query_classifier/test/oracle/exception.test new file mode 100644 index 000000000..6448a6ef6 --- /dev/null +++ b/query_classifier/test/oracle/exception.test @@ -0,0 +1,457 @@ +SET sql_mode=ORACLE; + +--echo # +--echo # sql_mode=ORACLE: Predefined exceptions: TOO_MANY_ROWS, NO_DATA_FOUND, DUP_VAL_ON_INDEX +--echo # + +--echo # +--echo # Testing NO_DATA_FOUND and TOO_MANY_ROWS +--echo # + +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (10),(20); +DELIMITER $$; +CREATE PROCEDURE p1(lim INT, res OUT VARCHAR) +AS + a INT; +BEGIN + SELECT a INTO a FROM t1 LIMIT lim; +EXCEPTION + WHEN TOO_MANY_ROWS THEN res:='--- too_many_rows cought ---'; + WHEN NO_DATA_FOUND THEN res:='--- no_data_found cought ---'; +END; +$$ +DELIMITER ;$$ +SET @res=''; +CALL p1(0, @res); +SELECT @res; +CALL p1(2, @res); +SELECT @res; +DROP PROCEDURE p1; +DROP TABLE t1; + +--echo # +--echo # Testing DUP_VAL_ON_INDEX +--echo # + +CREATE TABLE t1 (a INT PRIMARY KEY); +DELIMITER $$; +CREATE PROCEDURE p1(res OUT VARCHAR) +AS +BEGIN + INSERT INTO t1 VALUES (10); + INSERT INTO t1 VALUES (10); +EXCEPTION + WHEN DUP_VAL_ON_INDEX THEN res:='--- dup_val_on_index cought ---'; +END; +$$ +DELIMITER ;$$ +SET @res=''; +CALL p1(@res); +SELECT @res; +SELECT * FROM t1; +DROP PROCEDURE p1; +DROP TABLE t1; + + +--echo # +--echo # MDEV-10840 sql_mode=ORACLE: RAISE statement for predefined exceptions +--echo # + +--echo # +--echo # RAISE outside of an SP context +--echo # + +--error ER_SP_COND_MISMATCH +RAISE NO_DATA_FOUND; +--error ER_SP_COND_MISMATCH +RAISE INVALID_CURSOR; +--error ER_SP_COND_MISMATCH +RAISE DUP_VAL_ON_INDEX; +--error ER_SP_COND_MISMATCH +RAISE TOO_MANY_ROWS; + +--error ER_RESIGNAL_WITHOUT_ACTIVE_HANDLER +RAISE; + + +--echo # +--echo # RAISE for an undefinite exception +--echo # + +DELIMITER $$; +--error ER_SP_COND_MISMATCH +CREATE PROCEDURE p1 +AS +BEGIN + RAISE xxx; +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # RAISE for predefined exceptions +--echo # + +DELIMITER $$; +CREATE PROCEDURE p1 +AS +BEGIN + RAISE no_data_found; +END; +$$ +DELIMITER ;$$ +CALL p1(); +DROP PROCEDURE p1; + +DELIMITER $$; +CREATE PROCEDURE p1 +AS +BEGIN + RAISE invalid_cursor; +END; +$$ +DELIMITER ;$$ +--error ER_SP_CURSOR_NOT_OPEN +CALL p1(); +DROP PROCEDURE p1; + +DELIMITER $$; +CREATE PROCEDURE p1 +AS +BEGIN + RAISE dup_val_on_index; +END; +$$ +DELIMITER ;$$ +--error ER_DUP_ENTRY +CALL p1(); +DROP PROCEDURE p1; + +DELIMITER $$; +CREATE PROCEDURE p1 +AS +BEGIN + raise too_many_rows; +END; +$$ +DELIMITER ;$$ +--error ER_TOO_MANY_ROWS +CALL p1(); +DROP PROCEDURE p1; + + +--echo # +--echo # RAISE with no exception name (resignal) +--echo # + +DELIMITER $$; +CREATE PROCEDURE p1() +AS +BEGIN + RAISE; +END; +$$ +DELIMITER ;$$ +--error ER_RESIGNAL_WITHOUT_ACTIVE_HANDLER +CALL p1(); +DROP PROCEDURE p1; + + +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (10),(20); +DELIMITER $$; +CREATE PROCEDURE p1(lim INT) +AS + a INT; +BEGIN + SELECT a INTO a FROM t1 LIMIT lim; +EXCEPTION + WHEN TOO_MANY_ROWS THEN RAISE; + WHEN NO_DATA_FOUND THEN RAISE; +END; +$$ +DELIMITER ;$$ +CALL p1(0); +--error ER_TOO_MANY_ROWS +CALL p1(2); +DROP PROCEDURE p1; +DROP TABLE t1; + + +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (10),(20); +DELIMITER $$; +CREATE PROCEDURE p1(lim INT) +AS + a INT; +BEGIN + SELECT a INTO a FROM t1 LIMIT lim; +EXCEPTION + WHEN OTHERS THEN RAISE; +END; +$$ +DELIMITER ;$$ +CALL p1(0); +--error ER_TOO_MANY_ROWS +CALL p1(2); +DROP PROCEDURE p1; +DROP TABLE t1; + + +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (10),(20); +DELIMITER $$; +CREATE PROCEDURE p1() +AS + a INT; + CURSOR c IS SELECT a FROM t1; +BEGIN + FETCH c INTO a; +EXCEPTION + WHEN INVALID_CURSOR THEN RAISE; +END; +$$ +DELIMITER ;$$ +--error ER_SP_CURSOR_NOT_OPEN +CALL p1(); +DROP PROCEDURE p1; +DROP TABLE t1; + + +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (10),(20); +DELIMITER $$; +CREATE PROCEDURE p1() +AS + a INT; + CURSOR c IS SELECT a FROM t1; +BEGIN + FETCH c INTO a; +EXCEPTION + WHEN OTHERS THEN RAISE; +END; +$$ +DELIMITER ;$$ +--error ER_SP_CURSOR_NOT_OPEN +CALL p1(); +DROP PROCEDURE p1; +DROP TABLE t1; + +--echo # +--echo # Testing that warning-alike errors are caught by OTHERS +--echo # + +CREATE TABLE t1 (a INT); +DELIMITER $$; +CREATE FUNCTION f1 RETURN VARCHAR +AS + a INT:=10; +BEGIN + SELECT a INTO a FROM t1; + RETURN 'OK'; +EXCEPTION + WHEN OTHERS THEN RETURN 'Exception'; +END; +$$ +DELIMITER ;$$ +SELECT f1() FROM DUAL; +DROP FUNCTION f1; +DROP TABLE t1; + + +--echo # +--echo # End of MDEV-10840 sql_mode=ORACLE: RAISE statement for predefined exceptions +--echo # + + +--echo # +--echo # MDEV-10587 sql_mode=ORACLE: User defined exceptions +--echo # + +--echo # +--echo # Checking that duplicate WHEN clause is not allowed +--echo # + +DELIMITER $$; +--error ER_SP_DUP_HANDLER +CREATE FUNCTION f1() RETURN VARCHAR +AS + e EXCEPTION; +BEGIN + RETURN 'Got no exceptions'; +EXCEPTION + WHEN e THEN RETURN 'Got exception e'; + WHEN e THEN RETURN 'Got exception e'; +END; +$$ +DELIMITER ;$$ + + +--echo # +--echo # Checking that raised user exceptions are further caught by name +--echo # + +DELIMITER $$; +CREATE FUNCTION f1(c VARCHAR) RETURN VARCHAR +AS + e EXCEPTION; + f EXCEPTION; +BEGIN + IF c = 'e' THEN RAISE e; END IF; + IF c = 'f' THEN RAISE f; END IF; + RETURN 'Got no exceptions'; +EXCEPTION + WHEN e THEN RETURN 'Got exception e'; +END; +$$ +DELIMITER ;$$ +SELECT f1(''); +SELECT f1('e'); +--error ER_SIGNAL_EXCEPTION +SELECT f1('f'); +DROP FUNCTION f1; + + +--echo # +--echo # Checking that raised user exceptions are further caught by OTHERS +--echo # + +DELIMITER $$; +CREATE FUNCTION f1(c VARCHAR) RETURN VARCHAR +AS + e EXCEPTION; + f EXCEPTION; +BEGIN + IF c = 'e' THEN RAISE e; END IF; + IF c = 'f' THEN RAISE f; END IF; + RETURN 'Got no exceptions'; +EXCEPTION + WHEN OTHERS THEN RETURN 'Got some exception'; +END; +$$ +DELIMITER ;$$ +SELECT f1(''); +SELECT f1('e'); +SELECT f1('f'); +DROP FUNCTION f1; + + +--echo # +--echo # Checking that 'WHEN e .. WHEN f' does not produce ER_SP_DUP_HANDLER +--echo # + +DELIMITER $$; +CREATE FUNCTION f1(c VARCHAR) RETURN VARCHAR +AS + e EXCEPTION; + f EXCEPTION; + a VARCHAR(64):=''; +BEGIN + BEGIN + IF c = 'e' THEN RAISE e; END IF; + IF c = 'f' THEN RAISE f; END IF; + EXCEPTION + WHEN e THEN BEGIN a:='Got EXCEPTION1/e; '; RAISE e; END; + WHEN f THEN BEGIN a:='Got EXCEPTION1/f; '; RAISE f; END; + END; + RETURN 'Got no exceptions'; +EXCEPTION + WHEN OTHERS THEN RETURN a || 'Got EXCEPTION2/OTHERS;'; +END; +$$ +DELIMITER ;$$ +SELECT f1(''); +SELECT f1('e'); +SELECT f1('f'); +DROP FUNCTION f1; + + +--echo # +--echo # Checking that resignaled user exceptions are further caught by name +--echo # +DELIMITER $$; +CREATE FUNCTION f1(c VARCHAR) RETURN VARCHAR +AS + e EXCEPTION; + f EXCEPTION; + a VARCHAR(64):=''; +BEGIN + BEGIN + IF c = 'e' THEN RAISE e; END IF; + IF c = 'f' THEN RAISE f; END IF; + EXCEPTION + WHEN e THEN BEGIN a:='Got EXCEPTION1/e; '; RAISE; END; + WHEN f THEN BEGIN a:='Got EXCEPTION1/f; '; RAISE; END; + END; + RETURN 'Got no exceptions'; +EXCEPTION + WHEN e THEN RETURN a || 'Got EXCEPTION2/e;'; +END; +$$ +DELIMITER ;$$ +SELECT f1(''); +SELECT f1('e'); +--error ER_SIGNAL_EXCEPTION +SELECT f1('f'); +DROP FUNCTION f1; + + +--echo # +--echo # Checking that resignaled user exceptions are further caught by OTHERS +--echo # + +DELIMITER $$; +CREATE FUNCTION f1(c VARCHAR) RETURN VARCHAR +AS + e EXCEPTION; + f EXCEPTION; + a VARCHAR(64):=''; +BEGIN + BEGIN + IF c = 'e' THEN RAISE e; END IF; + IF c = 'f' THEN RAISE f; END IF; + EXCEPTION + WHEN e THEN BEGIN a:='Got EXCEPTION1/e; '; RAISE; END; + WHEN f THEN BEGIN a:='Got EXCEPTION1/f; '; RAISE; END; + END; + RETURN 'Got no exceptions'; +EXCEPTION + WHEN OTHERS THEN RETURN a || 'Got EXCEPTION2/OTHERS;'; +END; +$$ +DELIMITER ;$$ +SELECT f1(''); +SELECT f1('e'); +SELECT f1('f'); +DROP FUNCTION f1; + + +--echo # +--echo # End of MDEV-10587 sql_mode=ORACLE: User defined exceptions +--echo # + +--echo # +--echo # MDEV-12088 sql_mode=ORACLE: Do not require BEGIN..END in multi-statement exception handlers in THEN clause +--echo # +CREATE TABLE t1 (a INT PRIMARY KEY); +INSERT INTO t1 VALUES (10),(20),(30); +DELIMITER $$; +CREATE PROCEDURE p1(a INT) AS +BEGIN + INSERT INTO t1 (a) VALUES (a); +EXCEPTION + WHEN DUP_VAL_ON_INDEX THEN + a:= a+1; + INSERT INTO t1 VALUES (a); + WHEN OTHERS THEN + NULL; + NULL; +END; +$$ +DELIMITER ;$$ +CALL p1(30); +SELECT * FROM t1; +DROP PROCEDURE p1; +DROP TABLE t1; diff --git a/query_classifier/test/oracle/func_case.test b/query_classifier/test/oracle/func_case.test new file mode 100644 index 000000000..d5e0d6509 --- /dev/null +++ b/query_classifier/test/oracle/func_case.test @@ -0,0 +1,9 @@ +# +# Testing CASE and its abbreviations +# + +SET sql_mode=ORACLE; + +SELECT NVL(NULL, 'a'), NVL('a', 'b'); + +SELECT NVL2(NULL, 'a', 'b'), NVL2('a', 'b', 'c'); diff --git a/query_classifier/test/oracle/func_concat.test b/query_classifier/test/oracle/func_concat.test new file mode 100644 index 000000000..e1d8a5c47 --- /dev/null +++ b/query_classifier/test/oracle/func_concat.test @@ -0,0 +1,116 @@ +# +# Testing CONCAT with null values +# + +SET sql_mode=ORACLE; + +EXPLAIN EXTENDED SELECT 'a'||'b'||'c'; +EXPLAIN EXTENDED SELECT CONCAT('a'||'b'||'c'); + +SELECT '' || ''; +SELECT '' || 'b'; +SELECT '' || NULL; +SELECT 'a' || ''; +SELECT 'a' || 'b'; +SELECT 'a' || NULL; +SELECT NULL || ''; +SELECT NULL || 'b'; +SELECT NULL || NULL; + +SELECT '' || '' || ''; +SELECT '' || '' || 'c'; +SELECT '' || '' || NULL; +SELECT '' || 'b' || ''; +SELECT '' || 'b' || 'c'; +SELECT '' || 'b' || NULL; +SELECT '' || NULL || ''; +SELECT '' || NULL || 'c'; +SELECT '' || NULL || NULL; + +SELECT 'a' || '' || ''; +SELECT 'a' || '' || 'c'; +SELECT 'a' || '' || NULL; +SELECT 'a' || 'b' || ''; +SELECT 'a' || 'b' || 'c'; +SELECT 'a' || 'b' || NULL; +SELECT 'a' || NULL || ''; +SELECT 'a' || NULL || 'c'; +SELECT 'a' || NULL || NULL; + +SELECT NULL || '' || ''; +SELECT NULL || '' || 'c'; +SELECT NULL || '' || NULL; +SELECT NULL || 'b' || ''; +SELECT NULL || 'b' || 'c'; +SELECT NULL || 'b' || NULL; +SELECT NULL || NULL || ''; +SELECT NULL || NULL || 'c'; +SELECT NULL || NULL || NULL; + +CREATE TABLE t1 (a VARCHAR(10), b VARCHAR(10), c VARCHAR(10)); + +INSERT INTO t1 VALUES ('', '', ''); +INSERT INTO t1 VALUES ('', '', 'c'); +INSERT INTO t1 VALUES ('', '', NULL); +INSERT INTO t1 VALUES ('', 'b', ''); +INSERT INTO t1 VALUES ('', 'b', 'c'); +INSERT INTO t1 VALUES ('', 'b', NULL); +INSERT INTO t1 VALUES ('', NULL, ''); +INSERT INTO t1 VALUES ('', NULL, 'c'); +INSERT INTO t1 VALUES ('', NULL, NULL); + +INSERT INTO t1 VALUES ('a', '', ''); +INSERT INTO t1 VALUES ('a', '', 'c'); +INSERT INTO t1 VALUES ('a', '', NULL); +INSERT INTO t1 VALUES ('a', 'b', ''); +INSERT INTO t1 VALUES ('a', 'b', 'c'); +INSERT INTO t1 VALUES ('a', 'b', NULL); +INSERT INTO t1 VALUES ('a', NULL, ''); +INSERT INTO t1 VALUES ('a', NULL, 'c'); +INSERT INTO t1 VALUES ('a', NULL, NULL); + +INSERT INTO t1 VALUES (NULL, '', ''); +INSERT INTO t1 VALUES (NULL, '', 'c'); +INSERT INTO t1 VALUES (NULL, '', NULL); +INSERT INTO t1 VALUES (NULL, 'b', ''); +INSERT INTO t1 VALUES (NULL, 'b', 'c'); +INSERT INTO t1 VALUES (NULL, 'b', NULL); +INSERT INTO t1 VALUES (NULL, NULL, ''); +INSERT INTO t1 VALUES (NULL, NULL, 'c'); +INSERT INTO t1 VALUES (NULL, NULL, NULL); + +SELECT LENGTH(a||b||c), a||b||c FROM t1 ORDER BY a,b,c; +SELECT LENGTH(CONCAT(a||b||c)), CONCAT(a||b||c) FROM t1 ORDER BY a,b,c; + +DROP TABLE t1; + +--echo # +--echo # MDEV-12478 CONCAT function inside view casts values incorrectly with Oracle sql_mode +--echo # + +SET sql_mode=ORACLE; +CREATE VIEW v1 AS SELECT 'foo'||NULL||'bar' AS test; +SHOW CREATE VIEW v1; +SELECT * FROM v1; +SET sql_mode=DEFAULT; +SHOW CREATE VIEW v1; +SELECT * FROM v1; +DROP VIEW v1; + +SET sql_mode=DEFAULT; +CREATE VIEW v1 AS SELECT CONCAT('foo',NULL,'bar') AS test; +SHOW CREATE VIEW v1; +SELECT * FROM v1; +SET sql_mode=ORACLE; +SHOW CREATE VIEW v1; +SELECT * FROM v1; +DROP VIEW v1; + +SET sql_mode=DEFAULT; +CREATE VIEW v1 AS SELECT '0'||'1' AS test; +SHOW CREATE VIEW v1; +SELECT * FROM v1; +SET sql_mode=ORACLE; +SHOW CREATE VIEW v1; +SELECT * FROM v1; +DROP VIEW v1; diff --git a/query_classifier/test/oracle/func_decode.test b/query_classifier/test/oracle/func_decode.test new file mode 100644 index 000000000..ae05cb2c3 --- /dev/null +++ b/query_classifier/test/oracle/func_decode.test @@ -0,0 +1,21 @@ +SET sql_mode=ORACLE; + +--error ER_PARSE_ERROR +SELECT DECODE(10); +--error ER_PARSE_ERROR +SELECT DECODE(10,10); + +SELECT DECODE(10,10,'x10'); +SELECT DECODE(11,10,'x10'); + +SELECT DECODE(10,10,'x10','def'); +SELECT DECODE(11,10,'x10','def'); + +SELECT DECODE(10,10,'x10',11,'x11','def'); +SELECT DECODE(11,10,'x10',11,'x11','def'); +SELECT DECODE(12,10,'x10',11,'x11','def'); + +EXPLAIN EXTENDED SELECT DECODE(12,10,'x10',11,'x11','def'); + +CREATE TABLE decode (decode int); +DROP TABLE decode; diff --git a/query_classifier/test/oracle/func_misc.test b/query_classifier/test/oracle/func_misc.test new file mode 100644 index 000000000..a356d7040 --- /dev/null +++ b/query_classifier/test/oracle/func_misc.test @@ -0,0 +1,331 @@ +SET sql_mode=ORACLE; + +--echo # +--echo # MDEV-10578 sql_mode=ORACLE: SP control functions SQLCODE, SQLERRM +--echo # + +--echo # +--echo # Using SQLCODE and SQLERRM outside of an SP +--echo # + +--error ER_BAD_FIELD_ERROR +SELECT SQLCODE; + +--error ER_BAD_FIELD_ERROR +SELECT SQLERRM; + +CREATE TABLE t1 (SQLCODE INT, SQLERRM VARCHAR(10)); +INSERT INTO t1 VALUES (10, 'test'); +SELECT SQLCODE, SQLERRM FROM t1; +DROP TABLE t1; + +--echo # +--echo # Normal SQLCODE and SQLERRM usage +--echo # + +DELIMITER $$; +CREATE PROCEDURE p1(stmt VARCHAR) +AS +BEGIN + EXECUTE IMMEDIATE stmt; + SELECT 'Error1: ' || SQLCODE || ' ' || SQLERRM; +EXCEPTION + WHEN OTHERS THEN + SELECT 'Error2: ' || SQLCODE || ' ' || SQLERRM; +END; +$$ +DELIMITER ;$$ +CALL p1('SELECT 1'); +CALL p1('xxx'); +CALL p1('SELECT 1'); +DROP PROCEDURE p1; + +--echo # +--echo # SQLCODE and SQLERRM hidden by local variables +--echo # + +DELIMITER $$; +CREATE PROCEDURE p1() +AS + sqlcode INT:= 10; + sqlerrm VARCHAR(64) := 'test'; +BEGIN + SELECT 'Error: ' || SQLCODE || ' ' || SQLERRM; +END; +$$ +DELIMITER ;$$ +CALL p1; +DROP PROCEDURE p1; + +DELIMITER $$; +CREATE PROCEDURE p1() +AS + sqlcode INT; + sqlerrm VARCHAR(64); +BEGIN + SQLCODE:= 10; + sqlerrm:= 'test'; + SELECT 'Error: ' || SQLCODE || ' ' || SQLERRM; +END; +$$ +DELIMITER ;$$ +CALL p1; +DROP PROCEDURE p1; + + +--echo # +--echo # SQLCODE and SQLERRM hidden by parameters +--echo # + +DELIMITER $$; +CREATE PROCEDURE p1(sqlcode INT, sqlerrm VARCHAR) +AS +BEGIN + SELECT 'Error: ' || SQLCODE || ' ' || SQLERRM; +END; +$$ +DELIMITER ;$$ +CALL p1(10, 'test'); +DROP PROCEDURE p1; + + +--echo # +--echo # SQLCODE and SQLERRM in CREATE..SELECT +--echo # + +DELIMITER $$; +CREATE PROCEDURE p1 +AS +BEGIN + CREATE TABLE t1 AS SELECT SQLCODE, SQLERRM; +END; +$$ +DELIMITER ;$$ +CALL p1; +SHOW CREATE TABLE t1; +DROP TABLE t1; +DROP PROCEDURE p1; + + +--echo # +--echo # SQLCODE and SQLERRM in EXPLAIN EXTENDED SELECT +--echo # + +DELIMITER $$; +CREATE PROCEDURE p1 +AS +BEGIN + EXPLAIN EXTENDED SELECT SQLCode, SQLErrm; +END; +$$ +DELIMITER ;$$ +CALL p1; +DROP PROCEDURE p1; + + + +--echo # +--echo # Warning-alike errors in stored functions +--echo # + +CREATE TABLE t1 (a INT); +DELIMITER $$; +CREATE FUNCTION f1 RETURN VARCHAR +AS + a INT; +BEGIN + SELECT a INTO a FROM t1; + RETURN 'No exception ' || SQLCODE || ' ' || SQLERRM; +EXCEPTION + WHEN NO_DATA_FOUND THEN + RETURN 'Exception ' || SQLCODE || ' ' || SQLERRM; +END; +$$ +DELIMITER ;$$ +SELECT f1() FROM DUAL; +DROP FUNCTION f1; +DROP TABLE t1; + + +CREATE TABLE t1 (a INT); +DELIMITER $$; +CREATE FUNCTION f1 RETURN VARCHAR +AS + a INT; +BEGIN + SELECT a INTO a FROM t1; + RETURN 'No exception ' || SQLCODE || ' ' || SQLERRM; +EXCEPTION + WHEN OTHERS THEN + RETURN 'Exception ' || SQLCODE || ' ' || SQLERRM; +END; +$$ +DELIMITER ;$$ +SELECT f1() FROM DUAL; +DROP FUNCTION f1; +DROP TABLE t1; + + +--echo # +--echo # Warning-alike errors in stored procedures +--echo # + +CREATE TABLE t1 (a INT); +DELIMITER $$; +CREATE PROCEDURE p1(res OUT VARCHAR) +AS + a INT; +BEGIN + SELECT a INTO a FROM t1; + res:= 'No exception ' || SQLCODE || ' ' || SQLERRM; +EXCEPTION + WHEN NO_DATA_FOUND THEN + res:= 'Exception ' || SQLCODE || ' ' || SQLERRM; +END; +$$ +DELIMITER ;$$ +CALL p1(@a); +SELECT @a; +DROP PROCEDURE p1; +DROP TABLE t1; + + +CREATE TABLE t1 (a INT); +DELIMITER $$; +CREATE PROCEDURE p1(res OUT VARCHAR) +AS + a INT; +BEGIN + SELECT a INTO a FROM t1; + res:= 'No exception ' || SQLCODE || ' ' || SQLERRM; +EXCEPTION + WHEN OTHERS THEN + res:= 'Exception ' || SQLCODE || ' ' || SQLERRM; +END; +$$ +DELIMITER ;$$ +CALL p1(@a); +SELECT @a; +DROP PROCEDURE p1; +DROP TABLE t1; + + +--echo # +--echo # SQLCODE and SQLERRM are cleared on RETURN +--echo # + +CREATE TABLE t1 (a INT); +DELIMITER $$; +CREATE FUNCTION f1 RETURN VARCHAR +AS + a INT:=10; +BEGIN + SELECT a INTO a FROM t1; + RETURN 'Value=' || a; +EXCEPTION + WHEN NO_DATA_FOUND THEN RETURN 'Exception|' || SQLCODE || ' ' || SQLERRM; +END; +$$ +CREATE FUNCTION f2 RETURN VARCHAR +AS + a VARCHAR(128); +BEGIN + RETURN f1() || '|' || SQLCODE || ' ' || SQLERRM; +END; +$$ +DELIMITER ;$$ +SELECT f1() FROM DUAL; +SELECT f2() FROM DUAL; +DROP TABLE t1; +DROP FUNCTION f2; +DROP FUNCTION f1; + + +CREATE TABLE t1 (a INT); +DELIMITER $$; +CREATE FUNCTION f1 RETURN VARCHAR +AS + a INT:=10; +BEGIN + SELECT a INTO a FROM t1; + RETURN 'Value=' || a; +EXCEPTION + WHEN OTHERS THEN RETURN 'Exception|' || SQLCODE || ' ' || SQLERRM; +END; +$$ +CREATE FUNCTION f2 RETURN VARCHAR +AS + a VARCHAR(128); +BEGIN + RETURN f1() || '|' || SQLCODE || ' ' || SQLERRM; +END; +$$ +DELIMITER ;$$ +SELECT f1() FROM DUAL; +SELECT f2() FROM DUAL; +DROP TABLE t1; +DROP FUNCTION f2; +DROP FUNCTION f1; + + +--echo # +--echo # SQLCODE and SQLERRM are cleared on a return from a PROCEDURE +--echo # + +CREATE TABLE t1 (a INT); +DELIMITER $$; +CREATE PROCEDURE p1(res OUT VARCHAR) +AS + a INT:=10; +BEGIN + SELECT a INTO a FROM t1; + res:='Value=' || a; +EXCEPTION + WHEN NO_DATA_FOUND THEN res:='Exception|' || SQLCODE || ' ' || SQLERRM; +END; +$$ +CREATE FUNCTION f2 RETURN VARCHAR +AS + res VARCHAR(128); +BEGIN + CALL p1(res); + RETURN res || '|' || SQLCODE || ' ' || SQLERRM; +END; +$$ +DELIMITER ;$$ +SELECT f2() FROM DUAL; +DROP FUNCTION f2; +DROP PROCEDURE p1; +DROP TABLE t1; + + +CREATE TABLE t1 (a INT); +DELIMITER $$; +CREATE PROCEDURE p1(res OUT VARCHAR) +AS + a INT:=10; +BEGIN + SELECT a INTO a FROM t1; + res:='Value=' || a; +EXCEPTION + WHEN OTHERS THEN res:='Exception|' || SQLCODE || ' ' || SQLERRM; +END; +$$ +CREATE FUNCTION f2 RETURN VARCHAR +AS + res VARCHAR(128); +BEGIN + CALL p1(res); + RETURN res || '|' || SQLCODE || ' ' || SQLERRM; +END; +$$ +DELIMITER ;$$ +SELECT f2() FROM DUAL; +DROP FUNCTION f2; +DROP PROCEDURE p1; +DROP TABLE t1; + + +--echo # +--echo # End of MDEV-10578 sql_mode=ORACLE: SP control functions SQLCODE, SQLERRM +--echo # diff --git a/query_classifier/test/oracle/misc.test b/query_classifier/test/oracle/misc.test new file mode 100644 index 000000000..d939b20f8 --- /dev/null +++ b/query_classifier/test/oracle/misc.test @@ -0,0 +1,10 @@ +SET sql_mode=ORACLE; + +--echo # +--echo # MDEV-12086 sql_mode=ORACLE: allow SELECT UNIQUE as a synonym for SELECT DISTINCT +--echo # + +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (10),(20),(20),(30),(30),(30); +SELECT UNIQUE a FROM t1; +DROP TABLE t1; diff --git a/query_classifier/test/oracle/ps.test b/query_classifier/test/oracle/ps.test new file mode 100644 index 000000000..08bb957c3 --- /dev/null +++ b/query_classifier/test/oracle/ps.test @@ -0,0 +1,266 @@ +SET sql_mode=ORACLE; + +--echo # +--echo # MDEV-10801 sql_mode: dynamic SQL placeholders +--echo # + +SET @a=10, @b=20; +PREPARE stmt FROM 'SELECT ?,?'; +EXECUTE stmt USING @a, @b; +PREPARE stmt FROM 'SELECT :a,:b'; +EXECUTE stmt USING @a, @b; +PREPARE stmt FROM 'SELECT :aaa,:bbb'; +EXECUTE stmt USING @a, @b; +PREPARE stmt FROM 'SELECT :"a",:"b"'; +EXECUTE stmt USING @a, @b; +PREPARE stmt FROM 'SELECT :"aaa",:"bbb"'; +EXECUTE stmt USING @a, @b; +PREPARE stmt FROM 'SELECT :1,:2'; +EXECUTE stmt USING @a, @b; +PREPARE stmt FROM 'SELECT :222,:111'; +EXECUTE stmt USING @a, @b; +PREPARE stmt FROM 'SELECT :0,:65535'; +EXECUTE stmt USING @a, @b; +PREPARE stmt FROM 'SELECT :65535,:0'; +EXECUTE stmt USING @a, @b; + +--echo # +--echo # MDEV-10709 Expressions as parameters to Dynamic SQL +--echo # + +--echo # +--echo # Testing disallowed expressions in USING +--echo # + +PREPARE stmt FROM 'SELECT :1 FROM DUAL'; +--error ER_PARSE_ERROR +EXECUTE stmt USING (SELECT 1); +DEALLOCATE PREPARE stmt; + +DELIMITER $$; +CREATE FUNCTION f1() RETURN VARCHAR +AS +BEGIN + RETURN 'test'; +END; +$$ +DELIMITER ;$$ +PREPARE stmt FROM 'SELECT ? FROM DUAL'; +--error ER_SUBQUERIES_NOT_SUPPORTED +EXECUTE stmt USING f1(); +DEALLOCATE PREPARE stmt; +DROP FUNCTION f1; + +--echo # +--echo # Using a user variable as a EXECUTE..USING out parameter +--echo # + +DELIMITER /; +CREATE PROCEDURE p1(a OUT INT) +AS +BEGIN + a:= 10; +END; +/ +DELIMITER ;/ +SET @a=1; +CALL p1(@a); +SELECT @a; +SET @a=2; +PREPARE stmt FROM 'CALL p1(?)'; +EXECUTE stmt USING @a; +SELECT @a; +DROP PROCEDURE p1; + + +--echo # +--echo # Using an SP variable as a EXECUTE..USING out parameter +--echo # + +DELIMITER /; +CREATE PROCEDURE p1 (a OUT INT) +AS +BEGIN + a:=10; +END; +/ +CREATE PROCEDURE p2 (a OUT INT) +AS +BEGIN + PREPARE stmt FROM 'CALL p1(?)'; + EXECUTE stmt USING a; +END; +/ +DELIMITER ;/ +SET @a= 1; +CALL p2(@a); +SELECT @a; +DROP PROCEDURE p2; +DROP PROCEDURE p1; + + +--echo # +--echo # Using a trigger field as a EXECUTE..USING out parameter +--echo # +DELIMITER /; +CREATE PROCEDURE p1 (a OUT INT) +AS +BEGIN + a:= 10; +END; +/ +DELIMITER ;/ +CREATE TABLE t1 (a INT); +CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW CALL p1(:NEW.a); +INSERT INTO t1 VALUES (1); +SELECT * FROM t1; +DROP TABLE t1; +DROP PROCEDURE p1; + + +--echo # +--echo # Testing re-prepare on a table metadata update between PREPARE and EXECUTE +--echo # + +CREATE TABLE t1 (a INT); +DELIMITER /; +CREATE PROCEDURE p1(a IN INT) +AS +BEGIN + INSERT INTO t1 VALUES (a); +END; +/ +DELIMITER ;/ +PREPARE stmt FROM 'CALL p1(?)'; +EXECUTE stmt USING 10; +SELECT * FROM t1; +CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW NEW.a:=NEW.a+1; +EXECUTE stmt USING 20; +SELECT * FROM t1; +DEALLOCATE PREPARE stmt; +DROP PROCEDURE p1; +DROP TABLE t1; + +--echo # +--echo # End of MDEV-10709 Expressions as parameters to Dynamic SQL +--echo # + +--echo # +--echo # MDEV-10585 EXECUTE IMMEDIATE statement +--echo # + +--echo # +--echo # Testing disallowed expressions in USING +--echo # + +--error ER_PARSE_ERROR +EXECUTE IMMEDIATE 'SELECT :1 FROM DUAL' USING (SELECT 1); + +DELIMITER $$; +CREATE FUNCTION f1() RETURN VARCHAR +AS +BEGIN + RETURN 'test'; +END; +$$ +DELIMITER ;$$ +--error ER_SUBQUERIES_NOT_SUPPORTED +EXECUTE IMMEDIATE 'SELECT ? FROM DUAL' USING f1(); +DROP FUNCTION f1; + + +--echo # +--echo # Testing simple expressions +--echo # + +EXECUTE IMMEDIATE 'SELECT :1 FROM DUAL' USING 10; + + +--echo # +--echo # MDEV-10866 Extend PREPARE and EXECUTE IMMEDIATE to understand expressions +--echo # + +--echo # +--echo # Testing erroneous and diallowed prepare source +--echo # + +--error ER_CANT_AGGREGATE_2COLLATIONS +EXECUTE IMMEDIATE _latin1'SELECT 1 AS c FROM ' || _latin2 'DUAL'; +--error ER_CANT_AGGREGATE_2COLLATIONS +PREPARE stmt FROM _latin1'SELECT 1 AS c FROM ' || _latin2 'DUAL'; + +--error ER_PARSE_ERROR +EXECUTE IMMEDIATE (SELECT 'SELECT 1'); +--error ER_PARSE_ERROR +PREPARE stmt FROM (SELECT 'SELECT 1'); + +--error ER_BAD_FIELD_ERROR +EXECUTE IMMEDIATE a; +--error ER_BAD_FIELD_ERROR +PREPARE stmt FROM a; + +--error ER_PARSE_ERROR +EXECUTE IMMEDIATE NULL; +--error ER_PARSE_ERROR +PREPARE stmt FROM NULL; + +--error ER_PARSE_ERROR +EXECUTE IMMEDIATE COALESCE(NULL); +--error ER_PARSE_ERROR +PREPARE stmt FROM COALESCE(NULL); + +DELIMITER $$; +CREATE FUNCTION f1() RETURN VARCHAR +AS +BEGIN + RETURN 't1'; +END; +$$ +DELIMITER ;$$ +--error ER_SUBQUERIES_NOT_SUPPORTED +EXECUTE IMMEDIATE f1(); +--error ER_SUBQUERIES_NOT_SUPPORTED +PREPARE stmt FROM f1(); +DROP FUNCTION f1; + +--echo # +--echo # Testing user variables in prepare source +--echo # + +SET @table_name='DUAL'; +EXECUTE IMMEDIATE 'SELECT 1 AS a FROM ' || @table_name; +PREPARE stmt FROM 'SELECT 1 AS a FROM ' || @table_name; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +--echo # +--echo # Testing SP parameters and variables in prepare source +--echo # + +DELIMITER $$; +CREATE PROCEDURE p1(table_name VARCHAR) +AS +BEGIN + EXECUTE IMMEDIATE 'SELECT 1 AS c FROM '|| table_name; +END; +$$ +DELIMITER ;$$ +CALL p1('DUAL'); +DROP PROCEDURE p1; + +DELIMITER $$; +CREATE PROCEDURE p1() +AS + table_name VARCHAR(64):='DUAL'; +BEGIN + EXECUTE IMMEDIATE 'SELECT 1 AS c FROM ' || table_name; +END; +$$ +DELIMITER ;$$ +CALL p1(); +DROP PROCEDURE p1; + + +--echo # +--echo # End of MDEV-10866 Extend PREPARE and EXECUTE IMMEDIATE to understand expressions +--echo # diff --git a/query_classifier/test/oracle/sequence.test b/query_classifier/test/oracle/sequence.test new file mode 100644 index 000000000..719c4bcd4 --- /dev/null +++ b/query_classifier/test/oracle/sequence.test @@ -0,0 +1,43 @@ +--source include/have_binlog_format_row.inc + +SET sql_mode=ORACLE; + +CREATE SEQUENCE s1; +SHOW CREATE SEQUENCE s1; +SELECT s1.currval; +SELECT s1.nextval; +SELECT s1.nextval; +SELECT s1.nextval; +EXPLAIN EXTENDED SELECT s1.nextval; +SELECT nextval(s1); +EXPLAIN EXTENDED SELECT s1.currval; +SELECT lastval(s1); +DROP SEQUENCE s1; + + +CREATE SEQUENCE s1; +CREATE VIEW v1 AS SELECT s1.nextval AS a; +SELECT VIEW_DEFINITION FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_NAME='v1'; +SELECT * FROM v1; +SHOW CREATE VIEW v1; +DROP VIEW v1; +DROP SEQUENCE s1; + + +CREATE SEQUENCE s1; +CREATE VIEW v1 AS SELECT s1.currval AS a; +SELECT VIEW_DEFINITION FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_NAME='v1'; +SELECT * FROM v1; +SHOW CREATE VIEW v1; +DROP VIEW v1; +DROP SEQUENCE s1; + +--echo # +--echo # MDEV-12533 sql_mode=ORACLE: Add support for database qualified sequence names in NEXTVAL and CURRVAL +--echo # +CREATE SEQUENCE s1; +SELECT test.s1.nextval; +SELECT test.s1.currval; +SELECT .s1.nextval; +SELECT .s1.currval; +DROP SEQUENCE s1; diff --git a/query_classifier/test/oracle/sp-anonymous.test b/query_classifier/test/oracle/sp-anonymous.test new file mode 100644 index 000000000..ac61e8ace --- /dev/null +++ b/query_classifier/test/oracle/sp-anonymous.test @@ -0,0 +1,244 @@ +--source include/have_innodb.inc + +SET sql_mode=ORACLE; + +--echo # +--echo # MDEV-10655 Anonymous blocks +--echo # + +--echo # Testing BEGIN NOT ATOMIC with no declarations +DELIMITER /; +BEGIN NOT ATOMIC + SELECT 1 AS a; +END +/ +DELIMITER ;/ + +--echo # Testing BEGIN NOT ATOMIC with declarations +--echo # DECLARE starts a new block and thus must be followed by BEGIN .. END +DELIMITER /; +BEGIN NOT ATOMIC + DECLARE + i INT DEFAULT 5; + x INT DEFAULT 10; + BEGIN + <