Merge branch '2.1-oracle-compat' into develop-new-merge-oracle

This commit is contained in:
Johan Wikman 2017-06-28 22:30:16 +02:00
commit 6cd6ded3d8
88 changed files with 15138 additions and 745 deletions

View File

@ -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)

View File

@ -584,6 +584,43 @@ documentation for more details.
Enable or disable the admin interface. This allows the admin interface to
be completely disabled to prevent access to it.
#### `sql_mode`
Specifies whether the query classifier parser should initially expect _MariaDB_
or _PL/SQL_ kind of SQL.
The allowed values are:
`default`: The parser expects regular _MariaDB_ SQL.
`oracle` : The parser expects PL/SQL.
```
sql_mode=oracle
```
The default value is `default`.
**NOTE** If `sql_mode` is set to `oracle`, then MaxScale will also assume
that `autocommit` initially is off.
At runtime, MariaDB MaxScale will recognize statements like
```
set sql_mode=oracle;
```
and
```
set sql_mode=default;
```
and change mode accordingly.
**NOTE** If `set sql_mode=oracle;` is encountered, then MaxScale will also
behave as if `autocommit` had been turned off and conversely, if
`set sql_mode=default;` is encountered, then MaxScale will also behave
as if `autocommit` had been turned on.
Note that MariaDB MaxScale is **not** explicitly aware of the sql mode of
the server, so the value of `sql_mode` should reflect the sql mode used
when the server is started.
### Service
A service represents the database service that MariaDB MaxScale offers to the

View File

@ -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).

View File

@ -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).

View File

@ -182,7 +182,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);

View File

@ -6,7 +6,7 @@
# MYSQL_EMBEDDED_LIBRARIES - The MySQL embedded library
# MYSQL_EMBEDDED_INCLUDE_DIR - Path to MySQL headers
find_file(MYSQL_EMBEDDED_VERSION_H mysql_version.h PATHS ${MYSQL_EMBEDDED_INCLUDE_DIR} PATH_SUFFIXES mysql)
find_file(MYSQL_EMBEDDED_VERSION_H mysql_version.h HINTS ${MYSQL_EMBEDDED_INCLUDE_DIR} PATH_SUFFIXES mysql)
if(MYSQL_EMBEDDED_VERSION_H MATCHES "MYSQL_EMBEDDED_VERSION_H-NOTFOUND")
message(FATAL_ERROR "Cannot find the mysql_version.h header")
else()

View File

@ -25,6 +25,7 @@
#include <maxscale/modinfo.h>
#include <maxscale/jansson.h>
#include <maxscale/pcre2.h>
#include <maxscale/query_classifier.h>
MXS_BEGIN_DECLS
@ -204,6 +205,7 @@ typedef struct
bool skip_permission_checks; /**< Skip service and monitor permission checks */
char qc_name[PATH_MAX]; /**< The name of the query classifier to load */
char* qc_args; /**< Arguments for the query classifier */
qc_sql_mode_t qc_sql_mode; /**< The query classifier sql mode */
char admin_host[MAX_ADMIN_HOST_LEN]; /**< Admin interface host */
uint16_t admin_port; /**< Admin interface port */
bool admin_auth; /**< Admin interface authentication */

View File

@ -0,0 +1,238 @@
#pragma once
/*
* Copyright (c) 2016 MariaDB Corporation Ab
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file and at www.mariadb.com/bsl11.
*
* Change Date: 2019-07-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2 or later of the General
* Public License.
*/
#include <maxscale/cppdefs.hh>
#include <maxscale/log_manager.h>
#include <maxscale/modutil.h>
#include <ctype.h>
namespace maxscale
{
#define MXS_CP_EXPECT_TOKEN(string_literal) string_literal, (sizeof(string_literal) - 1)
// For debugging purposes.
// #define MXS_CP_LOG_UNEXPECTED_AND_EXHAUSTED
#undef MXS_CP_LOG_UNEXPECTED_AND_EXHAUSTED
class CustomParser
{
CustomParser(const CustomParser&);
CustomParser& operator = (const CustomParser&);
public:
typedef int32_t token_t;
enum token_required_t
{
TOKEN_REQUIRED,
TOKEN_NOT_REQUIRED,
};
enum
{
PARSER_UNKNOWN_TOKEN = -2,
PARSER_EXHAUSTED = -1
};
CustomParser()
: m_pSql(NULL)
, m_len(0)
, m_pI(NULL)
, m_pEnd(NULL)
{
}
protected:
/**
* To be called when unexpected data is encountered. For debugging
* purposes, logging will only be performed if the define
* MXS_CP_LOG_UNEXPECTED_AND_EXHAUSTED is defined.
*/
void log_unexpected()
{
#ifdef MXS_CP_LOG_UNEXPECTED_AND_EXHAUSTED
MXS_NOTICE("Custom parser: In statement '%.*s', unexpected token at '%.*s'.",
(int)m_len, m_pSql, (int)(m_pEnd - m_pI), m_pI);
#endif
}
/**
* To be called when there is no more data even though there is
* expected to be. For debugging purposes, logging will only be
* performed if the define MXS_CP_LOG_UNEXPECTED_AND_EXHAUSTED
* is defined.
*/
void log_exhausted()
{
#ifdef MXS_CP_LOG_UNEXPECTED_AND_EXHAUSTED
MXS_NOTICE("Custom parser: More tokens expected in statement '%.*s'.", (int)m_len, m_pSql);
#endif
}
/**
* Is the character an alphabetic character.
*
* @param c A char
*
* @return True if @c c is between 'a' and 'z' or 'A' and 'Z', inclusive.
*/
static bool is_alpha(char c)
{
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
}
/**
* Is the character a number
*
* @param c A char
*
* @return True if @c c is between '0' and '9' inclusive.
*/
static bool is_number(char c)
{
return (c >= '0' && c <= '9');
}
/**
* Is a character some offset from the current position, a specific one.
*
* @param uc An UPPERCASE character.
* @param offset How many characters from the current position.
*
* @return True if the character at the position is the one specified or
* its lowercase equivalent.
*/
bool is_next_alpha(char uc, int offset = 1) const
{
ss_dassert(uc >= 'A' && uc <= 'Z');
char lc = uc + ('a' - 'A');
return
((m_pI + offset) < m_pEnd) &&
((*(m_pI + offset) == uc) || (*(m_pI + offset) == lc));
}
/**
* Peek current character.
*
* @param pC Upon successful return will be the current character.
*
* @return True, if the current character was returned, false otherwise.
* False will only be returned if the current position is at
* the end.
*/
bool peek_current_char(char* pC) const
{
if (m_pI != m_pEnd)
{
*pC = *m_pI;
}
return m_pI != m_pEnd;
}
/**
* Peek next character.
*
* @param pC Upon successful return will be the next character.
*
* @return True, if the next character was returned, false otherwise.
* False will only be returned if the current position is at
* the end.
*/
bool peek_next_char(char* pC) const
{
bool rc = (m_pI + 1 < m_pEnd);
if (rc)
{
*pC = *(m_pI + 1);
}
return rc;
}
/**
* Convert a character to upper case.
*
* @param c The character to convert.
*
* @return The uppercase equivalent. If @c c is already uppercase,
* then it is returned.
*/
static char toupper(char c)
{
// Significantly faster than library version.
return (c >= 'a' && c <='z') ? c - ('a' - 'A') : c;
}
/**
* Bypass all whitespace from current position.
*/
void bypass_whitespace()
{
m_pI = modutil_MySQL_bypass_whitespace(const_cast<char*>(m_pI), m_pEnd - m_pI);
}
/**
* Check whether an expected token is available.
*
* @param zWord A token.
* @param len The token length.
* @param token The value to be returned if the next token is the
* expected one.
*
* @return @c token if the current token is the expected one,
* otherwise PARSER_UNKNOWN_TOKEN.
*/
token_t expect_token(const char* zWord, int len, token_t token)
{
const char* pI = m_pI;
const char* pEnd = zWord + len;
while ((pI < m_pEnd) && (zWord < pEnd) && (toupper(*pI) == *zWord))
{
++pI;
++zWord;
}
if (zWord == pEnd)
{
if ((pI == m_pEnd) || (!isalpha(*pI))) // Handwritten isalpha not faster than library version.
{
m_pI = pI;
}
else
{
token = PARSER_UNKNOWN_TOKEN;
}
}
else
{
token = PARSER_UNKNOWN_TOKEN;
}
return token;
}
protected:
const char* m_pSql;
int m_len;
const char* m_pI;
const char* m_pEnd;
};
}

View File

@ -29,6 +29,16 @@ typedef enum qc_init_kind
QC_INIT_BOTH = 0x03
} qc_init_kind_t;
/**
* qc_sql_mode_t specifies what should be assumed of the statements
* that will be parsed.
*/
typedef enum qc_sql_mode
{
QC_SQL_MODE_DEFAULT, /*< Assume the statements are MariaDB SQL. */
QC_SQL_MODE_ORACLE /*< Assume the statements are PL/SQL. */
} qc_sql_mode_t;
/**
* @c qc_collect_info_t specifies what information should be collected during parsing.
*/
@ -83,19 +93,22 @@ 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_GRANT,
QUERY_OP_REVOKE,
QUERY_OP_CREATE,
QUERY_OP_DELETE,
QUERY_OP_DROP,
QUERY_OP_EXECUTE,
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;
/**
@ -173,12 +186,13 @@ typedef struct query_classifier
/**
* Called once to setup the query classifier
*
* @param args The value of `query_classifier_args` in the configuration file.
* @param sql_mode The default sql mode.
* @param args The value of `query_classifier_args` in the configuration file.
*
* @return QC_RESULT_OK, if the query classifier could be setup, otherwise
* some specific error code.
*/
int32_t (*qc_setup)(const char* args);
int32_t (*qc_setup)(qc_sql_mode_t sql_mode, const char* args);
/**
* Called once at process startup, after @c qc_setup has successfully
@ -393,6 +407,24 @@ typedef struct query_classifier
* version = major * 10000 + minor * 100 + patch
*/
void (*qc_get_server_version)(uint64_t* version);
/**
* Gets the sql mode of the *calling* thread.
*
* @param sql_mode The mode.
*
* @return QC_RESULT_OK
*/
int32_t (*qc_get_sql_mode)(qc_sql_mode_t* sql_mode);
/**
* Sets the sql mode for the *calling* thread.
*
* @param sql_mode The mode.
*
* @return QC_RESULT_OK if @sql_mode is valid, otherwise QC_RESULT_ERROR.
*/
int32_t (*qc_set_sql_mode)(qc_sql_mode_t sql_mode);
} QUERY_CLASSIFIER;
/**
@ -406,6 +438,7 @@ typedef struct query_classifier
*
* @param plugin_name The name of the plugin from which the query classifier
* should be loaded.
* @param sql_mode The default sql mode.
* @param plugin_args The arguments to be provided to the query classifier.
*
* @return True if the query classifier could be loaded and initialized,
@ -413,7 +446,7 @@ typedef struct query_classifier
*
* @see qc_end qc_thread_init
*/
bool qc_setup(const char* plugin_name, const char* plugin_args);
bool qc_setup(const char* plugin_name, qc_sql_mode_t sql_mode, const char* plugin_args);
/**
* Intializes the query classifier.
@ -658,6 +691,13 @@ char* qc_get_prepare_name(GWBUF* stmt);
*/
GWBUF* qc_get_preparable_stmt(GWBUF* stmt);
/**
* Gets the sql mode of the *calling* thread.
*
* @return The mode.
*/
qc_sql_mode_t qc_get_sql_mode();
/**
* Returns the tables accessed by the statement.
*
@ -756,6 +796,13 @@ static inline bool qc_query_is_type(uint32_t typemask, qc_query_type_t type)
*/
bool qc_query_has_clause(GWBUF* stmt);
/**
* Sets the sql mode for the *calling* thread.
*
* @param sql_mode The mode.
*/
void qc_set_sql_mode(qc_sql_mode_t sql_mode);
/**
* Returns the string representation of a query type.
*

View File

@ -146,6 +146,7 @@ typedef struct session
int refcount; /*< Reference count on the session */
mxs_session_trx_state_t trx_state; /*< The current transaction state. */
bool autocommit; /*< Whether autocommit is on. */
intptr_t client_protocol_data; /*< Owned and managed by the client protocol. */
struct
{
GWBUF *buffer; /**< Buffer containing the statement */

View File

@ -591,6 +591,8 @@ 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
# 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

View File

@ -29,7 +29,6 @@
#if defined(MYSQL_CLIENT)
#undef MYSQL_CLIENT
#endif
#include <my_config.h>
#include <mysql.h>
#include <my_sys.h>
@ -55,8 +54,11 @@
#include <strfunc.h>
#include <item_func.h>
#include <pthread.h>
#include <maxscale/debug.h>
#include <maxscale/log_manager.h>
#include <maxscale/platform.h>
#include <maxscale/query_classifier.h>
// <maxscale/protocol/mysql.h> assumes it is being compiled agains Connector-C,
// so we need to make certain Connector-C constants visible.
@ -72,6 +74,47 @@
#include <string.h>
#include <stdarg.h>
/**
* Defines what a particular name should be mapped to.
*/
typedef struct name_mapping
{
const char* from;
const char* to;
} NAME_MAPPING;
static NAME_MAPPING function_name_mappings_default[] =
{
{ NULL, NULL }
};
static NAME_MAPPING function_name_mappings_oracle[] =
{
{ "concat_operator_oracle", "concat" },
{ "case", "decode" },
{ NULL, NULL }
};
static const char* map_function_name(NAME_MAPPING* function_name_mappings, const char* from)
{
NAME_MAPPING* map = 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;
}
#define MYSQL_COM_QUERY_HEADER_SIZE 5 /*< 3 bytes size, 1 sequence, 1 command */
#define MAX_QUERYBUF_SIZE 2048
typedef struct parsing_info_st
@ -89,19 +132,14 @@ typedef struct parsing_info_st
size_t function_infos_len;
size_t function_infos_capacity;
GWBUF* preparable_stmt;
qc_parse_result_t result;
int32_t type_mask;
NAME_MAPPING* function_name_mappings;
#if defined(SS_DEBUG)
skygw_chk_t pi_chk_tail;
#endif
} parsing_info_t;
static thread_local struct
{
// The version information is not used; the embedded library parses according
// to the version of the embedded library it has been linked with. However, we
// need to store the information so that qc_[get|set]_server_version will work.
uint64_t version;
} this_thread;
#define QTYPE_LESS_RESTRICTIVE_THAN_WRITE(t) (t<QUERY_TYPE_WRITE ? true : false)
static THD* get_or_create_thd_for_parsing(MYSQL* mysql, char* query_str);
@ -119,8 +157,50 @@ static TABLE_LIST* skygw_get_affected_tables(void* lexptr);
static bool ensure_query_is_parsed(GWBUF* query);
static bool parse_query(GWBUF* querybuf);
static bool query_is_parsed(GWBUF* buf);
int32_t qc_mysql_get_field_info(GWBUF* buf, const QC_FIELD_INFO** infos, uint32_t* n_infos);
#if MYSQL_VERSION_MAJOR >= 10 && 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
static struct
{
qc_sql_mode_t sql_mode;
pthread_mutex_t sql_mode_mutex;
NAME_MAPPING* function_name_mappings;
} this_unit =
{
QC_SQL_MODE_DEFAULT,
PTHREAD_MUTEX_INITIALIZER,
function_name_mappings_default
};
static thread_local struct
{
qc_sql_mode_t sql_mode;
NAME_MAPPING* function_name_mappings;
// The version information is not used; the embedded library parses according
// to the version of the embedded library it has been linked with. However, we
// need to store the information so that qc_[get|set]_server_version will work.
uint64_t version;
} this_thread =
{
QC_SQL_MODE_DEFAULT,
function_name_mappings_default,
0
};
/**
* Ensures that the query is parsed. If it is not already parsed, it
* will be parsed.
@ -133,8 +213,38 @@ bool ensure_query_is_parsed(GWBUF* query)
if (!parsed)
{
// Instead of modifying global_system_variables, from which
// thd->variables.sql_mode will be initialied, we should modify
// thd->variables.sql_mode _after_ it has been created and
// initialized.
//
// However, for whatever reason, the offset of that variable is
// different when accessed from within libmysqld and qc_mysqlembedded,
// so we will not modify the right variable even if it appears we do.
//
// So, for the time being we modify global_system_variables.sql_mode and
// serialize the parsing. That's ok, since qc_mysqlembedded is only
// used for verifying the behaviour of qc_sqlite.
ss_debug(int rv);
ss_debug(rv = )pthread_mutex_lock(&this_unit.sql_mode_mutex);
ss_dassert(rv == 0);
if (this_thread.sql_mode == QC_SQL_MODE_ORACLE)
{
global_system_variables.sql_mode |= MODE_ORACLE;
}
else
{
global_system_variables.sql_mode &= ~MODE_ORACLE;
}
parsed = parse_query(query);
ss_debug(rv = )pthread_mutex_unlock(&this_unit.sql_mode_mutex);
ss_dassert(rv == 0);
if (!parsed)
{
MXS_ERROR("Unable to parse query, out of resources?");
@ -156,7 +266,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
{
@ -168,6 +282,8 @@ int32_t qc_mysql_parse(GWBUF* querybuf, uint32_t collect, int32_t* result)
int32_t qc_mysql_get_type_mask(GWBUF* querybuf, uint32_t* type_mask)
{
int32_t rv = QC_RESULT_OK;
*type_mask = QUERY_TYPE_UNKNOWN;
MYSQL* mysql;
bool succp;
@ -198,12 +314,25 @@ int32_t qc_mysql_get_type_mask(GWBUF* querybuf, uint32_t* type_mask)
if (mysql != NULL)
{
*type_mask = resolve_query_type(pi, (THD *) mysql->thd);
#if MYSQL_VERSION_MAJOR >= 10 && MYSQL_VERSION_MINOR >= 3
// If in 10.3 mode we need to ensure that sequence related functions
// are taken into account. That we can ensure by querying for the fields.
const QC_FIELD_INFO* field_infos;
uint32_t n_field_infos;
rv = qc_mysql_get_field_info(querybuf, &field_infos, &n_field_infos);
if (rv == QC_RESULT_OK)
{
*type_mask |= pi->type_mask;
}
#endif
}
}
}
retblock:
return QC_RESULT_OK;
return rv;
}
/**
@ -278,7 +407,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,
@ -462,7 +595,7 @@ static bool create_parse_tree(THD* thd)
}
return_here:
return failp;
return !failp;
}
/**
@ -601,6 +734,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))
@ -716,6 +855,10 @@ static uint32_t resolve_query_type(parsing_info_t *pi, THD* thd)
}
}
}
else
{
type |= QUERY_TYPE_READ;
}
goto return_qtype;
}
@ -770,7 +913,6 @@ static uint32_t resolve_query_type(parsing_info_t *pi, THD* thd)
break;
case SQLCOM_SELECT:
case SQLCOM_SHOW_SLAVE_STAT:
type |= QUERY_TYPE_READ;
break;
@ -811,15 +953,30 @@ static uint32_t resolve_query_type(parsing_info_t *pi, THD* thd)
goto return_qtype;
break;
case SQLCOM_SHOW_FIELDS:
type |= QUERY_TYPE_READ;
break;
case SQLCOM_SHOW_TABLES:
type |= QUERY_TYPE_SHOW_TABLES;
goto return_qtype;
break;
case SQLCOM_SHOW_CREATE:
case SQLCOM_SHOW_CREATE_DB:
case SQLCOM_SHOW_CREATE_FUNC:
case SQLCOM_SHOW_CREATE_PROC:
case SQLCOM_SHOW_FIELDS:
case SQLCOM_SHOW_FUNC_CODE:
case SQLCOM_SHOW_GRANTS:
case SQLCOM_SHOW_PROC_CODE:
case SQLCOM_SHOW_SLAVE_HOSTS:
case SQLCOM_SHOW_SLAVE_STAT:
case SQLCOM_SHOW_STATUS:
type |= QUERY_TYPE_READ;
goto return_qtype;
break;
case SQLCOM_END:
goto return_qtype;
break;
default:
type |= QUERY_TYPE_WRITE;
break;
@ -857,8 +1014,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)
{
@ -952,10 +1107,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;
}
@ -1273,6 +1436,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;
@ -1295,6 +1485,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)
@ -1428,16 +1623,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();
}
}
}
}
@ -1498,6 +1696,9 @@ 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;
ss_dassert(this_thread.function_name_mappings);
pi->function_name_mappings = this_thread.function_name_mappings;
retblock:
return pi;
@ -1600,6 +1801,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)
@ -1663,94 +1869,128 @@ 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:
#if MYSQL_VERSION_MAJOR >= 10 && MYSQL_VERSION_MINOR >= 3
case SQLCOM_CREATE_SEQUENCE:
#endif
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:
#if MYSQL_VERSION_MAJOR >= 10 && MYSQL_VERSION_MINOR >= 3
case SQLCOM_DROP_SEQUENCE:
#endif
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;
case SQLCOM_EXECUTE:
*operation = QUERY_OP_EXECUTE;
break;
case SQLCOM_SHOW_CREATE:
case SQLCOM_SHOW_CREATE_DB:
case SQLCOM_SHOW_CREATE_FUNC:
case SQLCOM_SHOW_CREATE_PROC:
case SQLCOM_SHOW_DATABASES:
case SQLCOM_SHOW_FIELDS:
case SQLCOM_SHOW_FUNC_CODE:
case SQLCOM_SHOW_GRANTS:
case SQLCOM_SHOW_KEYS:
case SQLCOM_SHOW_MASTER_STAT:
case SQLCOM_SHOW_PROC_CODE:
case SQLCOM_SHOW_SLAVE_HOSTS:
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;
case SQLCOM_EXECUTE:
*operation = QUERY_OP_EXECUTE;
break;
default:
*operation = QUERY_OP_UNDEFINED;
}
}
}
}
@ -1769,15 +2009,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;
}
}
}
}
@ -1796,14 +2039,21 @@ 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);
if (!pi->preparable_stmt)
{
const char* preparable_stmt = lex->prepared_stmt_code.str;
size_t preparable_stmt_len = lex->prepared_stmt_code.length;
const char* preparable_stmt;
size_t preparable_stmt_len;
#if MYSQL_VERSION_MINOR >= 3
preparable_stmt = lex->prepared_stmt_code->name.str;
preparable_stmt_len = lex->prepared_stmt_code->name.length;
#else
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;
@ -1861,9 +2111,13 @@ static bool should_exclude(const char* name, List<Item>* 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;
}
@ -1981,6 +2235,8 @@ static void add_function_info(parsing_info_t* info,
{
ss_dassert(name);
name = map_function_name(info->function_name_mappings, name);
QC_FUNCTION_INFO item = { (char*)name, usage };
size_t i;
@ -2036,7 +2292,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);
@ -2125,7 +2386,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);
}
@ -2161,6 +2427,43 @@ static void remove_surrounding_back_ticks(char* s)
}
}
static bool should_function_be_ignored(parsing_info_t* pi, const char* func_name)
{
bool rv = false;
// We want to ignore functions that do not really appear as such in an
// actual SQL statement. E.g. "SELECT @a" appears as a function "get_user_var".
if ((strcasecmp(func_name, "decimal_typecast") == 0) ||
(strcasecmp(func_name, "cast_as_char") == 0) ||
(strcasecmp(func_name, "cast_as_date") == 0) ||
(strcasecmp(func_name, "cast_as_datetime") == 0) ||
(strcasecmp(func_name, "cast_as_time") == 0) ||
(strcasecmp(func_name, "cast_as_signed") == 0) ||
(strcasecmp(func_name, "cast_as_unsigned") == 0) ||
(strcasecmp(func_name, "get_user_var") == 0) ||
(strcasecmp(func_name, "get_system_var") == 0) ||
(strcasecmp(func_name, "set_user_var") == 0) ||
(strcasecmp(func_name, "set_system_var") == 0))
{
rv = true;
}
// Any sequence related functions should be ignored as well.
#if MYSQL_VERSION_MAJOR >= 10 && MYSQL_VERSION_MINOR >= 3
if (!rv)
{
if ((strcasecmp(func_name, "lastval") == 0) ||
(strcasecmp(func_name, "nextval") == 0))
{
pi->type_mask |= QUERY_TYPE_WRITE;
rv = true;
}
}
#endif
return rv;
}
static void update_field_infos(parsing_info_t* pi,
collect_source_t source,
Item* item,
@ -2278,24 +2581,17 @@ static void update_field_infos(parsing_info_t* pi,
// We want to ignore functions that do not really appear as such in an
// actual SQL statement. E.g. "SELECT @a" appears as a function "get_user_var".
if ((strcasecmp(func_name, "decimal_typecast") != 0) &&
(strcasecmp(func_name, "cast_as_char") != 0) &&
(strcasecmp(func_name, "cast_as_date") != 0) &&
(strcasecmp(func_name, "cast_as_datetime") != 0) &&
(strcasecmp(func_name, "cast_as_time") != 0) &&
(strcasecmp(func_name, "cast_as_signed") != 0) &&
(strcasecmp(func_name, "cast_as_unsigned") != 0) &&
(strcasecmp(func_name, "get_user_var") != 0) &&
(strcasecmp(func_name, "get_system_var") != 0) &&
(strcasecmp(func_name, "set_user_var") != 0) &&
(strcasecmp(func_name, "set_system_var") != 0))
if (!should_function_be_ignored(pi, func_name))
{
if (strcmp(func_name, "%") == 0)
{
// 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");
}
@ -2311,7 +2607,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");
}
@ -2472,6 +2771,9 @@ static void update_field_infos(parsing_info_t* pi,
int32_t qc_mysql_get_field_info(GWBUF* buf, const QC_FIELD_INFO** infos, uint32_t* n_infos)
{
*infos = NULL;
*n_infos = 0;
if (!buf)
{
return QC_RESULT_OK;
@ -2479,7 +2781,7 @@ int32_t qc_mysql_get_field_info(GWBUF* buf, const QC_FIELD_INFO** infos, uint32_
if (!ensure_query_is_parsed(buf))
{
return QC_RESULT_ERROR;;
return QC_RESULT_ERROR;
}
parsing_info_t* pi = get_pinfo(buf);
@ -2495,6 +2797,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)
@ -2587,19 +2896,27 @@ int32_t qc_mysql_get_function_info(GWBUF* buf,
*function_infos = NULL;
*n_function_infos = 0;
const QC_FIELD_INFO* field_infos;
uint32_t n_field_infos;
int32_t rv = QC_RESULT_OK;
// We ensure the information has been collected by querying the fields first.
qc_mysql_get_field_info(buf, &field_infos, &n_field_infos);
if (buf)
{
const QC_FIELD_INFO* field_infos;
uint32_t n_field_infos;
parsing_info_t* pi = get_pinfo(buf);
ss_dassert(pi);
// We ensure the information has been collected by querying the fields first.
rv = qc_mysql_get_field_info(buf, &field_infos, &n_field_infos);
*function_infos = pi->function_infos;
*n_function_infos = pi->function_infos_len;
if (rv == QC_RESULT_OK)
{
parsing_info_t* pi = get_pinfo(buf);
ss_dassert(pi);
return QC_RESULT_OK;
*function_infos = pi->function_infos;
*n_function_infos = pi->function_infos_len;
}
}
return rv;
}
void qc_mysql_set_server_version(uint64_t version)
@ -2622,7 +2939,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
};
@ -2668,12 +2988,19 @@ void configure_options(const char* datadir, const char* langdir)
}
int32_t qc_mysql_setup(const char* args)
int32_t qc_mysql_setup(qc_sql_mode_t sql_mode, const char* zArgs)
{
if (args)
this_unit.sql_mode = sql_mode;
if (sql_mode == QC_SQL_MODE_ORACLE)
{
this_unit.function_name_mappings = function_name_mappings_oracle;
}
if (zArgs)
{
MXS_WARNING("'%s' provided as arguments, "
"even though no arguments are supported.", args);
"even though no arguments are supported.", zArgs);
}
return QC_RESULT_OK;
@ -2699,6 +3026,10 @@ int32_t qc_mysql_process_init(void)
if (rc != 0)
{
this_thread.sql_mode = this_unit.sql_mode;
ss_dassert(this_unit.function_name_mappings);
this_thread.function_name_mappings = this_unit.function_name_mappings;
MXS_ERROR("mysql_library_init() failed. Error code: %d", rc);
}
else
@ -2721,6 +3052,10 @@ void qc_mysql_process_end(void)
int32_t qc_mysql_thread_init(void)
{
this_thread.sql_mode = this_unit.sql_mode;
ss_dassert(this_unit.function_name_mappings);
this_thread.function_name_mappings = this_unit.function_name_mappings;
bool inited = (mysql_thread_init() == 0);
if (!inited)
@ -2736,6 +3071,35 @@ void qc_mysql_thread_end(void)
mysql_thread_end();
}
int32_t qc_mysql_get_sql_mode(qc_sql_mode_t* sql_mode)
{
*sql_mode = this_thread.sql_mode;
return QC_RESULT_OK;
}
int32_t qc_mysql_set_sql_mode(qc_sql_mode_t sql_mode)
{
int32_t rv = QC_RESULT_OK;
switch (sql_mode)
{
case QC_SQL_MODE_DEFAULT:
this_thread.sql_mode = sql_mode;
this_thread.function_name_mappings = function_name_mappings_default;
break;
case QC_SQL_MODE_ORACLE:
this_thread.sql_mode = sql_mode;
this_thread.function_name_mappings = function_name_mappings_oracle;
break;
default:
rv = QC_RESULT_ERROR;
}
return rv;
}
/**
* EXPORTS
*/
@ -2767,6 +3131,8 @@ extern "C"
qc_mysql_get_preparable_stmt,
qc_mysql_set_server_version,
qc_mysql_get_server_version,
qc_mysql_get_sql_mode,
qc_mysql_set_sql_mode,
};
static MXS_MODULE info =

View File

@ -413,6 +413,14 @@ static const char* BUILTIN_10_2_3_FUNCTIONS[] =
const size_t N_BUILTIN_10_2_3_FUNCTIONS =
sizeof(BUILTIN_10_2_3_FUNCTIONS) / sizeof(BUILTIN_10_2_3_FUNCTIONS[0]);
static const char* ORACLE_FUNCTIONS[] =
{
"nvl",
"nvl2"
};
const size_t N_ORACLE_FUNCTIONS = sizeof(ORACLE_FUNCTIONS) / sizeof(ORACLE_FUNCTIONS[0]);
// NOTE: sort_compare and search_compare are not identical, so don't
// NOTE: optimize either of them away.
static int sort_compare(const void* key, const void* value)
@ -435,6 +443,7 @@ void init_builtin_functions()
qsort(BUILTIN_FUNCTIONS, N_BUILTIN_FUNCTIONS, sizeof(char*), sort_compare);
qsort(BUILTIN_10_2_3_FUNCTIONS, N_BUILTIN_10_2_3_FUNCTIONS, sizeof(char*), sort_compare);
qsort(ORACLE_FUNCTIONS, N_ORACLE_FUNCTIONS, sizeof(char*), sort_compare);
unit.inited = true;
}
@ -445,7 +454,9 @@ void finish_builtin_functions()
unit.inited = false;
}
bool is_builtin_readonly_function(const char* key, uint32_t major, uint32_t minor, uint32_t patch)
bool is_builtin_readonly_function(const char* key,
uint32_t major, uint32_t minor, uint32_t patch,
bool check_oracle)
{
ss_dassert(unit.inited);
@ -462,5 +473,10 @@ bool is_builtin_readonly_function(const char* key, uint32_t major, uint32_t mino
}
}
if (!value && check_oracle)
{
value = bsearch(key, ORACLE_FUNCTIONS, N_ORACLE_FUNCTIONS, sizeof(char*), search_compare);
}
return value ? true : false;
}

View File

@ -22,7 +22,9 @@ extern "C" {
void init_builtin_functions();
void finish_builtin_functions();
bool is_builtin_readonly_function(const char* zToken, uint32_t major, uint32_t minor, uint32_t patch);
bool is_builtin_readonly_function(const char* zToken,
uint32_t major, uint32_t minor, uint32_t patch,
bool check_oracle);
#ifdef __cplusplus
}

File diff suppressed because it is too large Load Diff

View File

@ -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
@ -85,7 +86,7 @@ typedef enum qc_field_usage
extern void mxs_sqlite3AlterFinishAddColumn(Parse *, Token *);
extern void mxs_sqlite3AlterBeginAddColumn(Parse *, SrcList *);
extern void mxs_sqlite3Analyze(Parse *, SrcList *);
extern void mxs_sqlite3BeginTransaction(Parse*, int);
extern void mxs_sqlite3BeginTransaction(Parse*, int token, int type);
extern void mxs_sqlite3CommitTransaction(Parse*);
extern void mxs_sqlite3CreateIndex(Parse*,Token*,Token*,SrcList*,ExprList*,int,Token*,
Expr*, int, int);
@ -107,18 +108,21 @@ 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, ExprList* pExprList);
extern void maxscaleCheckTable(Parse*, SrcList* pTables);
extern void maxscaleCreateSequence(Parse*, Token* pDatabase, Token* pTable);
extern void maxscaleDeclare(Parse* pParse);
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 maxscaleDrop(Parse*, int what, Token* pDatabase, Token* pName);
extern void maxscaleExecute(Parse*, Token* pName, int type_mask);
extern void maxscaleExecuteImmediate(Parse*, Token* pName, ExprSpan* pExprSpan, int type_mask);
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);
extern void maxscaleLock(Parse*, mxs_lock_t, SrcList*);
extern void maxscalePrepare(Parse*, Token* pName, Token* pStmt);
extern void maxscalePrepare(Parse*, Token* pName, Expr* pStmt);
extern void maxscalePrivileges(Parse*, int kind);
extern void maxscaleRenameTable(Parse*, SrcList* pTables);
extern void maxscaleSet(Parse*, int scope, mxs_set_t kind, ExprList*);
@ -276,33 +280,23 @@ input ::= cmdlist.
cmdlist ::= cmdlist ecmd.
cmdlist ::= ecmd.
ecmd ::= SEMI.
ecmd ::= explain cmdx SEMI.
ecmd ::= explain SEMI.
ecmd ::= cmdx SEMI.
ecmd ::= oracle_assignment 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
@ -315,7 +309,7 @@ cmdx ::= cmd. { sqlite3FinishCoding(pParse); }
%ifdef MAXSCALE
work_opt ::= WORK.
work_opt ::= .
cmd ::= BEGIN work_opt. {mxs_sqlite3BeginTransaction(pParse, 0);} // BEGIN [WORK]
cmd ::= BEGIN work_opt. {mxs_sqlite3BeginTransaction(pParse, TK_BEGIN, 0);} // BEGIN [WORK]
%endif
%ifndef MAXSCALE
cmd ::= BEGIN transtype(Y) trans_opt. {sqlite3BeginTransaction(pParse, Y);}
@ -590,14 +584,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
@ -607,10 +601,10 @@ columnid(A) ::= nm(X). {
NO
OF OFFSET OPEN
QUICK
RAISE RECURSIVE /*REINDEX*/ RELEASE /*RENAME*/ REPLACE RESTRICT ROLLBACK ROLLUP ROW
SAVEPOINT SELECT_OPTIONS_KW SLAVE START STATUS
RAISE RECURSIVE /*REINDEX*/ RELEASE /*RENAME*/ /*REPLACE*/ RESTRICT ROLLBACK ROLLUP ROW
SAVEPOINT SELECT_OPTIONS_KW /*SEQUENCE*/ 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
@ -663,6 +657,10 @@ 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;}
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.
@ -1122,6 +1120,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;}
@ -1131,6 +1130,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;}
@ -1771,13 +1773,18 @@ term(A) ::= DEFAULT(X). {spanExpr(&A, pParse, @X, &X);}
%endif
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) ::= nm(X) DOT nm(Y). {
Expr *temp1 = sqlite3PExpr(pParse, TK_ID, 0, 0, &X);
Expr *temp2 = sqlite3PExpr(pParse, TK_ID, 0, 0, &Y);
A.pExpr = sqlite3PExpr(pParse, TK_DOT, temp1, temp2, 0);
spanSet(&A,&X,&Y);
}
expr(A) ::= DOT nm(X) DOT nm(Y). {
Expr *temp1 = sqlite3PExpr(pParse, TK_ID, 0, 0, &X);
Expr *temp2 = sqlite3PExpr(pParse, TK_ID, 0, 0, &Y);
A.pExpr = sqlite3PExpr(pParse, TK_DOT, temp1, temp2, 0);
spanSet(&A,&X,&Y);
}
expr(A) ::= nm(X) DOT nm(Y) DOT nm(Z). {
Expr *temp1 = sqlite3PExpr(pParse, TK_ID, 0, 0, &X);
Expr *temp2 = sqlite3PExpr(pParse, TK_ID, 0, 0, &Y);
@ -2675,7 +2682,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.
@ -2690,31 +2697,18 @@ default_opt ::= DEFAULT.
//
cmd ::= call.
call_arg ::= INTEGER.
call_arg ::= FLOAT.
call_arg ::= STRING.
call_arg ::= id.
call_arg ::= VARIABLE.
%type call_args_opt {ExprList*}
call_args_opt(A) ::= . {A=0;}
call_args_opt(A) ::= LP exprlist(X) RP. {A=X;}
call_args ::= call_arg.
call_args ::= call_args COMMA call_arg.
call_args_opt ::= .
call_args_opt ::= LP RP.
call_args_opt ::= LP call_args RP.
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 ////////////////////////////////////
//
cmd ::= DROP FUNCTION_KW ifexists nm(X). {
MxsDrop drop;
drop.what = MXS_DROP_FUNCTION;
drop.token = X;
maxscaleDrop(pParse, &drop);
maxscaleDrop(pParse, MXS_DROP_FUNCTION, NULL, &X);
}
//////////////////////// The CHECK TABLE statement ////////////////////////////////////
@ -2845,19 +2839,32 @@ cmd ::= prepare.
cmd ::= execute.
cmd ::= deallocate.
prepare ::= PREPARE nm(X) FROM STRING(Y).
prepare ::= PREPARE nm(X) FROM expr(Y).
{
maxscalePrepare(pParse, &X, &Y);
maxscalePrepare(pParse, &X, Y.pExpr);
}
execute_variables ::= VARIABLE.
execute_variables ::= execute_variables COMMA VARIABLE.
%type execute_variable {int}
execute_variable(A) ::= INTEGER. {A=0;} // For Oracle
execute_variable(A) ::= VARIABLE. {A=QUERY_TYPE_USERVAR_READ;}
execute_variables_opt ::= .
execute_variables_opt ::= USING execute_variables.
%type execute_variables {int}
execute_variables(A) ::= execute_variable(X). {A=X;}
execute_variables(A) ::= execute_variables(X) COMMA execute_variable(Y). {
A = X|Y;
}
execute ::= EXECUTE nm(X) execute_variables_opt. {
maxscaleExecute(pParse, &X);
%type execute_variables_opt {int}
execute_variables_opt(A) ::= . {A=0;}
execute_variables_opt(A) ::= USING execute_variables(X). {A=X;}
execute ::= EXECUTE nm(X) execute_variables_opt(Y). {
maxscaleExecute(pParse, &X, Y);
}
execute ::= EXECUTE id(X) expr(Y) execute_variables_opt(Z). {
maxscaleExecuteImmediate(pParse, &X, &Y, Z);
}
dod ::= DEALLOCATE.
@ -3092,6 +3099,19 @@ show(A) ::= SHOW CREATE VIEW nm(X) dbnm(Y). {
}
}
show(A) ::= SHOW CREATE SEQUENCE nm(X) dbnm(Y). {
A.what = MXS_SHOW_CREATE_SEQUENCE;
A.data = 0;
if (Y.z) {
A.pName = &Y;
A.pDatabase = &X;
}
else {
A.pName = &X;
A.pDatabase = NULL;
}
}
show(A) ::= SHOW DATABASES_KW like_or_where_opt. {
A.what = MXS_SHOW_DATABASES;
A.data = 0;
@ -3219,7 +3239,7 @@ start_transaction_characteristics(A) ::=
}
cmd ::= START TRANSACTION start_transaction_characteristics(X). {
mxs_sqlite3BeginTransaction(pParse, X);
mxs_sqlite3BeginTransaction(pParse, TK_START, X);
}
//////////////////////// The TRUNCATE statement ////////////////////////////////////
@ -3243,4 +3263,55 @@ cmd ::= TRUNCATE table_opt nm(X) dbnm(Y). {
maxscaleTruncate(pParse, pDatabase, pName);
}
//////////////////////// 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);
maxscaleSet(pParse, 0, MXS_SET_VARIABLES, pExprList);
}
//////////////////////// ORACLE CREATE SEQUENCE ////////////////////////////////////
//
cmd ::= CREATE SEQUENCE nm(X) dbnm(Y).{ // CREATE SEQUENCE db
Token* pDatabase;
Token* pTable;
if (Y.z)
{
pDatabase = &X;
pTable = &Y;
}
else
{
pDatabase = NULL;
pTable = &X;
}
maxscaleCreateSequence(pParse, pDatabase, pTable);
}
//////////////////////// ORACLE CREATE SEQUENCE ////////////////////////////////////
//
cmd ::= DROP SEQUENCE nm(X) dbnm(Y).{ // CREATE SEQUENCE db
Token* pDatabase;
Token* pTable;
if (Y.z)
{
pDatabase = &X;
pTable = &Y;
}
else
{
pDatabase = NULL;
pTable = &X;
}
maxscaleDrop(pParse, MXS_DROP_SEQUENCE, pDatabase, pTable);
}
//////////////////////// ORACLE DECLARE ////////////////////////////////////
//
cmd ::= DECLARE. {
maxscaleDeclare(pParse);
}
%endif

View File

@ -4092,14 +4092,9 @@ int sqlite3DbstatRegister(sqlite3*);
typedef enum mxs_drop
{
MXS_DROP_FUNCTION,
MXS_DROP_SEQUENCE,
} mxs_drop_t;
typedef struct MxsDrop
{
mxs_drop_t what;
Token token;
} MxsDrop;
typedef enum mxs_set
{
MXS_SET_VARIABLES,
@ -4109,6 +4104,7 @@ typedef enum mxs_set
typedef enum mxs_show
{
MXS_SHOW_COLUMNS,
MXS_SHOW_CREATE_SEQUENCE,
MXS_SHOW_CREATE_TABLE,
MXS_SHOW_CREATE_VIEW,
MXS_SHOW_DATABASES,

View File

@ -496,6 +496,20 @@ int sqlite3GetToken(const unsigned char *z, int *tokenType){
break;
}else if( c==':' && z[i+1]==':' ){
i++;
#endif
#ifdef MAXSCALE
}else if ( c=='\'' || c=='"' || c=='`' ){
int q=c;
++i;
while ( IdChar(z[i]) ) {
++i;
++n;
}
if ( z[i]==q )
{
++i;
break;
}
#endif
}else{
break;
@ -554,8 +568,20 @@ int sqlite3GetToken(const unsigned char *z, int *tokenType){
}
if (*tokenType != TK_ID) {
extern void maxscaleKeyword(int);
maxscaleKeyword(*tokenType);
extern int maxscaleKeyword(int);
extern int maxscaleTranslateKeyword(int);
*tokenType = maxscaleTranslateKeyword(*tokenType);
if (*tokenType != TK_ID) {
if (maxscaleKeyword(*tokenType) != 0)
{
/* Consume the entire string. */
while ( z[i] ) {
++i;
}
}
}
}
}
return i;

View File

@ -218,6 +218,7 @@ static Keyword aKeywordTable[] = {
#ifdef MAXSCALE
{ "DATABASES", "TK_DATABASES_KW", ALWAYS },
{ "DEALLOCATE", "TK_DEALLOCATE", ALWAYS },
{ "DECLARE", "TK_DECLARE", ALWAYS },
#endif
{ "DEFAULT", "TK_DEFAULT", ALWAYS },
{ "DEFERRED", "TK_DEFERRED", ALWAYS },
@ -268,6 +269,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
@ -396,6 +400,7 @@ static Keyword aKeywordTable[] = {
{ "SAVEPOINT", "TK_SAVEPOINT", ALWAYS },
#ifdef MAXSCALE
{ "SCHEMAS", "TK_DATABASES_KW", ALWAYS },
{ "SEQUENCE", "TK_SEQUENCE", ALWAYS },
#endif
{ "SELECT", "TK_SELECT", ALWAYS },
#ifdef MAXSCALE

View File

@ -1,5 +1,7 @@
# Include the embedded library headers
if (BUILD_QC_MYSQLEMBEDDED)
find_package(MySQL)
subdirs(MYSQL_INCLUDE_DIR_ALL ${MYSQL_EMBEDDED_INCLUDE_DIR})
foreach(DIR ${MYSQL_INCLUDE_DIR_ALL})
include_directories(${DIR})
@ -44,6 +46,42 @@ if (BUILD_QC_MYSQLEMBEDDED)
add_test(TestQC_CompareWhiteSpace compare -v 2 -S -s "select user from mysql.user; ")
add_test(TestQC_version_sensitivity version_sensitivity)
if(NOT (MYSQL_EMBEDDED_VERSION VERSION_LESS 10.3))
add_test(TestQC_Oracle-binlog_stm_ps compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/oracle/binlog_stm_ps.test)
add_test(TestQC_Oracle-binlog_stm_sp compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/oracle/binlog_stm_sp.test)
add_test(TestQC_Oracle-exception compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/oracle/exception.test)
add_test(TestQC_Oracle-func_case compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/oracle/func_case.test)
add_test(TestQC_Oracle-func_concat compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/oracle/func_concat.test)
add_test(TestQC_Oracle-func_decode compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/oracle/func_decode.test)
add_test(TestQC_Oracle-func_length compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/oracle/func_length.test)
add_test(TestQC_Oracle-func_misc compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/oracle/func_misc.test)
add_test(TestQC_Oracle-misc compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/oracle/misc.test)
add_test(TestQC_Oracle-ps compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/oracle/ps.test)
add_test(TestQC_Oracle-sequence compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/oracle/sequence.test)
add_test(TestQC_Oracle-sp-anonymous compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/oracle/sp-anonymous.test)
add_test(TestQC_Oracle-sp-code compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/oracle/sp-code.test)
add_test(TestQC_Oracle-sp-cursor-decl compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/oracle/sp-cursor-decl.test)
add_test(TestQC_Oracle-sp-cursor-rowtype compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/oracle/sp-cursor-rowtype.test)
add_test(TestQC_Oracle-sp-cursor compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/oracle/sp-cursor.test)
add_test(TestQC_Oracle-sp-goto compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/oracle/sp-goto.test)
add_test(TestQC_Oracle-sp-param_inc compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/oracle/sp-param.inc)
add_test(TestQC_Oracle-sp-param compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/oracle/sp-param.test)
add_test(TestQC_Oracle-sp-row compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/oracle/sp-row.test)
add_test(TestQC_Oracle-sp-row-vs-var_inc compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/oracle/sp-row-vs-var.inc)
add_test(TestQC_Oracle-sp-security compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/oracle/sp-security.test)
add_test(TestQC_Oracle-sp compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/oracle/sp.test)
add_test(TestQC_Oracle-trigger compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/oracle/trigger.test)
add_test(TestQC_Oracle-truncate compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/oracle/truncate.test)
add_test(TestQC_Oracle-type_blob compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/oracle/type_blob.test)
add_test(TestQC_Oracle-type_clob compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/oracle/type_clob.test)
add_test(TestQC_Oracle-type_date compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/oracle/type_date.test)
add_test(TestQC_Oracle-type_number compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/oracle/type_number.test)
add_test(TestQC_Oracle-type_raw compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/oracle/type_raw.test)
add_test(TestQC_Oracle-type_varchar compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/oracle/type_varchar.test)
add_test(TestQC_Oracle-type_varchar2 compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/oracle/type_varchar2.test)
add_test(TestQC_Oracle-type_variables compare -v 2 ${CMAKE_CURRENT_SOURCE_DIR}/oracle/variables.test)
endif()
endif()
add_subdirectory(canonical_tests)

View File

@ -46,7 +46,7 @@ int main(int argc, char** argv)
set_langdir(strdup("."));
set_process_datadir(strdup("/tmp"));
qc_setup("qc_sqlite", NULL);
qc_setup("qc_sqlite", QC_SQL_MODE_DEFAULT, NULL);
qc_process_init(QC_INIT_BOTH);
infile = fopen(argv[1], "rb");

View File

@ -313,7 +313,7 @@ int main(int argc, char** argv)
if (mxs_log_init(NULL, ".", MXS_LOG_TARGET_DEFAULT))
{
if (qc_setup(lib, NULL) && qc_process_init(QC_INIT_BOTH))
if (qc_setup(lib, QC_SQL_MODE_DEFAULT, NULL) && qc_process_init(QC_INIT_BOTH))
{
rc = run(input_name, expected_name);
qc_process_end(QC_INIT_BOTH);

View File

@ -20,6 +20,8 @@
#include <set>
#include <string>
#include <sstream>
#include <my_config.h>
#define MYSQL_COM_QUERY COM_QUERY
#define MYSQL_COM_QUIT COM_QUIT
#define MYSQL_COM_INIT_DB COM_INIT_DB
#define MYSQL_COM_CHANGE_USER COM_CHANGE_USER
@ -27,6 +29,7 @@
#include <maxscale/log_manager.h>
#include <maxscale/protocol/mysql.h>
#include <maxscale/query_classifier.h>
#include "../../server/modules/protocol/MySQL/MySQLClient/setsqlmodeparser.hh"
#include "testreader.hh"
using std::cerr;
using std::cin;
@ -39,20 +42,29 @@ 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
{
char USAGE[] =
"usage: compare [-r count] [-d] [-1 classfier1] [-2 classifier2] "
"[-A args] [-B args] [-v [0..2]] [-s statement]|[file]]\n\n"
"[-A args] [-B args] [-C args] [-m [default|oracle]] [-v [0..2]] [-s statement]|[file]]\n\n"
"-r redo the test the specified number of times; 0 means forever, default is 1\n"
"-d don't stop after first failed query\n"
"-1 the first classifier, default qc_mysqlembedded\n"
"-2 the second classifier, default qc_sqlite\n"
"-1 the first classifier, default 'qc_mysqlembedded'\n"
"-2 the second classifier, default 'qc_sqlite'\n"
"-A arguments for the first classifier\n"
"-B arguments for the second classifier\n"
"-C arguments for both classifiers\n"
"-m initial sql mode, 'default' or 'oracle', default is 'default'\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 +87,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 +100,7 @@ struct State
false, // result_printed
true, // stop_at_error
false, // strict
false, // strict reporting
0, // line
0, // n_statements
0, // n_errors
@ -160,13 +174,13 @@ QUERY_CLASSIFIER* load_classifier(const char* name)
return pClassifier;
}
QUERY_CLASSIFIER* get_classifier(const char* zName, const char* zArgs)
QUERY_CLASSIFIER* get_classifier(const char* zName, qc_sql_mode_t sql_mode, const char* zArgs)
{
QUERY_CLASSIFIER* pClassifier = load_classifier(zName);
if (pClassifier)
{
if ((pClassifier->qc_setup(zArgs) != QC_RESULT_OK) ||
if ((pClassifier->qc_setup(sql_mode, zArgs) != QC_RESULT_OK) ||
((pClassifier->qc_process_init() != QC_RESULT_OK)))
{
cerr << "error: Could not setup or init classifier " << zName << "." << endl;
@ -187,16 +201,17 @@ void put_classifier(QUERY_CLASSIFIER* pClassifier)
}
}
bool get_classifiers(const char* zName1, const char* zArgs1, QUERY_CLASSIFIER** ppClassifier1,
bool get_classifiers(qc_sql_mode_t sql_mode,
const char* zName1, const char* zArgs1, QUERY_CLASSIFIER** ppClassifier1,
const char* zName2, const char* zArgs2, QUERY_CLASSIFIER** ppClassifier2)
{
bool rc = false;
QUERY_CLASSIFIER* pClassifier1 = get_classifier(zName1, zArgs1);
QUERY_CLASSIFIER* pClassifier1 = get_classifier(zName1, sql_mode, zArgs1);
if (pClassifier1)
{
QUERY_CLASSIFIER* pClassifier2 = get_classifier(zName2, zArgs2);
QUERY_CLASSIFIER* pClassifier2 = get_classifier(zName2, sql_mode, zArgs2);
if (pClassifier2)
{
@ -339,7 +354,10 @@ bool compare_parse(QUERY_CLASSIFIER* pClassifier1, GWBUF* pCopy1,
else
{
ss << "INF: ";
success = true;
if (!global.strict_reporting)
{
success = true;
}
}
ss << static_cast<qc_parse_result_t>(rv1) << " != " << static_cast<qc_parse_result_t>(rv2);
@ -1221,6 +1239,33 @@ bool compare(QUERY_CLASSIFIER* pClassifier1, QUERY_CLASSIFIER* pClassifier2, con
bool success = compare(pClassifier1, pCopy1, pClassifier2, pCopy2);
if (success)
{
SetSqlModeParser::sql_mode_t sql_mode;
SetSqlModeParser parser;
if (parser.get_sql_mode(&pCopy1, &sql_mode) == SetSqlModeParser::IS_SET_SQL_MODE)
{
switch (sql_mode)
{
case SetSqlModeParser::DEFAULT:
pClassifier1->qc_set_sql_mode(QC_SQL_MODE_DEFAULT);
pClassifier2->qc_set_sql_mode(QC_SQL_MODE_DEFAULT);
break;
case SetSqlModeParser::ORACLE:
pClassifier1->qc_set_sql_mode(QC_SQL_MODE_ORACLE);
pClassifier2->qc_set_sql_mode(QC_SQL_MODE_ORACLE);
break;
default:
ss_dassert(!true);
case SetSqlModeParser::SOMETHING:
break;
};
}
}
gwbuf_free(pCopy1);
gwbuf_free(pCopy2);
@ -1302,6 +1347,15 @@ int run(QUERY_CLASSIFIER* pClassifier1, QUERY_CLASSIFIER* pClassifier2, const st
return global.n_errors == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
}
void append_arg(string& args, const string& arg)
{
if (!args.empty())
{
args += ",";
}
args += arg;
}
}
int main(int argc, char* argv[])
@ -1310,14 +1364,19 @@ int main(int argc, char* argv[])
const char* zClassifier1 = "qc_mysqlembedded";
const char* zClassifier2 = "qc_sqlite";
const char* zClassifier1Args = NULL;
const char* zClassifier2Args = "log_unrecognized_statements=1";
string classifier1Args;
#if defined(USING_MARIADB_103)
string classifier2Args("parse_as=10.3,log_unrecognized_statements=1");
#else
string classifier2Args("log_unrecognized_statements=1");
#endif
const char* zStatement = NULL;
qc_sql_mode_t sql_mode = QC_SQL_MODE_DEFAULT;
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:C:m:s:SR")) != -1)
{
switch (c)
{
@ -1338,11 +1397,16 @@ int main(int argc, char* argv[])
break;
case 'A':
zClassifier1Args = optarg;
append_arg(classifier1Args, optarg);
break;
case 'B':
zClassifier2Args = optarg;
append_arg(classifier2Args, optarg);
break;
case 'C':
append_arg(classifier1Args, optarg);
append_arg(classifier2Args, optarg);
break;
case 'd':
@ -1353,10 +1417,30 @@ int main(int argc, char* argv[])
zStatement = optarg;
break;
case 'm':
if (strcasecmp(optarg, "default") == 0)
{
sql_mode = QC_SQL_MODE_DEFAULT;
}
else if (strcasecmp(optarg, "oracle") == 0)
{
sql_mode = QC_SQL_MODE_ORACLE;
}
else
{
rc = EXIT_FAILURE;
break;
}
break;
case 'S':
global.strict = true;
break;
case 'R':
global.strict_reporting = true;
break;
default:
rc = EXIT_FAILURE;
break;
@ -1378,10 +1462,14 @@ int main(int argc, char* argv[])
if (mxs_log_init(NULL, ".", MXS_LOG_TARGET_DEFAULT))
{
const char* zClassifier1Args = classifier1Args.c_str();
const char* zClassifier2Args = classifier2Args.c_str();
QUERY_CLASSIFIER* pClassifier1;
QUERY_CLASSIFIER* pClassifier2;
if (get_classifiers(zClassifier1, zClassifier1Args, &pClassifier1,
if (get_classifiers(sql_mode,
zClassifier1, zClassifier1Args, &pClassifier1,
zClassifier2, zClassifier2Args, &pClassifier2))
{
size_t round = 0;

View File

@ -41,7 +41,7 @@ int main()
set_libdir(strdup("../qc_sqlite"));
if (qc_setup("qc_sqlite", NULL) && qc_process_init(QC_INIT_BOTH))
if (qc_setup("qc_sqlite", QC_SQL_MODE_DEFAULT, NULL) && qc_process_init(QC_INIT_BOTH))
{
const char s[] = "SELECT @@global.max_allowed_packet";

View File

@ -0,0 +1,11 @@
#
# This file contains PL/SQL statements that in some way are not handled
# completely or correctly.
#
PREPARE stmt FROM 'SELECT 1 AS a FROM ' || @table_name;
# qc_sqlite parses this correctly. However, currently there is no way
# that the "'SELECT 1 AS a FROM ' || @table_name" can be expressed as
# a statement that separately can be analyzed. Consequently, statements
# like this will not pass through the database firewall filter.

View File

@ -0,0 +1,37 @@
--source include/not_embedded.inc
--source include/have_binlog_format_statement.inc
--disable_query_log
#qc_sqlite: 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;

View File

@ -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");
#qc_sqlite: 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;

View File

@ -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;

View File

@ -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');

View File

@ -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;

View File

@ -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;

View File

@ -0,0 +1,20 @@
SET sql_mode=ORACLE;
--echo #
--echo # MDEV-12783 sql_mode=ORACLE: Functions LENGTH() and LENGTHB()
--echo #
#
# Testing LENGTH / LENGTHB
#
# LENGTH : return the length of char
# LENGTHB : return the length of byte
SELECT LENGTH(null), LENGTH('a'), LENGTH(123);
SELECT LENGTHB(null), LENGTHB('a'), LENGTHB(123);
# qc_sqlite: SELECT LENGTH(_utf8 0xC39F), LENGTH(CHAR(14844588 USING utf8));
# Sqlite3 error: SQL logic error or missing database, near "0xC39F": syntax error
# qc_sqlite: SELECT LENGTHB(_utf8 0xC39F), LENGTHB(CHAR(14844588 USING utf8));
# Sqlite3 error: SQL logic error or missing database, near "0xC39F": syntax error
EXPLAIN EXTENDED SELECT LENGTH('a'), LENGTHB('a');

View File

@ -0,0 +1,346 @@
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 #
--echo #
--echo # MDEV-12854 Synchronize CREATE..SELECT data type and result set metadata data type for INT functions
--echo #
--enable_metadata
--disable_ps_protocol
DELIMITER $$;
BEGIN
SELECT SQLCODE;
END
$$
DELIMITER ;$$
--enable_ps_protocol
--disable_metadata

View File

@ -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;

View File

@ -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;
#qc_mysqlembedded: PREPARE stmt FROM 'SELECT :"a",:"b"';
EXECUTE stmt USING @a, @b;
#qc_mysqlembedded: 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;
#qc: 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 #

View File

@ -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;

View File

@ -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
<<label>>
WHILE i > 3 LOOP
i:= i - 1;
SELECT i;
END LOOP label;
END;
END
/
DELIMITER ;/
--echo # Anonymous blocks with no declarations and no exceptions
DELIMITER $$;
BEGIN
SELECT 1 AS a;
END
$$
DELIMITER ;$$
SET AUTOCOMMIT=OFF;
CREATE TABLE t1 (a INT) ENGINE=InnoDB;
INSERT INTO t1 VALUES (10);
DELIMITER $$;
BEGIN
INSERT INTO t1 VALUES(20);
INSERT INTO t1 VALUES(30);
ROLLBACK;
END;
$$
DELIMITER ;$$
SELECT * FROM t1;
DROP TABLE t1;
SET AUTOCOMMIT=DEFAULT;
SET AUTOCOMMIT=OFF;
CREATE TABLE t1 (a INT) ENGINE=InnoDB;
INSERT INTO t1 VALUES (10);
DELIMITER $$;
BEGIN
INSERT INTO t1 VALUES(20);
INSERT INTO t1 VALUES(30);
END;
$$
DELIMITER ;$$
ROLLBACK;
SELECT * FROM t1;
DROP TABLE t1;
SET AUTOCOMMIT=DEFAULT;
SET AUTOCOMMIT=OFF;
CREATE TABLE t1 (a INT) ENGINE=InnoDB;
INSERT INTO t1 VALUES (10);
DELIMITER $$;
BEGIN
INSERT INTO t1 VALUES(20);
INSERT INTO t1 VALUES(30);
COMMIT;
END;
$$
DELIMITER ;$$
SELECT * FROM t1;
DROP TABLE t1;
SET AUTOCOMMIT=DEFAULT;
SET AUTOCOMMIT=OFF;
CREATE TABLE t1 (a INT) ENGINE=InnoDB;
INSERT INTO t1 VALUES (10);
DELIMITER $$;
BEGIN
INSERT INTO t1 VALUES(20);
INSERT INTO t1 VALUES(30);
END;
$$
DELIMITER ;$$
COMMIT;
SELECT * FROM t1;
DROP TABLE t1;
SET AUTOCOMMIT=DEFAULT;
SET AUTOCOMMIT=OFF;
CREATE TABLE t1 (a INT NOT NULL PRIMARY KEY) ENGINE=InnoDB;
INSERT INTO t1 VALUES (10);
DELIMITER $$;
--error ER_DUP_ENTRY
BEGIN
INSERT INTO t1 VALUES(20);
INSERT INTO t1 VALUES(20);
END;
$$
DELIMITER ;$$
COMMIT;
SELECT * FROM t1;
DROP TABLE t1;
SET AUTOCOMMIT=DEFAULT;
--echo # Anonymous blocks with no declarations, with exceptions
SET AUTOCOMMIT=OFF;
CREATE TABLE t1 (a INT NOT NULL PRIMARY KEY) ENGINE=InnoDB;
INSERT INTO t1 VALUES (10);
DELIMITER $$;
BEGIN
INSERT INTO t1 VALUES(20);
INSERT INTO t1 VALUES(20);
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN NULL;
END;
$$
DELIMITER ;$$
COMMIT;
SELECT * FROM t1;
DROP TABLE t1;
SET AUTOCOMMIT=DEFAULT;
--echo # Anonymous blocks with declarations, with no exceptions
SET AUTOCOMMIT=OFF;
CREATE TABLE t1 (a INT) ENGINE=InnoDB;
INSERT INTO t1 VALUES (10);
DELIMITER $$;
DECLARE
a20 INT:=20;
a30 INT:=30;
BEGIN
INSERT INTO t1 VALUES(a20);
INSERT INTO t1 VALUES(a30);
ROLLBACK;
END;
$$
DELIMITER ;$$
SELECT * FROM t1;
DROP TABLE t1;
SET AUTOCOMMIT=DEFAULT;
SET AUTOCOMMIT=OFF;
CREATE TABLE t1 (a INT) ENGINE=InnoDB;
INSERT INTO t1 VALUES (10);
DELIMITER $$;
DECLARE
a20 INT:=20;
a30 INT:=30;
BEGIN
INSERT INTO t1 VALUES(a20);
INSERT INTO t1 VALUES(a30);
END;
$$
DELIMITER ;$$
ROLLBACK;
SELECT * FROM t1;
DROP TABLE t1;
SET AUTOCOMMIT=DEFAULT;
SET AUTOCOMMIT=OFF;
CREATE TABLE t1 (a INT) ENGINE=InnoDB;
INSERT INTO t1 VALUES (10);
DELIMITER $$;
DECLARE
a20 INT:=20;
a30 INT:=30;
BEGIN
INSERT INTO t1 VALUES(a20);
INSERT INTO t1 VALUES(a30);
COMMIT;
END;
$$
DELIMITER ;$$
SELECT * FROM t1;
DROP TABLE t1;
SET AUTOCOMMIT=DEFAULT;
SET AUTOCOMMIT=OFF;
CREATE TABLE t1 (a INT) ENGINE=InnoDB;
INSERT INTO t1 VALUES (10);
DELIMITER $$;
DECLARE
a20 INT:=20;
a30 INT:=30;
BEGIN
INSERT INTO t1 VALUES(a20);
INSERT INTO t1 VALUES(a30);
END;
$$
DELIMITER ;$$
COMMIT;
SELECT * FROM t1;
DROP TABLE t1;
SET AUTOCOMMIT=DEFAULT;
--echo # Anonymous blocks with declarations, with exceptions
SET AUTOCOMMIT=OFF;
CREATE TABLE t1 (a INT NOT NULL PRIMARY KEY) ENGINE=InnoDB;
INSERT INTO t1 VALUES (10);
DELIMITER $$;
DECLARE
a20 INT:=20;
BEGIN
INSERT INTO t1 VALUES(a20);
INSERT INTO t1 VALUES(a20);
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN NULL;
END;
$$
DELIMITER ;$$
COMMIT;
SELECT * FROM t1;
DROP TABLE t1;
SET AUTOCOMMIT=DEFAULT;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,295 @@
SET sql_mode=ORACLE;
--echo #
--echo # MDEV-10598 sql_mode=ORACLE: Variable declarations can go after cursor declarations
--echo #
--echo #
--echo # Variable after cursor declaration
--echo #
CREATE TABLE t1 (a INT);
insert into t1 values (1);
insert into t1 values (2);
DELIMITER $$;
CREATE PROCEDURE p1
AS
CURSOR c IS SELECT a FROM t1;
var1 varchar(10);
BEGIN
OPEN c;
fetch c into var1;
SELECT c%ROWCOUNT,var1;
close c;
END;
$$
DELIMITER ;$$
CALL p1;
DROP PROCEDURE p1;
drop table t1;
--echo #
--echo # Variable after condition declaration
--echo #
CREATE TABLE t1 (col1 INT);
insert into t1 values (1);
create unique index t1_col1 on t1 (col1);
DELIMITER $$;
CREATE PROCEDURE p1
AS
dup_key CONDITION FOR SQLSTATE '23000';
var1 varchar(40);
CONTINUE HANDLER FOR dup_key
BEGIN
var1:='duplicate key in index';
END;
BEGIN
var1:='';
insert into t1 values (1);
select var1;
END;
$$
DELIMITER ;$$
CALL p1;
DROP PROCEDURE p1;
drop table t1;
--echo #
--echo # Condition after cursor declaration
--echo #
CREATE TABLE t1 (col1 INT);
insert into t1 values (1);
create unique index t1_col1 on t1 (col1);
DELIMITER $$;
CREATE PROCEDURE p1
AS
var1 varchar(40);
var2 integer;
CURSOR c IS SELECT col1 FROM t1;
dup_key CONDITION FOR SQLSTATE '23000';
CONTINUE HANDLER FOR dup_key
BEGIN
var1:='duplicate key in index';
END;
BEGIN
var1:='';
insert into t1 values (1);
SELECT var1;
END;
$$
DELIMITER ;$$
CALL p1;
DROP PROCEDURE p1;
drop table t1;
--echo #
--echo # Cursor after handler declaration
--echo #
CREATE TABLE t1 (col1 INT);
insert into t1 values (1);
create unique index t1_col1 on t1 (col1);
DELIMITER $$;
--error ER_PARSE_ERROR
CREATE PROCEDURE p1
AS
var1 varchar(40);
var2 integer;
dup_key CONDITION FOR SQLSTATE '23000';
CONTINUE HANDLER FOR dup_key
BEGIN
var1:='duplicate key in index';
END;
CURSOR c IS SELECT col1 FROM t1;
BEGIN
var1:='';
insert into t1 values (1);
SELECT var1;
END;
$$
DELIMITER ;$$
drop table t1;
--echo #
--echo # Condition after handler declaration
--echo #
CREATE TABLE t1 (col1 INT);
insert into t1 values (1);
create unique index t1_col1 on t1 (col1);
DELIMITER $$;
--error ER_PARSE_ERROR
CREATE PROCEDURE p1
AS
var1 varchar(40);
var2 integer;
dup_key CONDITION FOR SQLSTATE '23000';
CURSOR c IS SELECT col1 FROM t1;
CONTINUE HANDLER FOR dup_key
BEGIN
var1:='duplicate key in index';
END;
divide_by_zero CONDITION FOR SQLSTATE '22012';
BEGIN
var1:='';
insert into t1 values (1);
SELECT var1;
END;
$$
DELIMITER ;$$
drop table t1;
--echo #
--echo # Variable after handler declaration
--echo #
CREATE TABLE t1 (col1 INT);
insert into t1 values (1);
create unique index t1_col1 on t1 (col1);
DELIMITER $$;
--error ER_PARSE_ERROR
CREATE PROCEDURE p1
AS
var1 varchar(40);
var2 integer;
dup_key CONDITION FOR SQLSTATE '23000';
CURSOR c IS SELECT col1 FROM t1;
CONTINUE HANDLER FOR dup_key
BEGIN
var1:='duplicate key in index';
END;
divide_by_zero CONDITION FOR SQLSTATE '22012';
BEGIN
var1:='';
insert into t1 values (1);
SELECT var1;
END;
$$
DELIMITER ;$$
drop table t1;
--echo #
--echo # Variable after cursor (inner block)
--echo #
CREATE TABLE t1 (col1 INT);
insert into t1 values (1);
insert into t1 values (2);
create unique index t1_col1 on t1 (col1);
DELIMITER $$;
CREATE PROCEDURE p1
AS
CURSOR c IS SELECT col1 FROM t1;
var1 varchar(40);
BEGIN
OPEN c;
begin
declare
CURSOR c IS SELECT col1 FROM t1 where col1=2;
var2 integer;
dup_key CONDITION FOR SQLSTATE '23000';
CONTINUE HANDLER FOR dup_key
BEGIN
var1:='duplicate key in index';
END;
begin
OPEN c;
fetch c into var1;
SELECT 'inner cursor',var1;
insert into t1 values (2);
close c;
end;
end;
SELECT var1;
fetch c into var1;
SELECT c%ROWCOUNT,var1;
begin
insert into t1 values (2);
exception when 1062 then
begin
SELECT 'dup key caugth';
end;
end;
close c;
END;
$$
DELIMITER ;$$
CALL p1;
DROP PROCEDURE p1;
drop table t1;
--echo #
--echo # Cursor declaration and row type declaration in same block
--echo #
CREATE TABLE t1 (a INT, b VARCHAR(10));
insert into t1 values(1,'a');
delimiter $$;
CREATE PROCEDURE p1()
AS
CURSOR cur1 IS SELECT a FROM t1;
rec1 cur1%ROWTYPE;
BEGIN
rec1.a:= 10;
END;
$$
delimiter ;$$
call p1;
DROP PROCEDURE p1;
drop table t1;
--echo #
--echo # Recursive cursor and cursor%ROWTYPE declarations in the same block
--echo #
delimiter $$;
CREATE PROCEDURE p1
AS
a INT:=10;
b VARCHAR(10):='b0';
c DOUBLE:=0.1;
CURSOR cur1 IS SELECT a, b, c;
rec1 cur1%ROWTYPE;
CURSOR cur2 IS SELECT rec1.a + 1 "a", rec1.b||'0' AS b, rec1.c AS c;
rec2 cur2%ROWTYPE;
BEGIN
OPEN cur1;
FETCH cur1 INTO rec1;
CLOSE cur1;
SELECT rec1.a;
OPEN cur2;
FETCH cur2 INTO rec2;
CLOSE cur2;
SELECT rec2.a;
CREATE TABLE t2 AS SELECT rec2.a AS a, rec2.b AS b, rec2.c AS c;
SHOW CREATE TABLE t2;
DROP TABLE t2;
END;
$$
DELIMITER ;$$
CALL p1();
DROP PROCEDURE p1;
--echo #
--echo # MDEV-12916 Wrong column data type for an INT field of a cursor-anchored ROW variable
--echo #
DELIMITER $$;
CREATE PROCEDURE p1
AS
a INT DEFAULT 10;
CURSOR cur1 IS SELECT a;
rec1 cur1%ROWTYPE;
BEGIN
CREATE TABLE t1 AS SELECT rec1.a;
SHOW CREATE TABLE t1;
DROP TABLE t1;
END;
$$
DELIMITER ;$$
CALL p1();
DROP PROCEDURE p1;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,954 @@
SET sql_mode=ORACLE;
--echo #
--echo # MDEV-10582 sql_mode=ORACLE: explicit cursor attributes %ISOPEN, %ROWCOUNT, %FOUND, %NOTFOUND
--echo #
--echo #
--echo # Cursor attributes outside of an SP context
--echo #
--error ER_SP_CURSOR_MISMATCH
SELECT c%ISOPEN;
--error ER_SP_CURSOR_MISMATCH
SELECT c%FOUND;
--error ER_SP_CURSOR_MISMATCH
SELECT c%NOTFOUND;
--error ER_SP_CURSOR_MISMATCH
SELECT c%ROWCOUNT;
--echo #
--echo # Undefinite cursor attributes
--echo #
DELIMITER $$;
--error ER_SP_CURSOR_MISMATCH
CREATE PROCEDURE p1
AS
BEGIN
SELECT c%ISOPEN;
END;
$$
--error ER_SP_CURSOR_MISMATCH
CREATE PROCEDURE p1
AS
BEGIN
SELECT c%ROWCOUNT;
END;
$$
--error ER_SP_CURSOR_MISMATCH
CREATE PROCEDURE p1
AS
BEGIN
SELECT c%FOUND;
END;
$$
--error ER_SP_CURSOR_MISMATCH
CREATE PROCEDURE p1
AS
BEGIN
SELECT c%NOTFOUND;
END;
$$
DELIMITER ;$$
--echo #
--echo # Not opened cursor attributes %FOUND, %NOTFOUND, %ROWCOUNT
--echo #
DELIMITER $$;
CREATE PROCEDURE p1
AS
CURSOR c IS SELECT 1 AS c FROM DUAL;
BEGIN
SELECT c%ROWCOUNT;
END;
$$
DELIMITER ;$$
--error ER_SP_CURSOR_NOT_OPEN
CALL p1;
DROP PROCEDURE p1;
DELIMITER $$;
CREATE PROCEDURE p1
AS
CURSOR c IS SELECT 1 AS c FROM DUAL;
BEGIN
SELECT c%FOUND;
END;
$$
DELIMITER ;$$
--error ER_SP_CURSOR_NOT_OPEN
CALL p1;
DROP PROCEDURE p1;
DELIMITER $$;
CREATE PROCEDURE p1
AS
CURSOR c IS SELECT 1 AS c FROM DUAL;
BEGIN
SELECT c%NOTFOUND;
END;
$$
DELIMITER ;$$
--error ER_SP_CURSOR_NOT_OPEN
CALL p1;
DROP PROCEDURE p1;
--echo #
--echo # Not opened cursor attributes %FOUND, %NOTFOUND, %ROWCOUNT with INVALID_CURSOR exception
--echo #
DELIMITER $$;
CREATE PROCEDURE p1
AS
CURSOR c IS SELECT 1 AS c FROM DUAL;
BEGIN
SELECT c%ROWCOUNT;
EXCEPTION
WHEN INVALID_CURSOR THEN SELECT 'INVALID_CURSOR caught' AS msg;
END;
$$
DELIMITER ;$$
CALL p1;
DROP PROCEDURE p1;
DELIMITER $$;
CREATE PROCEDURE p1
AS
CURSOR c IS SELECT 1 AS c FROM DUAL;
BEGIN
SELECT c%FOUND;
EXCEPTION
WHEN INVALID_CURSOR THEN SELECT 'INVALID_CURSOR caught' AS msg;
END;
$$
DELIMITER ;$$
CALL p1;
DROP PROCEDURE p1;
DELIMITER $$;
CREATE PROCEDURE p1
AS
CURSOR c IS SELECT 1 AS c FROM DUAL;
BEGIN
SELECT c%NOTFOUND;
EXCEPTION
WHEN INVALID_CURSOR THEN SELECT 'INVALID_CURSOR caught' AS msg;
END;
$$
DELIMITER ;$$
CALL p1;
DROP PROCEDURE p1;
--echo #
--echo # print()
--echo #
CREATE TABLE t1 (a INT);
DELIMITER $$;
CREATE PROCEDURE p1
AS
CURSOR c IS SELECT * FROM t1 ORDER BY a;
BEGIN
EXPLAIN EXTENDED SELECT c%ISOPEN, c%ROWCOUNT, c%FOUND, c%NOTFOUND;
END;
$$
DELIMITER ;$$
CALL p1();
DROP PROCEDURE p1;
DROP TABLE t1;
--echo #
--echo # Declared data type of the attributes
--echo #
CREATE TABLE t1 (a INT);
INSERT INTO t1 VALUES (10);
DELIMITER $$;
CREATE PROCEDURE p1
AS
CURSOR c IS SELECT * FROM t1 ORDER BY a;
BEGIN
OPEN c;
CREATE TABLE t2 AS SELECT c%ISOPEN, c%ROWCOUNT, c%FOUND, c%NOTFOUND;
SHOW CREATE TABLE t2;
DROP TABLE t2;
CLOSE c;
END;
$$
DELIMITER ;$$
CALL p1();
DROP PROCEDURE p1;
DROP TABLE t1;
--echo #
--echo # Core functionality
--echo #
CREATE TABLE t1 (a INT);
INSERT INTO t1 VALUES (10);
INSERT INTO t1 VALUES (20);
INSERT INTO t1 VALUES (30);
DELIMITER $$;
CREATE PROCEDURE p1
AS
a INT:=0;
CURSOR c IS SELECT * FROM t1 ORDER BY a;
BEGIN
SELECT a, c%ISOPEN;
OPEN c;
/*
After OPEN and before FETCH:
- %ROWCOUNT returns 0
- %FOUND and %NOTFOUND return NULL
*/
SELECT a, c%ISOPEN, c%ROWCOUNT, c%FOUND, c%NOTFOUND;
FETCH c INTO a;
SELECT a, c%ISOPEN, c%ROWCOUNT, c%FOUND, c%NOTFOUND;
FETCH c INTO a;
SELECT a, c%ISOPEN, c%ROWCOUNT, c%FOUND, c%NOTFOUND;
FETCH c INTO a;
SELECT a, c%ISOPEN, c%ROWCOUNT, c%FOUND, c%NOTFOUND;
FETCH c INTO a;
SELECT a, c%ISOPEN, c%ROWCOUNT, c%FOUND, c%NOTFOUND;
CLOSE c;
SELECT a, c%ISOPEN;
/*
After reopen and before FETCH:
- %ROWCOUNT returns 0
- %FOUND and %NOTFOUND return NULL
*/
OPEN c;
SELECT a, c%ISOPEN, c%ROWCOUNT, c%FOUND, c%NOTFOUND;
FETCH c INTO a;
SELECT a, c%ISOPEN, c%ROWCOUNT, c%FOUND, c%NOTFOUND;
CLOSE c;
END;
$$
DELIMITER ;$$
CALL p1();
DROP PROCEDURE p1;
DROP TABLE t1;
--echo #
--echo # %NOTFOUND as a loop exit condition
--echo #
CREATE TABLE t1 (a INT);
INSERT INTO t1 VALUES (10);
INSERT INTO t1 VALUES (20);
INSERT INTO t1 VALUES (30);
DELIMITER $$;
CREATE PROCEDURE p1
AS
a INT:=0;
CURSOR c IS SELECT * FROM t1 ORDER BY a;
BEGIN
OPEN c;
LOOP
FETCH c INTO a;
EXIT WHEN c%NOTFOUND;
SELECT a;
END LOOP;
CLOSE c;
END;
$$
DELIMITER ;$$
CALL p1();
DROP PROCEDURE p1;
DROP TABLE t1;
--echo #
--echo # %FOUND as a loop exit condition
--echo #
CREATE TABLE t1 (a INT);
INSERT INTO t1 VALUES (10);
INSERT INTO t1 VALUES (20);
INSERT INTO t1 VALUES (30);
DELIMITER $$;
CREATE PROCEDURE p1
AS
a INT:=0;
CURSOR c IS SELECT * FROM t1 ORDER BY a;
BEGIN
OPEN c;
LOOP
FETCH c INTO a;
EXIT WHEN NOT c%FOUND;
SELECT a;
END LOOP;
CLOSE c;
END;
$$
DELIMITER ;$$
CALL p1();
DROP PROCEDURE p1;
DROP TABLE t1;
--echo #
--echo # End of MDEV-10582 sql_mode=ORACLE: explicit cursor attributes %ISOPEN, %ROWCOUNT, %FOUND, %NOTFOUND
--echo #
--echo #
--echo # MDEV-10597 Cursors with parameters
--echo #
--echo #
--echo # OPEN with a wrong number of parameters
--echo #
CREATE TABLE t1 (a INT, b VARCHAR(10));
DELIMITER $$;
--error ER_WRONG_PARAMCOUNT_TO_CURSOR
CREATE PROCEDURE p1(a_a INT,a_b VARCHAR)
AS
v_a INT;
v_b VARCHAR(10);
CURSOR c (p_a INT, p_b VARCHAR) IS SELECT * FROM t1 WHERE a=p_a;
BEGIN
OPEN c(a_a);
LOOP
FETCH c INTO v_a, v_b;
EXIT WHEN c%NOTFOUND;
DBMS_OUTPUT.PUT_LINE('Fetched a record a='||TO_CHAR(v_a)||' b='||v_b);
END LOOP;
CLOSE c;
END;
$$
DELIMITER ;$$
DROP TABLE t1;
--echo #
--echo # Cursor parameters are not visible outside of the cursor
--echo #
DELIMITER $$;
--error ER_UNKNOWN_SYSTEM_VARIABLE
CREATE PROCEDURE p1(a_a INT)
AS
v_a INT;
CURSOR c (p_a INT) IS SELECT a FROM t1 WHERE a=p_a;
BEGIN
OPEN c(a_a);
p_a:=10;
END;
$$
DELIMITER ;$$
DELIMITER $$;
--error ER_UNKNOWN_SYSTEM_VARIABLE
CREATE PROCEDURE p1(a_a INT)
AS
v_a INT;
CURSOR c (p_a INT) IS SELECT a FROM t1 WHERE a=p_a;
BEGIN
p_a:=10;
OPEN c(a_a);
END;
$$
DELIMITER ;$$
--echo #
--echo # Cursor parameter shadowing a local variable
--echo #
CREATE TABLE t1 (a INT);
INSERT INTO t1 VALUES (1);
DELIMITER $$;
CREATE PROCEDURE p1(a INT)
AS
v_a INT:=NULL;
p_a INT:=NULL;
CURSOR c (p_a VARCHAR2) IS SELECT a FROM t1 WHERE p_a IS NOT NULL;
BEGIN
OPEN c(a);
FETCH c INTO v_a;
IF c%NOTFOUND THEN
BEGIN
SELECT 'No records found' AS msg;
RETURN;
END;
END IF;
CLOSE c;
SELECT 'Fetched a record a='||v_a AS msg;
INSERT INTO t1 VALUES (v_a);
END;
$$
DELIMITER ;$$
CALL p1(1);
SELECT * FROM t1;
CALL p1(NULL);
SELECT * FROM t1;
DROP PROCEDURE p1;
DROP TABLE t1;
--echo #
--echo # Parameters in SELECT list
--echo #
DELIMITER $$;
CREATE PROCEDURE p1(a_a INT, a_b VARCHAR)
AS
v_a INT;
v_b VARCHAR(10);
CURSOR c (p_a INT, p_b VARCHAR) IS SELECT p_a,p_b FROM DUAL;
BEGIN
FOR i IN 0..1
LOOP
OPEN c(a_a + i,a_b);
LOOP
FETCH c INTO v_a, v_b;
EXIT WHEN c%NOTFOUND;
SELECT 'Fetched a record a=' || v_a || ' b=' || v_b AS msg;
END LOOP;
CLOSE c;
END LOOP;
END;
$$
DELIMITER ;$$
CALL p1(1,'b1');
DROP PROCEDURE p1;
--echo #
--echo # Parameters in SELECT list + UNION
--echo #
DELIMITER $$;
CREATE PROCEDURE p1(a_a INT, a_b VARCHAR)
AS
v_a INT;
v_b VARCHAR(10);
CURSOR c (p_a INT, p_b VARCHAR) IS
SELECT p_a,p_b FROM DUAL
UNION ALL
SELECT p_a+1,p_b||'b' FROM DUAL;
BEGIN
OPEN c(a_a,a_b);
LOOP
FETCH c INTO v_a, v_b;
EXIT WHEN c%NOTFOUND;
SELECT 'Fetched a record a=' || v_a || ' b=' || v_b AS msg;
END LOOP;
CLOSE c;
END;
$$
DELIMITER ;$$
CALL p1(1,'b1');
DROP PROCEDURE p1;
--echo #
--echo # Parameters in SELECT list + type conversion + warnings
--echo #
DELIMITER $$;
CREATE PROCEDURE p1(a_a VARCHAR)
AS
v_a INT;
CURSOR c (p_a INT) IS SELECT p_a FROM DUAL;
BEGIN
OPEN c(a_a);
LOOP
FETCH c INTO v_a;
EXIT WHEN c%NOTFOUND;
SELECT 'Fetched a record a=' || v_a AS msg;
END LOOP;
CLOSE c;
END;
$$
DELIMITER ;$$
CALL p1('1b');
CALL p1('b1');
DROP PROCEDURE p1;
--echo #
--echo # One parameter in SELECT list + subselect
--echo #
DELIMITER $$;
CREATE PROCEDURE p1(a_a VARCHAR)
AS
v_a VARCHAR(10);
CURSOR c (p_a VARCHAR) IS
SELECT p_a FROM DUAL UNION SELECT REVERSE(p_a) FROM DUAL;
BEGIN
OPEN c((SELECT a_a));
LOOP
FETCH c INTO v_a;
EXIT WHEN c%NOTFOUND;
SELECT v_a;
END LOOP;
CLOSE c;
END;
$$
DELIMITER ;$$
CALL p1('ab');
DROP PROCEDURE p1;
--echo #
--echo # Two parameters in SELECT list + subselect
--echo #
SET sql_mode=ORACLE;
DELIMITER $$;
CREATE PROCEDURE p1()
AS
v_a VARCHAR(10);
v_b VARCHAR(20);
CURSOR c (p_a VARCHAR, p_b VARCHAR) IS
SELECT p_a, p_b FROM DUAL
UNION
SELECT p_b, p_a FROM DUAL;
BEGIN
OPEN c((SELECT 'aaa'),(SELECT 'bbb'));
LOOP
FETCH c INTO v_a, v_b;
EXIT WHEN c%NOTFOUND;
SELECT v_a, v_b;
END LOOP;
CLOSE c;
END;
$$
DELIMITER ;$$
CALL p1();
DROP PROCEDURE p1;
--echo #
--echo # Two parameters in SELECT list + two parameters in WHERE + subselects
--echo #
SET sql_mode=ORACLE;
DELIMITER $$;
CREATE PROCEDURE p1(a_a VARCHAR, a_b VARCHAR)
AS
v_a VARCHAR(10);
v_b VARCHAR(20);
CURSOR c (value_a VARCHAR, value_b VARCHAR,
pattern_a VARCHAR, pattern_b VARCHAR) IS
SELECT value_a, value_b FROM DUAL WHERE value_a LIKE pattern_a
UNION
SELECT value_b, value_a FROM DUAL WHERE value_b LIKE pattern_b;
BEGIN
OPEN c((SELECT 'aaa'),(SELECT 'bbb'),(SELECT a_a),(SELECT a_b));
LOOP
FETCH c INTO v_a, v_b;
EXIT WHEN c%NOTFOUND;
SELECT v_a, v_b;
END LOOP;
CLOSE c;
END;
$$
DELIMITER ;$$
CALL p1('%','%');
CALL p1('aaa','xxx');
CALL p1('xxx','bbb');
CALL p1('xxx','xxx');
DROP PROCEDURE p1;
--echo #
--echo # Parameters in SELECT list + stored function
--echo #
DELIMITER $$;
CREATE FUNCTION f1 (a VARCHAR) RETURN VARCHAR
AS
BEGIN
RETURN a || 'y';
END;
$$
CREATE PROCEDURE p1(a_a VARCHAR)
AS
v_a VARCHAR(10);
v_b VARCHAR(10);
CURSOR c (p_sel_a VARCHAR, p_cmp_a VARCHAR) IS
SELECT p_sel_a, p_cmp_a FROM DUAL;
BEGIN
OPEN c(f1(a_a), f1(a_a));
LOOP
FETCH c INTO v_a, v_b;
EXIT WHEN c%NOTFOUND;
SELECT v_a;
END LOOP;
CLOSE c;
END;
$$
DELIMITER ;$$
CALL p1('x');
# A complex expression
CALL p1(f1(COALESCE(NULL, f1('x'))));
DROP PROCEDURE p1;
DROP FUNCTION f1;
--echo #
--echo # One parameter in WHERE clause
--echo #
CREATE TABLE t1 (a INT, b VARCHAR(10));
CREATE TABLE t2 (a INT, b VARCHAR(10));
INSERT INTO t1 VALUES (1,'11');
INSERT INTO t1 VALUES (1,'12');
INSERT INTO t1 VALUES (2,'21');
INSERT INTO t1 VALUES (2,'22');
INSERT INTO t1 VALUES (3,'31');
INSERT INTO t1 VALUES (3,'32');
DELIMITER $$;
CREATE PROCEDURE p1(a_a INT)
AS
v_a INT;
v_b VARCHAR(10);
CURSOR c (p_a INT) IS SELECT a,b FROM t1 WHERE a=p_a;
BEGIN
OPEN c(a_a);
LOOP
FETCH c INTO v_a, v_b;
EXIT WHEN c%NOTFOUND;
INSERT INTO t2 VALUES (v_a,v_b);
END LOOP;
CLOSE c;
END;
$$
DELIMITER ;$$
CALL p1(1);
SELECT * FROM t2;
DROP TABLE t1;
DROP TABLE t2;
DROP PROCEDURE p1;
--echo #
--echo # Two parameters in WHERE clause
--echo #
CREATE TABLE t1 (a INT, b VARCHAR(10));
CREATE TABLE t2 (a INT, b VARCHAR(10));
INSERT INTO t1 VALUES (1,'11');
INSERT INTO t1 VALUES (1,'12');
INSERT INTO t1 VALUES (2,'21');
INSERT INTO t1 VALUES (2,'22');
INSERT INTO t1 VALUES (3,'31');
INSERT INTO t1 VALUES (3,'32');
DELIMITER $$;
CREATE PROCEDURE p1(a_a INT, a_b VARCHAR)
AS
v_a INT;
v_b VARCHAR(10);
CURSOR c (p_a INT, p_b VARCHAR) IS SELECT a,b FROM t1 WHERE a=p_a AND b=p_b;
BEGIN
OPEN c(a_a, a_b);
LOOP
FETCH c INTO v_a, v_b;
EXIT WHEN c%NOTFOUND;
INSERT INTO t2 VALUES (v_a,v_b);
END LOOP;
CLOSE c;
END;
$$
DELIMITER ;$$
CALL p1(1,'11');
SELECT * FROM t2;
DROP TABLE t1;
DROP TABLE t2;
DROP PROCEDURE p1;
--echo #
--echo # Parameters in WHERE and HAVING clauses
--echo #
CREATE TABLE t1 (name VARCHAR(10), value INT);
INSERT INTO t1 VALUES ('but',1);
INSERT INTO t1 VALUES ('but',1);
INSERT INTO t1 VALUES ('but',1);
INSERT INTO t1 VALUES ('bin',1);
INSERT INTO t1 VALUES ('bin',1);
INSERT INTO t1 VALUES ('bot',1);
DELIMITER $$;
CREATE PROCEDURE p1 (arg_name_limit VARCHAR, arg_total_limit INT)
AS
v_name VARCHAR(10);
v_total INT;
-- +0 is needed to work around the bug MDEV-11081
CURSOR c(p_v INT) IS
SELECT name, SUM(value + p_v) + 0 AS total FROM t1
WHERE name LIKE arg_name_limit
GROUP BY name HAVING total>=arg_total_limit;
BEGIN
FOR i IN 0..1
LOOP
OPEN c(i);
LOOP
FETCH c INTO v_name, v_total;
EXIT WHEN c%NOTFOUND;
SELECT v_name, v_total;
END LOOP;
CLOSE c;
END LOOP;
END;
$$
DELIMITER ;$$
CALL p1('%', 2);
CALL p1('b_t', 0);
DROP PROCEDURE p1;
DROP TABLE t1;
--echo #
--echo # One parameter in LIMIT clause
--echo #
CREATE TABLE t1 (a INT, b VARCHAR(10));
INSERT INTO t1 VALUES (1,'b1');
INSERT INTO t1 VALUES (2,'b2');
INSERT INTO t1 VALUES (3,'b3');
INSERT INTO t1 VALUES (4,'b4');
INSERT INTO t1 VALUES (5,'b5');
INSERT INTO t1 VALUES (6,'b6');
DELIMITER $$;
CREATE PROCEDURE p1(a_a INT)
AS
v_a INT;
v_b VARCHAR(10);
CURSOR c (p_a INT) IS SELECT a,b FROM t1 ORDER BY a LIMIT p_a;
BEGIN
CREATE TABLE t2 (a INT, b VARCHAR(10));
OPEN c(a_a);
LOOP
FETCH c INTO v_a, v_b;
EXIT WHEN c%NOTFOUND;
INSERT INTO t2 VALUES (v_a,v_b);
END LOOP;
CLOSE c;
SELECT * FROM t2;
DROP TABLE t2;
END;
$$
DELIMITER ;$$
CALL p1(1);
CALL p1(3);
CALL p1(6);
DROP TABLE t1;
DROP PROCEDURE p1;
--echo #
--echo # End of MDEV-10597 Cursors with parameters
--echo #
--echo #
--echo # MDEV-12209 sql_mode=ORACLE: Syntax error in a OPEN cursor with parameters makes the server crash
--echo #
CREATE TABLE t1 (a INT, b VARCHAR(10));
INSERT INTO t1 VALUES (1,'A');
DELIMITER $$;
--error ER_PARSE_ERROR
CREATE PROCEDURE p1(a INT,b VARCHAR)
AS
CURSOR c (p_a INT, p_b VARCHAR) IS SELECT * FROM t1 WHERE a=p_a;
BEGIN
OPEN c(a+, b);
LOOP
FETCH c INTO a, b;
EXIT WHEN c%NOTFOUND;
SELECT a, b;
END LOOP;
CLOSE c;
END;
$$
DELIMITER ;$$
DROP TABLE t1;
--echo #
--echo # MDEV-10577 sql_mode=ORACLE: %TYPE in variable declarations
--echo #
CREATE TABLE t1 (a INT, b VARCHAR(10),c DATETIME(3));
INSERT INTO t1 VALUES (1,'b1','2001-01-01 10:20:30.123');
INSERT INTO t1 VALUES (2,'b2','2001-01-02 10:20:30.123');
CREATE TABLE t2 LIKE t1;
DELIMITER $$;
CREATE PROCEDURE p1()
AS
v_a t1.a%TYPE;
v_b t1.b%TYPE;
v_c t1.c%TYPE;
CURSOR c IS SELECT a,b,c FROM t1;
BEGIN
OPEN c;
LOOP
FETCH c INTO v_a, v_b, v_c;
EXIT WHEN c%NOTFOUND;
INSERT INTO t2 (a,b,c) VALUES (v_a, v_b, v_c);
END LOOP;
CLOSE c;
END;
$$
DELIMITER ;$$
CALL p1();
SELECT * FROM t2;
DROP TABLE t2;
DROP PROCEDURE p1;
DROP TABLE t1;
--echo #
--echo # MDEV-12007 Allow ROW variables as a cursor FETCH target
--echo #
CREATE TABLE t1 (a INT, b VARCHAR(32));
INSERT INTO t1 VALUES (10,'b10');
INSERT INTO t1 VALUES (20,'b20');
INSERT INTO t1 VALUES (30,'b30');
DELIMITER $$;
CREATE PROCEDURE p1 AS
rec ROW(a INT, b VARCHAR(32));
CURSOR c IS SELECT a,b FROM t1;
BEGIN
OPEN c;
LOOP
FETCH c INTO rec;
EXIT WHEN c%NOTFOUND;
SELECT ('rec=(' || rec.a ||','|| rec.b||')') AS c;
END LOOP;
CLOSE c;
END;
$$
DELIMITER ;$$
CALL p1();
DROP PROCEDURE p1;
DROP TABLE t1;
--echo #
--echo # MDEV-12441 Variables declared after cursors with parameters lose values
--echo #
DELIMITER $$;
CREATE PROCEDURE p1() AS
x0 INT:=100;
CURSOR cur(cp1 INT, cp2 INT) IS SELECT cp1+cp2;
x1 INT:=101;
BEGIN
OPEN cur(10,11);
CLOSE cur;
SELECT x0, x1;
END;
$$
DELIMITER ;$$
CALL p1();
DROP PROCEDURE p1;
CREATE TABLE t1 (a INT);
DELIMITER $$;
CREATE PROCEDURE p1() AS
x0 INT:=100;
CURSOR cur(cp1 INT, cp2 INT) IS SELECT cp1+cp2;
x1 t1.a%TYPE:=101;
BEGIN
OPEN cur(10,11);
CLOSE cur;
SELECT x0, x1;
END;
$$
DELIMITER ;$$
CALL p1();
DROP PROCEDURE p1;
DROP TABLE t1;
DELIMITER $$;
CREATE PROCEDURE p1() AS
x0 INT:=100;
CURSOR cur(cp1 INT, cp2 INT) IS SELECT cp1+cp2;
x1 ROW(a INT,b INT):=ROW(101,102);
BEGIN
OPEN cur(10,11);
CLOSE cur;
SELECT x0, x1.a, x1.b;
END;
$$
DELIMITER ;$$
CALL p1();
DROP PROCEDURE p1;
CREATE TABLE t1 (a INT, b VARCHAR(10));
INSERT INTO t1 VALUES (10,'Tbl-t1.b0');
DELIMITER $$;
CREATE PROCEDURE p1() AS
x0 INT:=100;
CURSOR cur(cp1 INT, cp2 INT) IS SELECT a,b FROM t1;
x1 t1%ROWTYPE:=ROW(101,'Var-x1.b0');
BEGIN
SELECT x0, x1.a, x1.b;
OPEN cur(10,11);
FETCH cur INTO x1;
CLOSE cur;
SELECT x0, x1.a, x1.b;
END;
$$
DELIMITER ;$$
CALL p1();
DROP PROCEDURE p1;
DROP TABLE t1;
CREATE TABLE t1 (a INT, b VARCHAR(10));
INSERT INTO t1 VALUES (10,'Tbl-t1.b0');
DELIMITER $$;
CREATE PROCEDURE p1() AS
x0 INT:=100;
CURSOR cur(cp1 INT, cp2 INT) IS SELECT a,b FROM t1;
x1 cur%ROWTYPE:=ROW(101,'Var-x1.b0');
BEGIN
SELECT x0, x1.a, x1.b;
OPEN cur(10,11);
FETCH cur INTO x1;
CLOSE cur;
SELECT x0, x1.a, x1.b;
END;
$$
DELIMITER ;$$
CALL p1();
DROP PROCEDURE p1;
DROP TABLE t1;
--echo #
--echo # MDEV-12854 Synchronize CREATE..SELECT data type and result set metadata data type for INT functions
--echo #
--enable_metadata
--disable_ps_protocol
DELIMITER $$;
DECLARE
CURSOR c IS SELECT 1 AS c FROM DUAL;
BEGIN
OPEN c;
SELECT
c%ISOPEN,
c%NOTFOUND,
c%FOUND,
c%ROWCOUNT;
CLOSE c;
END;
$$
DELIMITER ;$$
--enable_ps_protocol
--disable_metadata

View File

@ -0,0 +1,872 @@
set sql_mode=oracle;
--echo #
--echo # MDEV-10697 sql_mode=ORACLE: GOTO statement
--echo #
--echo # matrice of tests in procedure
--echo # |--------------------------------------------------------
--echo # | | Same | Outside | to sub | No |
--echo # | | block | one block | block | matching |
--echo # | | | | | label |
--echo # |--------------------------------------------------------
--echo # | Forward jump | F1 | F3 | F5 | F7 |
--echo # |--------------------------------------------------------
--echo # | Backward jump | F2 | F4 | F6 | F8 |
--echo # |--------------------------------------------------------
--echo # Jump from handler to outside handling code block : F9
--echo # Jump from handler to handling code block : F10 (forbidden)
--echo # Jump inside handler : F21
--echo # Jump between handler : F22 (forbidden)
--echo # Jump from cascaded block with handler : F11
--echo # Duplicate label in same block : F12 (forbidden)
--echo # Duplicate label in different block : F13
--echo # Jump outside unlabeled block : F14
--echo # Jump inside/outside labeled block : F15
--echo # Jump from if / else : F16
--echo # Jump with cursors : F17
--echo # Jump outside case : F18
--echo # Jump inside/outside case block : F19
--echo # Jump outside labeled loop : F20
--echo # Jump (continue) labeled loop : F23
--echo # Two consecutive label : P24
--echo # Two consecutive label (backward and forward jump) : P25
--echo # Two consecutive label, continue to wrong label : P26
--echo # Consecutive goto label and block label : P27
--echo # Test in function
--echo # backward jump : func1
--echo # forward jump : func2
--echo # Test in trigger
--echo # forward jump : trg1
--echo #
--echo # Forward jump in same block
--echo #
DELIMITER $$;
CREATE or replace procedure f1(p2 IN OUT VARCHAR)
AS
BEGIN
p2:='a';
goto lab1;
<<lab1>>
goto lab2;
p2:='b';
<<lab2>>
return ;
END;
$$
DELIMITER ;$$
call f1(@wp1);
select 'f1',@wp1;
DROP PROCEDURE f1;
--echo #
--echo # Backward jump in same block
--echo #
DELIMITER $$;
CREATE or replace procedure f2(p2 IN OUT VARCHAR)
AS
BEGIN
p2:='a';
<<lab1>>
if (p2='b') then
return ;
end if;
p2:='b';
goto lab1;
END;
$$
DELIMITER ;$$
call f2(@wp1);
select 'f2',@wp1;
DROP PROCEDURE f2;
--echo #
--echo # Forward jump outside one block
--echo #
DELIMITER $$;
CREATE or replace procedure f3(p2 IN OUT VARCHAR)
AS
BEGIN
p2:='a';
if (p2='a') then
goto lab1;
end if;
p2:='c';
<<lab1>>
return ;
END;
$$
DELIMITER ;$$
call f3(@wp1);
select 'f3',@wp1;
DROP PROCEDURE f3;
--echo #
--echo # Backward jump outside one block
--echo #
DELIMITER $$;
CREATE or replace procedure f4(p2 IN OUT VARCHAR)
AS
BEGIN
p2:='a';
<<lab1>>
if (p2='a') then
p2:=p2||'b';
goto lab1;
end if;
if (p2='ab') then
p2:=p2||'c';
end if;
return ;
END;
$$
DELIMITER ;$$
call f4(@wp1);
select 'f4',@wp1;
DROP PROCEDURE f4;
DELIMITER $$;
--echo #
--echo # Forward jump inside sub block
--error ER_SP_LILABEL_MISMATCH
CREATE or replace procedure f5(p2 IN OUT VARCHAR)
AS
BEGIN
p2:='a';
goto lab5 ;
if (p2='a') then
<<lab5>>
p2:=p2||'b';
end if;
return ;
END;
$$
DELIMITER ;$$
DELIMITER $$;
--echo #
--echo # Backward jump inside sub block
--error ER_SP_LILABEL_MISMATCH
CREATE or replace procedure f6(p2 IN OUT VARCHAR)
AS
BEGIN
p2:='a';
if (p2='a') then
<<lab6>>
p2:=p2||'b';
return ;
end if;
goto lab6 ;
END;
$$
DELIMITER ;$$
DELIMITER $$;
--echo #
--echo # Backward jump - missing label
--error ER_SP_LILABEL_MISMATCH
CREATE or replace procedure f7(p2 IN OUT VARCHAR)
AS
BEGIN
<<lab>>
goto lab7 ;
return ;
END;
$$
DELIMITER ;$$
DELIMITER $$;
--echo #
--echo # Forward jump - missing label
--error ER_SP_LILABEL_MISMATCH
CREATE or replace procedure f8(p2 IN OUT VARCHAR)
AS
BEGIN
goto lab8 ;
<<lab>>
return ;
END;
$$
DELIMITER ;$$
--echo #
--echo # Jump from handler to procedure code
--echo #
DELIMITER $$;
CREATE or replace procedure f9(lim INT, res OUT VARCHAR)
AS
a INT;
BEGIN
<<lab9>>
if lim=-1 then
res:=res||' -- goto end limit -1 --';
goto lab9_end;
end if;
begin
SELECT a INTO a FROM information_schema.tables LIMIT lim;
EXCEPTION
WHEN TOO_MANY_ROWS THEN
begin
res:=res||'--- too_many_rows cought ---';
lim:=0;
goto lab9;
end;
WHEN NO_DATA_FOUND THEN
begin
res:=res||'--- no_data_found cought ---';
lim:=-1;
goto lab9;
end;
end;
res:=res||'error';
<<lab9_end>>
return ;
END;
$$
DELIMITER ;$$
SET @res='';
CALL f9(2, @res);
SELECT 'f9',@res;
CALL f9(0, @res);
SELECT 'f9',@res;
DROP PROCEDURE f9;
DELIMITER $$;
--echo #
--echo # Jump from handler to handling bloc
--error ER_SP_LILABEL_MISMATCH
CREATE or replace procedure f10(lim INT, res OUT VARCHAR)
AS
a INT;
BEGIN
begin
<<lab10>>
SELECT a INTO a FROM information_schema.tables LIMIT lim;
EXCEPTION
WHEN TOO_MANY_ROWS THEN
begin
res:='--- too_many_rows cought ---';
goto lab10;
end;
WHEN NO_DATA_FOUND THEN res:='--- no_data_found cought ---';
end;
return ;
END;
$$
--echo #
--echo # Jump from cascaded block with handler
--echo #
CREATE or replace procedure f11(lim INT, res OUT VARCHAR)
AS
a INT;
BEGIN
<<lab11a>>
begin
SELECT a INTO a FROM information_schema.tables LIMIT lim;
EXCEPTION
WHEN TOO_MANY_ROWS THEN
begin
res:=res||'--- too_many_rows cought 1 ---';
goto lab11b;
end;
WHEN NO_DATA_FOUND THEN
begin
res:=res||'--- no_data_found cought 1 ---';
lim:=2;
SELECT a INTO a FROM information_schema.tables LIMIT lim;
EXCEPTION
WHEN TOO_MANY_ROWS THEN
begin
res:=res||'--- too_many_rows cought 2 ---';
goto lab11a;
end;
WHEN NO_DATA_FOUND THEN res:='--- no_data_found cought 2 ---';
end;
end;
set res:=res||' error ';
<<lab11b>>
return ;
END;
$$
DELIMITER ;$$
SET @res='';
CALL f11(0, @res);
SELECT 'f11',@res;
DROP PROCEDURE f11;
DELIMITER $$;
--echo #
--echo # Jump inside handler
--echo #
CREATE or replace procedure f21(lim INT, res OUT VARCHAR)
AS
a INT;
BEGIN
begin
SELECT a INTO a FROM information_schema.tables LIMIT lim;
EXCEPTION
WHEN TOO_MANY_ROWS THEN
begin
<<retry>>
lim:=lim-1;
loop
begin
SELECT a INTO a FROM information_schema.tables LIMIT lim;
EXCEPTION
WHEN TOO_MANY_ROWS THEN
begin
lim:=lim-1;
goto retry;
end;
end;
exit ;
end loop;
end;
end;
res:=lim;
return ;
END;
$$
DELIMITER ;$$
SET @res='';
CALL f21(10, @res);
SELECT 'f21',@res;
drop procedure f21;
DELIMITER $$;
--echo #
--echo # Jump beetween handler
--error ER_SP_LILABEL_MISMATCH
CREATE or replace procedure f22(lim INT, res OUT VARCHAR)
AS
a INT;
BEGIN
res:='ok';
begin
SELECT a INTO a FROM information_schema.tables LIMIT lim;
EXCEPTION
WHEN TOO_MANY_ROWS THEN
goto nodata ;
WHEN NO_DATA_FOUND THEN
begin
<<nodata>>
res:='error';
end;
end;
return ;
END;
$$
DELIMITER ;$$
DELIMITER $$;
--echo #
--echo # Duplicate label in same bloc
--error 1309
CREATE or replace procedure f12(lim INT, res OUT VARCHAR)
AS
a INT;
BEGIN
<<lab12>>
res:='error';
<<lab12>>
return ;
END;
$$
DELIMITER ;$$
--echo #
--echo # Duplicate label in different block
--echo #
DELIMITER $$;
CREATE or replace procedure f13(lim INT, res OUT VARCHAR)
AS
a INT;
BEGIN
a:=0;
<<lab13>>
a:=a+1;
begin
<<lab13>>
a:=a+1;
if (a<10) then
goto lab13;
end if;
end;
res:=a;
if (a=10) then
goto lab13;
end if;
return ;
END;
$$
DELIMITER ;$$
SET @res='';
CALL f13(0, @res);
SELECT 'f13',@res;
DROP PROCEDURE f13;
--echo #
--echo # Jump outside unlabeled block
--echo #
DELIMITER $$;
CREATE or replace procedure f14(lim INT, res OUT VARCHAR)
AS
a INT;
BEGIN
a:=0;
loop
a:=a+1;
if (a<10) then
continue;
end if;
if (a>=lim) then
goto lab14;
end if;
if (a>=20) then
exit;
end if;
end loop;
<<lab14>>
res:=a;
return ;
END;
$$
DELIMITER ;$$
SET @res='';
CALL f14(15, @res);
SELECT 'f14',@res;
CALL f14(8, @res);
SELECT 'f14',@res;
CALL f14(25, @res);
SELECT 'f14',@res;
DROP PROCEDURE f14;
--echo #
--echo # Jump inside/outside labeled block
--echo #
DELIMITER $$;
CREATE or replace procedure f15(lim INT, res OUT VARCHAR)
AS
a INT;
BEGIN
a:=0;
<<looplabel>> loop
<<beginlooplabel>>
a:=a+1;
if (a<10) then
continue looplabel;
end if;
if (a>=lim) then
goto lab15;
end if;
if (a>=20) then
exit looplabel;
end if;
goto beginlooplabel;
end loop;
<<lab15>>
res:=a;
return ;
END;
$$
DELIMITER ;$$
SET @res='';
CALL f15(15, @res);
SELECT 'f15',@res;
CALL f15(8, @res);
SELECT 'f15',@res;
CALL f15(25, @res);
SELECT 'f15',@res;
DROP PROCEDURE f15;
--echo #
--echo # Jump from if / else
--echo #
DELIMITER $$;
CREATE or replace procedure f16(lim INT, res OUT VARCHAR)
AS
a INT;
BEGIN
if (lim<10) then
goto lab16_1;
else
goto lab16_2;
end if;
<<lab16_1>>
res:='if lab16_1';
goto lab16_3;
<<lab16_2>>
res:='else lab16_2';
goto lab16_3;
res:='error lab16_3';
<<lab16_3>>
return ;
END;
$$
DELIMITER ;$$
SET @res='';
CALL f16(15, @res);
SELECT 'f16',@res;
CALL f16(8, @res);
SELECT 'f16',@res;
DROP PROCEDURE f16;
--echo #
--echo # Jump with cursors
--echo #
DELIMITER $$;
CREATE or replace procedure f17(lim INT, res OUT VARCHAR)
AS
v_a INT;
v_b VARCHAR(10);
CURSOR cur1 IS SELECT 1 FROM dual where 1=2;
BEGIN
OPEN cur1;
LOOP
FETCH cur1 INTO v_a;
EXIT WHEN cur1%NOTFOUND;
END LOOP;
CLOSE cur1;
<<lab17>>
lim:=lim-1;
begin
declare
CURSOR cur1 IS SELECT 1 FROM dual;
CURSOR cur2 IS SELECT 1 FROM dual where 1=2;
begin
LOOP
OPEN cur1;
FETCH cur1 INTO v_a;
EXIT WHEN cur1%NOTFOUND;
res:=res||'-'||lim ;
close cur1;
if (lim>0) then
goto lab17;
else
goto lab17_end;
end if;
END LOOP;
end;
<<lab17_end>>
null;
end;
END;
$$
DELIMITER ;$$
SET @res='';
CALL f17(5, @res);
SELECT 'f17',@res;
DROP PROCEDURE f17;
--echo #
--echo # Jump outside case
--echo #
DELIMITER $$;
CREATE or replace procedure f18(lim INT, res OUT VARCHAR)
AS
a INT;
BEGIN
case lim
when 1 then
res:='case branch 18_1';
goto lab18_1;
res:='error';
when 2 then
res:='case branch 18_2';
goto lab18_2;
res:='error';
else
res:='default branch 18';
end case;
<<lab18_1>>
null;
<<lab18_2>>
return ;
END;
$$
DELIMITER ;$$
SET @res='';
CALL f18(0, @res);
SELECT 'f18',@res;
CALL f18(1, @res);
SELECT 'f18',@res;
CALL f18(2, @res);
SELECT 'f18',@res;
DROP PROCEDURE f18;
--echo #
--echo # Jump inside/outside case block
--echo #
DELIMITER $$;
CREATE or replace procedure f19(lim INT, res OUT VARCHAR)
AS
a INT;
BEGIN
a:=1;
case lim
when 1 then
<<lab19_0>>
a:=a+1;
if (a<10) then
goto lab19_0;
else
goto lab19_1;
end if;
res:='case branch 19_1';
else
res:='default branch 18';
end case;
goto lab19_end;
<<lab19_1>>
res:=a;
<<lab19_end>>
return ;
END;
$$
DELIMITER ;$$
SET @res='';
CALL f19(1, @res);
SELECT 'f19',@res;
DROP PROCEDURE f19;
DELIMITER $$;
--echo #
--echo # Jump outside labeled loop
--echo #
CREATE OR REPLACE PROCEDURE f20(res OUT VARCHAR)
AS
a INT := 1;
BEGIN
<<lab>>
FOR i IN a..10 LOOP
IF i = 5 THEN
a:= a+1;
goto lab;
END IF;
END LOOP;
res:=a;
END;
$$
DELIMITER ;$$
CALL f20(@res);
SELECT 'f20',@res;
DROP PROCEDURE f20;
DELIMITER $$;
--echo #
--echo # Jump (continue) labeled loop
--echo #
CREATE OR REPLACE PROCEDURE f23(res OUT VARCHAR)
AS
a INT := 1;
BEGIN
<<lab>>
FOR i IN a..10 LOOP
IF i = 5 THEN
a:= a+1;
continue lab;
END IF;
END LOOP;
res:=a;
END;
$$
DELIMITER ;$$
CALL f23(@res);
SELECT 'f23',@res;
DROP PROCEDURE f23;
DELIMITER $$;
--echo #
--echo # Two consecutive label (backward jump)
--echo #
CREATE OR REPLACE PROCEDURE p24(action IN INT, res OUT varchar) AS
a integer;
BEGIN
<<lab1>>
<<lab2>>
if (action = 1) then
res:=res||' '||action;
action:=2;
goto lab1;
end if;
if (action = 2) then
res:=res||' '||action;
action:=3;
goto lab2;
end if;
END;
$$
DELIMITER ;$$
call p24(1,@res);
select 'p24',@res;
DROP PROCEDURE p24;
DELIMITER $$;
--echo #
--echo # Two consecutive label (backward and forward jump)
--echo #
CREATE OR REPLACE PROCEDURE p25(action IN INT, res OUT varchar) AS
a integer;
BEGIN
if (action = 1) then
res:=res||' '||action;
action:=2;
goto lab2;
end if;
goto lab_end;
<<lab1>>
<<lab2>>
res:=res||' '||action;
if (action = 2) then
res:=res||' '||action;
action:=3;
goto lab1;
end if;
<<lab_end>>
null;
END;
$$
DELIMITER ;$$
call p25(1,@res);
select 'p25',@res;
DROP PROCEDURE p25;
DELIMITER $$;
--echo #
--echo # Two consecutive label, continue to wrong label
--error ER_SP_LILABEL_MISMATCH
CREATE OR REPLACE PROCEDURE p26(action IN INT, res OUT varchar) AS
BEGIN
<<lab1>>
<<lab2>>
FOR i IN 1..10 LOOP
continue lab1;
END LOOP;
END;
$$
DELIMITER ;$$
DELIMITER $$;
--echo #
--echo # Consecutive goto label and block label
--echo #
CREATE OR REPLACE PROCEDURE p27(action IN INT, res OUT varchar) AS
BEGIN
res:='';
<<lab1>>
<<lab2>>
FOR i IN 1..10 LOOP
if (action = 1) then
res:=res||' '||action||'-'||i;
action:=2;
continue lab2;
end if;
if (action = 2) then
res:=res||' '||action||'-'||i;
action:='3';
goto lab2;
end if;
if (action = 3) then
res:=res||' '||action||'-'||i;
action:='4';
goto lab1;
end if;
if (action = 4) then
res:=res||' '||action||'-'||i;
exit lab2;
end if;
END LOOP;
END;
$$
DELIMITER ;$$
call p27(1,@res);
select 'p27',@res;
DROP PROCEDURE p27;
--echo # ----------------------
--echo # -- TEST IN FUNCTION --
--echo # ----------------------
--echo #
--echo # FUNCTION : Backward jump
--echo #
DELIMITER $$;
CREATE or replace function func1()
return varchar
AS
p2 varchar(10);
BEGIN
p2:='a';
<<lab1>>
if (p2='a') then
p2:=p2||'b';
goto lab1;
end if;
if (p2='ab') then
p2:=p2||'c';
end if;
return p2;
END;
$$
DELIMITER ;$$
select 'func1',func1();
DROP function func1;
--echo #
--echo # FUNCTION : forward jump
--echo #
DELIMITER $$;
CREATE or replace function func2()
return varchar
AS
p2 varchar(10);
BEGIN
p2:='a';
if (p2='a') then
goto lab1;
end if;
p2:='b';
<<lab1>>
return p2;
END;
$$
DELIMITER ;$$
select 'func2',func2();
DROP function func2;
--echo # ---------------------
--echo # -- TEST IN TRIGGER --
--echo # ---------------------
--echo #
--echo # TRIGGER : forward jump
--echo #
CREATE TABLE t1 (a INT);
DELIMITER $$;
CREATE TRIGGER trg1 BEFORE INSERT ON t1 FOR EACH ROW
BEGIN
IF :NEW.a IS NULL
THEN
:NEW.a:= 15;
goto end_trg;
END IF;
:NEW.a:= 10;
<<end_trg>>
null;
END;
$$
DELIMITER ;$$
insert into t1 values (1);
insert into t1 values (null);
SELECT * FROM t1;
DROP TRIGGER trg1;
DROP TABLE t1;

View File

@ -0,0 +1,9 @@
--eval CREATE FUNCTION f1(param $type) RETURN $type AS BEGIN RETURN param; END;
SHOW CREATE FUNCTION f1;
--eval SELECT LENGTH(f1(REPEAT('a',$length)));
--eval CREATE TABLE t1 AS SELECT f1(REPEAT('a',$length)) AS a;
SHOW CREATE TABLE t1;
DROP TABLE t1;
DROP FUNCTION f1;

View File

@ -0,0 +1,37 @@
SET sql_mode=ORACLE;
--echo #
--echo # MDEV-10596 Allow VARCHAR and VARCHAR2 without length as a data type of routine parameters and in RETURN clause
--echo #
--let type = CHAR
--let length = 2000
--source sp-param.inc
--let type = NCHAR
--let length = 2000
--source sp-param.inc
--let type = BINARY
--let length = 2000
--source sp-param.inc
--let type = VARCHAR
--let length = 4000
--source sp-param.inc
--let type = VARCHAR2
--let length = 4000
--source sp-param.inc
--let type = NVARCHAR
--let length = 4000
--source sp-param.inc
--let type = VARBINARY
--let length = 4000
--source sp-param.inc
--let type = RAW
--let length = 4000
--source sp-param.inc

View File

@ -0,0 +1,6 @@
--let $query= CREATE PROCEDURE p1() AS var $type; rec ROW(var $type); BEGIN CREATE TABLE t1 AS SELECT var,rec.var FROM DUAL;END
--eval $query
CALL p1();
SHOW CREATE TABLE t1;
DROP TABLE t1;
DROP PROCEDURE p1;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,346 @@
--source include/not_embedded.inc
SET sql_mode=ORACLE;
--echo #
--echo # MDEV-10577 sql_mode=ORACLE: %TYPE in variable declarations
--echo #
--echo #
--echo # Initiation:
--echo # - creating database db1
--echo # - creating user user1 with access rights to db1
--echo #
CREATE DATABASE db1;
CREATE TABLE db1.t1 (a INT, b VARCHAR(10));
CREATE USER user1;
GRANT ALL PRIVILEGES ON test.* TO user1;
connect (conn1,localhost,user1,,test);
SET sql_mode=ORACLE;
SELECT database();
SELECT user();
--echo #
--echo # Making sure that user1 does not have privileges to db1.t1
--echo #
--error ER_TABLEACCESS_DENIED_ERROR
SHOW CREATE TABLE db1.t1;
--error ER_TABLEACCESS_DENIED_ERROR
SHOW FIELDS IN db1.t1;
--echo #
--echo # Trigger: using %TYPE with a table we don't have access to
--echo #
CREATE TABLE test.t1 (a INT, b INT);
INSERT INTO test.t1 (a,b) VALUES (10,20);
SELECT * FROM t1;
DELIMITER $$;
CREATE TRIGGER test.tr1 BEFORE INSERT ON test.t1 FOR EACH ROW
BEGIN
DECLARE b db1.t1.b%TYPE := 20;
BEGIN
:NEW.b := 10;
END;
END
$$
DELIMITER ;$$
--error ER_TABLEACCESS_DENIED_ERROR
INSERT INTO t1 (a) VALUES (10);
SELECT * FROM t1;
DROP TRIGGER tr1;
DROP TABLE t1;
--echo #
--echo # Stored procedure: Using %TYPE for with a table that we don't have access to
--echo # DEFINER user1, SQL SECURITY DEFAULT
--echo #
DELIMITER $$;
CREATE PROCEDURE p1()
AS
a db1.t1.a%TYPE := 10;
BEGIN
SELECT a;
END;
$$
DELIMITER ;$$
--error ER_TABLEACCESS_DENIED_ERROR
CALL p1;
DROP PROCEDURE p1;
DELIMITER $$;
CREATE PROCEDURE p1()
AS
a db1.t1%ROWTYPE;
BEGIN
SELECT a.a;
END;
$$
DELIMITER ;$$
--error ER_TABLEACCESS_DENIED_ERROR
CALL p1;
DROP PROCEDURE p1;
--echo #
--echo # Stored procedure: Using %TYPE for with a table that we don't have access to
--echo # DEFINER root, SQL SECURITY INVOKER
--echo #
connection default;
DELIMITER $$;
CREATE PROCEDURE p1()
SQL SECURITY INVOKER
AS
a db1.t1.a%TYPE := 10;
BEGIN
SELECT a;
END;
$$
DELIMITER ;$$
connection conn1;
--error ER_TABLEACCESS_DENIED_ERROR
CALL p1;
DROP PROCEDURE p1;
connection default;
DELIMITER $$;
CREATE PROCEDURE p1()
SQL SECURITY INVOKER
AS
a db1.t1%ROWTYPE;
BEGIN
SELECT a.a;
END;
$$
DELIMITER ;$$
connection conn1;
--error ER_TABLEACCESS_DENIED_ERROR
CALL p1;
DROP PROCEDURE p1;
--echo #
--echo # Stored procedure: Using %TYPE for with a table that we don't have access to
--echo # DEFINER root, SQL SECURITY DEFINER
--echo #
connection default;
DELIMITER $$;
CREATE PROCEDURE p1()
SQL SECURITY DEFINER
AS
a db1.t1.a%TYPE := 10;
BEGIN
SELECT a;
END;
$$
DELIMITER ;$$
connection conn1;
CALL p1;
DROP PROCEDURE p1;
connection default;
DELIMITER $$;
CREATE PROCEDURE p1()
SQL SECURITY DEFINER
AS
a db1.t1%ROWTYPE;
BEGIN
a.a:= 10;
SELECT a.a;
END;
$$
DELIMITER ;$$
connection conn1;
CALL p1;
DROP PROCEDURE p1;
--echo #
--echo # Stored function: Using %TYPE for with a table that we don't have access to
--echo # DEFINER user1, SQL SECURITY DEFAULT
--echo #
CREATE TABLE t1 (a INT);
DELIMITER $$;
CREATE FUNCTION f1() RETURN INT
AS
a db1.t1.a%TYPE:=0;
BEGIN
RETURN OCTET_LENGTH(a);
END;
$$
DELIMITER ;$$
--error ER_TABLEACCESS_DENIED_ERROR
SELECT f1();
DROP FUNCTION f1;
DROP TABLE t1;
--echo #
--echo # Stored function: Using %TYPE for with a table that we don't have access to
--echo # DEFINER root, SQL SECURITY INVOKER
--echo #
connection default;
CREATE TABLE t1 (a INT);
DELIMITER $$;
CREATE FUNCTION f1() RETURN INT
SQL SECURITY INVOKER
AS
a db1.t1.a%TYPE:=0;
BEGIN
RETURN OCTET_LENGTH(a);
END;
$$
DELIMITER ;$$
connection conn1;
--error ER_TABLEACCESS_DENIED_ERROR
SELECT f1();
DROP FUNCTION f1;
DROP TABLE t1;
--echo #
--echo # Stored function: Using %TYPE for with a table that we don't have access to
--echo # DEFINER root, SQL SECURITY DEFINER
--echo #
connection default;
CREATE TABLE t1 (a INT);
DELIMITER $$;
CREATE FUNCTION f1() RETURN INT
SQL SECURITY DEFINER
AS
a db1.t1.a%TYPE:=0;
BEGIN
RETURN OCTET_LENGTH(a);
END;
$$
DELIMITER ;$$
connection conn1;
SELECT f1();
DROP FUNCTION f1;
DROP TABLE t1;
connection default;
# qc_sqlite: GRANT SELECT (a) ON db1.t1 TO user1;
# qc_sqlite: Does not collect database/able names.
connection conn1;
--echo #
--echo # Making sure that user1 has access to db1.t1.a, but not to db1.t1.b
--echo #
--error ER_TABLEACCESS_DENIED_ERROR
SHOW CREATE TABLE db1.t1;
SHOW FIELDS IN db1.t1;
--echo #
--echo # Trigger: Per-column privileges
--echo #
CREATE TABLE test.t1 (a INT, b INT);
INSERT INTO test.t1 (a,b) VALUES (10,20);
SELECT * FROM t1;
# %TYPE reference using a column we have access to
DELIMITER $$;
CREATE TRIGGER test.tr1 BEFORE INSERT ON test.t1 FOR EACH ROW
BEGIN
DECLARE a db1.t1.a%TYPE := 20;
BEGIN
:NEW.b := 10;
END;
END
$$
DELIMITER ;$$
INSERT INTO t1 (a) VALUES (10);
SELECT * FROM t1;
DROP TRIGGER tr1;
# %TYPE reference using a column that we don't have access to
DELIMITER $$;
CREATE TRIGGER test.tr1 BEFORE INSERT ON test.t1 FOR EACH ROW
BEGIN
DECLARE b db1.t1.b%TYPE := 20;
BEGIN
:NEW.b := 10;
END;
END
$$
DELIMITER ;$$
--error ER_COLUMNACCESS_DENIED_ERROR
INSERT INTO t1 (a) VALUES (10);
SELECT * FROM t1;
DROP TRIGGER tr1;
DROP TABLE t1;
--echo #
--echo # Stored procedure: Per-column privileges
--echo # DEFINER user1, SQL SECURITY DEFAULT
--echo #
DELIMITER $$;
CREATE PROCEDURE p1()
AS
a db1.t1.a%TYPE := 10;
BEGIN
SELECT a;
END;
$$
DELIMITER ;$$
CALL p1;
DROP PROCEDURE p1;
DELIMITER $$;
CREATE PROCEDURE p1()
AS
b db1.t1.b%TYPE := 10;
BEGIN
SELECT b;
END;
$$
DELIMITER ;$$
--error ER_COLUMNACCESS_DENIED_ERROR
CALL p1;
DROP PROCEDURE p1;
DELIMITER $$;
CREATE PROCEDURE p1()
AS
b db1.t1%ROWTYPE;
BEGIN
b.b:=10;
SELECT b.b;
END;
$$
DELIMITER ;$$
--error ER_COLUMNACCESS_DENIED_ERROR
CALL p1;
DROP PROCEDURE p1;
--echo #
--echo # Clean up
--echo #
disconnect conn1;
connection default;
DROP USER user1;
DROP DATABASE db1;
--echo #
--echo # End of MDEV-10577 sql_mode=ORACLE: %TYPE in variable declarations
--echo #

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,106 @@
set sql_mode=ORACLE;
--error ER_PARSE_ERROR
:NEW.a := 1;
--error ER_PARSE_ERROR
:OLD.a := 1;
--error ER_PARSE_ERROR
:OLa.a := 1;
--error ER_PARSE_ERROR
SELECT :NEW.a;
--error ER_PARSE_ERROR
SELECT :OLD.a;
--error ER_PARSE_ERROR
SELECT :OLa.a;
CREATE TABLE t1 (a INT);
CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW NEW.a:= 10;
INSERT INTO t1 VALUES ();
SELECT * FROM t1;
DROP TRIGGER tr1;
DROP TABLE t1;
CREATE TABLE t1 (a INT);
CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW :NEW.a:= 10;
INSERT INTO t1 VALUES ();
SELECT * FROM t1;
DROP TRIGGER tr1;
DROP TABLE t1;
CREATE TABLE t1 (a INT);
DELIMITER /;
CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW
BEGIN
IF :NEW.a IS NULL
THEN
:NEW.a:= 10;
END IF;
END;
/
DELIMITER ;/
INSERT INTO t1 VALUES (NULL);
SELECT * FROM t1;
DROP TRIGGER tr1;
DROP TABLE t1;
CREATE TABLE t1 (a INT);
DELIMITER /;
CREATE TRIGGER tr1 BEFORE UPDATE ON t1 FOR EACH ROW
BEGIN
IF :OLD.a IS NULL
THEN
:NEW.a:= 10;
END IF;
END;
/
DELIMITER ;/
INSERT INTO t1 VALUES (NULL);
UPDATE t1 SET a=NULL;
SELECT * FROM t1;
DROP TRIGGER tr1;
DROP TABLE t1;
CREATE TABLE t1 (a INT, b INT, c INT);
DELIMITER /;
CREATE TRIGGER tr1 BEFORE INSERT ON t1
FOR EACH ROW
DECLARE
cnt INT := 0;
BEGIN
IF :NEW.a IS NULL THEN cnt:=cnt+1; END IF;
IF :NEW.b IS NULL THEN cnt:=cnt+1; END IF;
IF :NEW.c IS NULL THEN :NEW.c:=cnt; END IF;
END;
/
DELIMITER ;/
INSERT INTO t1 VALUES ();
INSERT INTO t1 VALUES (1, NULL, NULL);
INSERT INTO t1 VALUES (NULL, 1, NULL);
INSERT INTO t1 VALUES (1, 1, NULL);
SELECT * FROM t1;
DROP TABLE t1;
--echo #
--echo # MDEV-10577 sql_mode=ORACLE: %TYPE in variable declarations
--echo #
CREATE TABLE t1 (a INT, b INT, total INT);
DELIMITER $$;
CREATE TRIGGER tr1 BEFORE INSERT ON t1
FOR EACH ROW
DECLARE
va t1.a%TYPE:= :NEW.a;
vb t1.b%TYPE:= :NEW.b;
BEGIN
:NEW.total:= va + vb;
END;
$$
DELIMITER ;$$
INSERT INTO t1 (a,b) VALUES (10, 20);
SELECT * FROM t1;
DROP TABLE t1;

View File

@ -0,0 +1,16 @@
SET sql_mode=ORACLE;
--echo #
--echo # MDEV-10588 sql_mode=ORACLE: TRUNCATE TABLE t1 [ {DROP|REUSE} STORAGE ]
--echo #
CREATE TABLE t1 (a INT);
TRUNCATE TABLE t1 REUSE STORAGE;
TRUNCATE TABLE t1 DROP STORAGE;
DROP TABLE t1;
# REUSE is actually a reserved word in Oracle.
# But we don't reserve it for MDEV-10588
CREATE TABLE reuse (reuse INT);
DROP TABLE reuse;

View File

@ -0,0 +1,4 @@
SET sql_mode=ORACLE;
CREATE TABLE t1 (a BLOB);
SHOW CREATE TABLE t1;
DROP TABLE t1;

View File

@ -0,0 +1,10 @@
SET sql_mode=ORACLE;
# CLOB is not a reserved word even in sql_mode=ORACLE
CREATE TABLE clob (clob INT);
SHOW CREATE TABLE clob;
DROP TABLE clob;
CREATE TABLE t1 (a CLOB);
SHOW CREATE TABLE t1;
DROP TABLE t1;

View File

@ -0,0 +1,4 @@
SET sql_mode=ORACLE;
CREATE TABLE t1 (a DATE);
SHOW CREATE TABLE t1;
DROP TABLE t1;

View File

@ -0,0 +1,9 @@
SET sql_mode=ORACLE;
CREATE TABLE t1 (a NUMBER);
SHOW CREATE TABLE t1;
DROP TABLE t1;
CREATE TABLE t1 (a NUMBER(10,2));
SHOW CREATE TABLE t1;
DROP TABLE t1;

View File

@ -0,0 +1,10 @@
SET sql_mode=ORACLE;
# RAW is not a reserved word even in sql_mode=ORACLE
CREATE TABLE raw (raw INT);
SHOW CREATE TABLE raw;
DROP TABLE raw;
CREATE TABLE t1 (a RAW(10));
SHOW CREATE TABLE t1;
DROP TABLE t1;

View File

@ -0,0 +1,9 @@
SET sql_mode=ORACLE;
--echo #
--echo # MDEV-11275 sql_mode=ORACLE: CAST(..AS VARCHAR(N))
--echo #
SELECT CAST(123 AS VARCHAR(10)) FROM DUAL;
--error ER_PARSE_ERROR
SELECT CAST(123 AS VARCHAR) FROM DUAL;

View File

@ -0,0 +1,19 @@
SET sql_mode=ORACLE;
# VARCHAR2 is not a reserved word even in sql_mode=ORACLE
CREATE TABLE varchar2 (varchar2 INT);
SHOW CREATE TABLE varchar2;
DROP TABLE varchar2;
CREATE TABLE t1 (a VARCHAR2(10));
SHOW CREATE TABLE t1;
DROP TABLE t1;
--echo #
--echo # MDEV-11275 sql_mode=ORACLE: CAST(..AS VARCHAR(N))
--echo #
SELECT CAST(123 AS VARCHAR2(10)) FROM DUAL;
--error ER_PARSE_ERROR
SELECT CAST(123 AS VARCHAR2) FROM DUAL;

View File

@ -0,0 +1,38 @@
SET sql_mode=oracle;
--echo #
--echo # MDEV-10411 Providing compatibility for basic PL/SQL constructs
--echo # Part 6: Assignment operator
--echo #
max_sort_length:=1030;
SELECT @@max_sort_length;
max_sort_length:=DEFAULT;
--echo #
--echo # Testing that SP variables shadow global variables in assignments
--echo #
DELIMITER $$;
CREATE PROCEDURE p1
AS
BEGIN
max_sort_length:=1030;
DECLARE
max_sort_length INT DEFAULT 1031;
BEGIN
SELECT @@max_sort_length, max_sort_length;
max_sort_length:=1032;
SELECT @@max_sort_length, max_sort_length;
END;
SELECT @@max_sort_length;
max_sort_length:= DEFAULT;
END;
$$
DELIMITER ;$$
CALL p1();
DROP PROCEDURE p1;
--echo #
--echo # End of MDEV-10411 Providing compatibility for basic PL/SQL constructs (part 6)
--echo #

View File

@ -53,3 +53,7 @@ PREPARE stmt FROM 'UPDATE t2 AS A NATURAL JOIN v1 B SET B.f1 = 1';
# (Sqlite3 error: SQL logic error or missing database, near "SET": syntax error):
# "UPDATE t2 AS A NATURAL JOIN v1 B SET B.f1 = 1"
SELECT LENGTH(_utf8 0xC39F), LENGTH(CHAR(14844588 USING utf8));
# warning: [qc_sqlite] Statement was classified only based on keywords
# (Sqlite3 error: SQL logic error or missing database, near "0xC39F": syntax error):
# "SELECT LENGTH(_utf8 0xC39F), LENGTH(CHAR(14844588 USING utf8));"

View File

@ -14,6 +14,7 @@
#include "testreader.hh"
#include <algorithm>
#include <map>
#include <iostream>
using std::istream;
using std::string;
@ -149,19 +150,34 @@ void init_keywords()
}
}
skip_action_t get_action(const string& keyword)
skip_action_t get_action(const string& keyword, const string& delimiter)
{
skip_action_t action = SKIP_NOTHING;
string key(keyword);
std::transform(key.begin(), key.end(), key.begin(), ::tolower);
KeywordActionMapping::iterator i = mtl_keywords.find(key);
if (i != mtl_keywords.end())
if (key == "delimiter")
{
action = i->second;
// DELIMITER is directly understood by the parser so it needs to
// be handled explicitly.
action = SKIP_DELIMITER;
}
else if (delimiter == ";")
{
// Some mysqltest keywords, such as "while", "exit" and "if" are also
// PL/SQL keywords. We assume they can only be used in the former role,
// if the delimiter is ";".
string key(keyword);
std::transform(key.begin(), key.end(), key.begin(), ::tolower);
KeywordActionMapping::iterator i = mtl_keywords.find(key);
if (i != mtl_keywords.end())
{
action = i->second;
}
}
return action;
@ -193,7 +209,7 @@ TestReader::TestReader(istream& in,
size_t line)
: m_in(in)
, m_line(line)
, m_delimiter(';')
, m_delimiter(";")
{
init();
}
@ -216,6 +232,12 @@ TestReader::result_t TestReader::get_statement(std::string& stmt)
if (!line.empty() && (line.at(0) != '#'))
{
// Ignore comment lines.
if ((line.substr(0, 3) == "-- ") || (line.substr(0, 1) == "#"))
{
continue;
}
if (!skip)
{
if (line.substr(0, 2) == "--")
@ -228,7 +250,7 @@ TestReader::result_t TestReader::get_statement(std::string& stmt)
std::ptr_fun<int,int>(std::isspace));
string keyword = line.substr(0, i - line.begin());
skip_action_t action = get_action(keyword);
skip_action_t action = get_action(keyword, m_delimiter);
switch (action)
{
@ -244,7 +266,21 @@ TestReader::result_t TestReader::get_statement(std::string& stmt)
trim(line);
if (line.length() > 0)
{
m_delimiter = line.at(0);
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;
@ -268,15 +304,42 @@ TestReader::result_t TestReader::get_statement(std::string& stmt)
stmt += line;
char c = line.at(line.length() - 1);
// Look for a ';'. If we are dealing with a one line test statment
// the delimiter will in practice be ';' and if it is a multi-line
// test statement then the test-script delimiter will be something
// else than ';' and ';' will be the delimiter used in the multi-line
// statement.
string::size_type i = line.find(";");
if (i != string::npos)
{
// Is there a "-- " or "#" after the delimiter?
if ((line.find("-- ", i) != string::npos) ||
(line.find("#", i) != string::npos))
{
// If so, add a newline. Otherwise the rest of the
// statement would be included in the comment.
stmt += "\n";
}
// This is somewhat fragile as a ";", "#" or "-- " inside a
// string will trigger this behaviour...
}
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)

View File

@ -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. */
};
};

View File

@ -134,7 +134,7 @@ int main(int argc, char* argv[])
set_libdir(strdup(LIBDIR));
if (qc_setup(QC_LIB, NULL))
if (qc_setup(QC_LIB, QC_SQL_MODE_DEFAULT, NULL))
{
if (qc_process_init(QC_INIT_BOTH))
{

View File

@ -124,6 +124,7 @@ const char CN_SERVICE[] = "service";
const char CN_SESSIONS[] = "sessions";
const char CN_SKIP_PERMISSION_CHECKS[] = "skip_permission_checks";
const char CN_SOCKET[] = "socket";
const char CN_SQL_MODE[] = "sql_mode";
const char CN_STATE[] = "state";
const char CN_STATUS[] = "status";
const char CN_SSL[] = "ssl";
@ -1521,6 +1522,22 @@ handle_global_item(const char *name, const char *value)
{
gateway.qc_args = MXS_STRDUP_A(value);
}
else if (strcmp(name, "sql_mode") == 0)
{
if (strcasecmp(value, "default") == 0)
{
gateway.qc_sql_mode = QC_SQL_MODE_DEFAULT;
}
else if (strcasecmp(value, "oracle") == 0)
{
gateway.qc_sql_mode = QC_SQL_MODE_ORACLE;
}
else
{
MXS_ERROR("'%s' is not a valid value for '%s'. Allowed values are 'DEFAULT' and "
"'ORACLE'. Using 'DEFAULT' as default.", value, name);
}
}
else if (strcmp(name, CN_LOG_THROTTLING) == 0)
{
if (*value == 0)
@ -1882,6 +1899,8 @@ global_defaults()
/* query_classifier */
memset(gateway.qc_name, 0, sizeof(gateway.qc_name));
gateway.qc_args = NULL;
gateway.qc_sql_mode = QC_SQL_MODE_DEFAULT;
}
/**

View File

@ -2600,6 +2600,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
{

View File

@ -1889,7 +1889,7 @@ int main(int argc, char **argv)
cnf = config_get_global_options();
ss_dassert(cnf);
if (!qc_setup(cnf->qc_name, cnf->qc_args))
if (!qc_setup(cnf->qc_name, cnf->qc_sql_mode, cnf->qc_args))
{
const char* logerr = "Failed to initialise query classifier library.";
print_log_n_stderr(true, true, logerr, logerr, eno);

View File

@ -27,7 +27,7 @@ MXS_BEGIN_DECLS
#define SESSION_INIT {.ses_chk_top = CHK_NUM_SESSION, \
.stats = SESSION_STATS_INIT, .head = MXS_DOWNSTREAM_INIT, .tail = MXS_UPSTREAM_INIT, \
.state = SESSION_STATE_ALLOC, .ses_chk_tail = CHK_NUM_SESSION}
.state = SESSION_STATE_ALLOC, .client_protocol_data = 0, .ses_chk_tail = CHK_NUM_SESSION}
#define SESSION_PROTOCOL(x, type) DCB_PROTOCOL((x)->client_dcb, type)

View File

@ -14,7 +14,7 @@
#include <maxscale/cppdefs.hh>
#include <ctype.h>
#include <maxscale/modutil.h>
#include <maxscale/customparser.hh>
#include <maxscale/query_classifier.h>
namespace maxscale
@ -37,8 +37,11 @@ namespace maxscale
* of utmost importance; consequently it is defined in its entirety
* in the header to allow for aggressive inlining.
*/
class TrxBoundaryParser
class TrxBoundaryParser : public maxscale::CustomParser
{
TrxBoundaryParser(const TrxBoundaryParser&);
TrxBoundaryParser& operator = (const TrxBoundaryParser&);
public:
enum token_t
{
@ -88,10 +91,6 @@ public:
* @endcode
*/
TrxBoundaryParser()
: m_pSql(NULL)
, m_len(0)
, m_pI(NULL)
, m_pEnd(NULL)
{
}
@ -546,34 +545,6 @@ private:
return type_mask;
}
inline bool is_next_alpha(char uc, int offset = 1) const
{
ss_dassert(uc >= 'A' && uc <= 'Z');
char lc = uc + ('a' - 'A');
return
((m_pI + offset) < m_pEnd) &&
((*(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);
if (rc)
{
*pC = *(m_pI + 1);
}
return rc;
}
// Significantly faster than library version.
static char toupper(char c)
{
@ -730,7 +701,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 +741,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);
}
@ -830,16 +801,6 @@ private:
return token;
}
private:
TrxBoundaryParser(const TrxBoundaryParser&);
TrxBoundaryParser& operator = (const TrxBoundaryParser&);
private:
const char* m_pSql;
int m_len;
const char* m_pI;
const char* m_pEnd;
};
}

View File

@ -45,7 +45,7 @@ static QUERY_CLASSIFIER* classifier;
static qc_trx_parse_using_t qc_trx_parse_using = QC_TRX_PARSE_USING_PARSER;
bool qc_setup(const char* plugin_name, const char* plugin_args)
bool qc_setup(const char* plugin_name, qc_sql_mode_t sql_mode, const char* plugin_args)
{
QC_TRACE();
ss_dassert(!classifier);
@ -61,7 +61,7 @@ bool qc_setup(const char* plugin_name, const char* plugin_args)
if (classifier)
{
rv = classifier->qc_setup(plugin_args);
rv = classifier->qc_setup(sql_mode, plugin_args);
if (rv != QC_RESULT_OK)
{
@ -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";
}
@ -921,3 +927,25 @@ uint64_t qc_get_server_version()
return version;
}
qc_sql_mode_t qc_get_sql_mode()
{
QC_TRACE();
ss_dassert(classifier);
qc_sql_mode_t sql_mode;
ss_debug(int32_t rv =) classifier->qc_get_sql_mode(&sql_mode);
ss_dassert(rv == QC_RESULT_OK);
return sql_mode;
}
void qc_set_sql_mode(qc_sql_mode_t sql_mode)
{
QC_TRACE();
ss_dassert(classifier);
ss_debug(int32_t rv =) classifier->qc_set_sql_mode(sql_mode);
ss_dassert(rv == QC_RESULT_OK);
}

View File

@ -183,6 +183,12 @@ static MXS_SESSION* session_alloc_body(SERVICE* service, DCB* client_dcb,
session->stats.connect = time(0);
session->stmt.buffer = NULL;
session->stmt.target = NULL;
MXS_CONFIG *config = config_get_global_options();
// If MaxScale is running in Oracle mode, then autocommit needs to
// initially be off.
bool autocommit = (config->qc_sql_mode == QC_SQL_MODE_ORACLE) ? false : true;
session_set_autocommit(session, autocommit);
/*<
* Associate the session to the client DCB and set the reference count on
* the session to indicate that there is a single reference to the

View File

@ -184,7 +184,7 @@ int main(int argc, char* argv[])
if (mxs_log_init(NULL, ".", MXS_LOG_TARGET_DEFAULT))
{
// We have to setup something in order for the regexes to be compiled.
if (qc_setup("qc_sqlite", NULL) && qc_process_init(QC_INIT_BOTH))
if (qc_setup("qc_sqlite", QC_SQL_MODE_DEFAULT, NULL) && qc_process_init(QC_INIT_BOTH))
{
Tester tester(verbosity);

View File

@ -421,7 +421,7 @@ int main(int argc, char* argv[])
if (mxs_log_init(NULL, ".", MXS_LOG_TARGET_DEFAULT))
{
// We have to setup something in order for the regexes to be compiled.
if (qc_setup("qc_sqlite", NULL) && qc_process_init(QC_INIT_BOTH))
if (qc_setup("qc_sqlite", QC_SQL_MODE_DEFAULT, NULL) && qc_process_init(QC_INIT_BOTH))
{
rc = EXIT_SUCCESS;

View File

@ -245,8 +245,6 @@ void gw_str_xor(uint8_t *output, const uint8_t *input1, const uint8_t *input2, u
{
*output++ = *input1++ ^ *input2++;
}
*output = '\0';
}
/**********************************************************

View File

@ -128,7 +128,7 @@ int main(int argc, char* argv[])
{
if (mxs_log_init(NULL, ".", MXS_LOG_TARGET_DEFAULT))
{
if (qc_setup(NULL, NULL) && qc_process_init(QC_INIT_BOTH))
if (qc_setup(NULL, QC_SQL_MODE_DEFAULT, NULL) && qc_process_init(QC_INIT_BOTH))
{
const char* zModule = argv[1];

View File

@ -240,7 +240,7 @@ int main()
pConfig->n_threads = 1;
set_libdir(MXS_STRDUP_A("../../../../../query_classifier/qc_sqlite/"));
if (qc_setup("qc_sqlite", "") && qc_process_init(QC_INIT_BOTH))
if (qc_setup("qc_sqlite", QC_SQL_MODE_DEFAULT, "") && qc_process_init(QC_INIT_BOTH))
{
set_libdir(MXS_STRDUP_A("../"));
rc = test();

View File

@ -50,7 +50,7 @@ int TestStorage::run(int argc, char** argv)
{
if (mxs_log_init(NULL, ".", MXS_LOG_TARGET_DEFAULT))
{
if (qc_setup(NULL, NULL) && qc_process_init(QC_INIT_BOTH))
if (qc_setup(NULL, QC_SQL_MODE_DEFAULT, NULL) && qc_process_init(QC_INIT_BOTH))
{
const char* zModule = NULL;
size_t threads = m_threads;

View File

@ -120,6 +120,80 @@ typedef enum
RT_CLAUSE /*< WHERE-clause requirement rule */
} ruletype_t;
/**
* What operator a rule should apply to.
*
* Note that each operator is represented by a unique bit, so that they
* can be combined as a bitmask, while query_op_t enumeration of the query
* classifier consists of a sequence of unique numbers.
*/
typedef enum fw_op
{
FW_OP_UNDEFINED = 0,
// NOTE: If you add something here, check the 'qc_op_to_fw_op' function below.
FW_OP_ALTER = (1 << 0),
FW_OP_CHANGE_DB = (1 << 1),
FW_OP_CREATE = (1 << 2),
FW_OP_DELETE = (1 << 3),
FW_OP_DROP = (1 << 4),
FW_OP_GRANT = (1 << 5),
FW_OP_INSERT = (1 << 6),
FW_OP_LOAD = (1 << 7),
FW_OP_REVOKE = (1 << 8),
FW_OP_SELECT = (1 << 9),
FW_OP_UPDATE = (1 << 10),
} fw_op_t;
/**
* Convert a qc_query_op_t to the equivalent fw_op_t.
*
* @param op A query classifier operator.
*
* @return The corresponding bit value.
*/
static inline fw_op_t qc_op_to_fw_op(qc_query_op_t op)
{
switch (op)
{
case QUERY_OP_ALTER:
return FW_OP_ALTER;
case QUERY_OP_CHANGE_DB:
return FW_OP_CHANGE_DB;
case QUERY_OP_CREATE:
return FW_OP_CREATE;
case QUERY_OP_DELETE:
return FW_OP_DELETE;
case QUERY_OP_DROP:
return FW_OP_DROP;
case QUERY_OP_GRANT:
return FW_OP_GRANT;
case QUERY_OP_INSERT:
return FW_OP_INSERT;
case QUERY_OP_LOAD:
return FW_OP_LOAD;
case QUERY_OP_REVOKE:
return FW_OP_REVOKE;
case QUERY_OP_SELECT:
return FW_OP_SELECT;
case QUERY_OP_UPDATE:
return FW_OP_UPDATE;
default:
return FW_OP_UNDEFINED;
};
}
/**
* Possible actions to take when the query matches a rule
*/
@ -199,7 +273,7 @@ typedef struct rule_t
void* data; /*< Actual implementation of the rule */
char* name; /*< Name of the rule */
ruletype_t type; /*< Type of the rule */
qc_query_op_t on_queries; /*< Types of queries to inspect */
uint32_t on_queries; /*< Types of queries to inspect */
int times_matched; /*< Number of times this rule has been matched */
TIMERANGE* active; /*< List of times when this rule is active */
struct rule_t *next;
@ -543,47 +617,47 @@ bool parse_querytypes(const char* str, RULE* rule)
*dest = '\0';
if (strcmp(buffer, "select") == 0)
{
rule->on_queries |= QUERY_OP_SELECT;
rule->on_queries |= FW_OP_SELECT;
}
else if (strcmp(buffer, "insert") == 0)
{
rule->on_queries |= QUERY_OP_INSERT;
rule->on_queries |= FW_OP_INSERT;
}
else if (strcmp(buffer, "update") == 0)
{
rule->on_queries |= QUERY_OP_UPDATE;
rule->on_queries |= FW_OP_UPDATE;
}
else if (strcmp(buffer, "delete") == 0)
{
rule->on_queries |= QUERY_OP_DELETE;
rule->on_queries |= FW_OP_DELETE;
}
else if (strcmp(buffer, "use") == 0)
{
rule->on_queries |= QUERY_OP_CHANGE_DB;
rule->on_queries |= FW_OP_CHANGE_DB;
}
else if (strcmp(buffer, "grant") == 0)
{
rule->on_queries |= QUERY_OP_GRANT;
rule->on_queries |= FW_OP_GRANT;
}
else if (strcmp(buffer, "revoke") == 0)
{
rule->on_queries |= QUERY_OP_REVOKE;
rule->on_queries |= FW_OP_REVOKE;
}
else if (strcmp(buffer, "drop") == 0)
{
rule->on_queries |= QUERY_OP_DROP;
rule->on_queries |= FW_OP_DROP;
}
else if (strcmp(buffer, "create") == 0)
{
rule->on_queries |= QUERY_OP_CREATE;
rule->on_queries |= FW_OP_CREATE;
}
else if (strcmp(buffer, "alter") == 0)
{
rule->on_queries |= QUERY_OP_ALTER;
rule->on_queries |= FW_OP_ALTER;
}
else if (strcmp(buffer, "load") == 0)
{
rule->on_queries |= QUERY_OP_LOAD;
rule->on_queries |= FW_OP_LOAD;
}
if (done)
@ -1078,7 +1152,7 @@ bool create_rule(void* scanner, const char* name)
if (ruledef && (ruledef->name = MXS_STRDUP(name)))
{
ruledef->type = RT_UNDEFINED;
ruledef->on_queries = QUERY_OP_UNDEFINED;
ruledef->on_queries = FW_OP_UNDEFINED;
ruledef->next = rstack->rule;
ruledef->active = NULL;
ruledef->times_matched = 0;
@ -2113,10 +2187,10 @@ bool rule_matches(FW_INSTANCE* my_instance,
}
}
if (rulebook->rule->on_queries == QUERY_OP_UNDEFINED ||
rulebook->rule->on_queries & optype ||
if (rulebook->rule->on_queries == FW_OP_UNDEFINED ||
rulebook->rule->on_queries & qc_op_to_fw_op(optype) ||
(MYSQL_IS_COM_INIT_DB((uint8_t*)GWBUF_DATA(queue)) &&
rulebook->rule->on_queries & QUERY_OP_CHANGE_DB))
rulebook->rule->on_queries & FW_OP_CHANGE_DB))
{
switch (rulebook->rule->type)
{

View File

@ -1,4 +1,8 @@
add_library(MySQLClient SHARED mysql_client.c)
add_library(MySQLClient SHARED mysql_client.cc)
target_link_libraries(MySQLClient maxscale-common MySQLCommon)
set_target_properties(MySQLClient PROPERTIES VERSION "1.0.0")
install_module(MySQLClient core)
if(BUILD_TESTS)
add_subdirectory(test)
endif()

View File

@ -14,23 +14,26 @@
#define MXS_MODULE_NAME "MySQLClient"
#include <maxscale/cppdefs.hh>
#include <inttypes.h>
#include <limits.h>
#include <netinet/tcp.h>
#include <sys/stat.h>
#include <maxscale/protocol.h>
#include <netinet/tcp.h>
#include <sys/stat.h>
#include <maxscale/alloc.h>
#include <maxscale/authenticator.h>
#include <maxscale/log_manager.h>
#include <maxscale/protocol/mysql.h>
#include <maxscale/ssl.h>
#include <maxscale/poll.h>
#include <maxscale/modinfo.h>
#include <maxscale/modutil.h>
#include <maxscale/poll.h>
#include <maxscale/protocol/mysql.h>
#include <maxscale/query_classifier.h>
#include <maxscale/authenticator.h>
#include <maxscale/session.h>
#include <maxscale/worker.h>
#include <maxscale/ssl.h>
#include "setsqlmodeparser.hh"
/** Return type of process_special_commands() */
typedef enum spec_com_res_t
@ -86,6 +89,10 @@ static bool parse_kill_query(char *query, uint64_t *thread_id_out, kill_type_t *
*
* @return The module object
*/
extern "C"
{
MXS_MODULE* MXS_CREATE_MODULE()
{
static MXS_PROTOCOL MyObject =
@ -126,6 +133,8 @@ MXS_MODULE* MXS_CREATE_MODULE()
return &info;
}
}
/*lint +e14 */
/**
@ -186,7 +195,7 @@ static void thread_finish(void)
*/
static char *gw_default_auth()
{
return "MySQLAuth";
return (char*)"MySQLAuth";
}
/**
@ -242,7 +251,7 @@ int MySQLSendHandshake(DCB* dcb)
}
else
{
version_string = GW_MYSQL_VERSION;
version_string = (char*)GW_MYSQL_VERSION;
len_version_string = strlen(GW_MYSQL_VERSION);
}
@ -486,7 +495,7 @@ int gw_read_client_event(DCB* dcb)
case MXS_AUTH_STATE_MESSAGE_READ:
/* After this call read_buffer will point to freed data */
if (nbytes_read < 3 || (0 == max_bytes && nbytes_read <
(MYSQL_GET_PAYLOAD_LEN((uint8_t *) GWBUF_DATA(read_buffer)) + 4)) ||
(int)(MYSQL_GET_PAYLOAD_LEN((uint8_t *) GWBUF_DATA(read_buffer)) + 4)) ||
(0 != max_bytes && nbytes_read < max_bytes))
{
@ -671,7 +680,7 @@ gw_read_do_authentication(DCB *dcb, GWBUF *read_buffer, int nbytes_read)
if (dcb->user == NULL)
{
/** User authentication complete, copy the username to the DCB */
MYSQL_session *ses = dcb->data;
MYSQL_session *ses = (MYSQL_session*)dcb->data;
if ((dcb->user = MXS_STRDUP(ses->user)) == NULL)
{
dcb_close(dcb);
@ -691,6 +700,9 @@ gw_read_do_authentication(DCB *dcb, GWBUF *read_buffer, int nbytes_read)
MXS_SESSION *session =
session_alloc_with_id(dcb->service, dcb, protocol->thread_id);
// For the time being only the sql_mode is stored in MXS_SESSION::client_protocol_data.
session->client_protocol_data = QC_SQL_MODE_DEFAULT;
if (session != NULL)
{
CHK_SESSION(session);
@ -848,7 +860,7 @@ static bool process_client_commands(DCB* dcb, int bytes_available, GWBUF** buffe
if (dcb->protocol_packet_length - MYSQL_HEADER_LEN != GW_MYSQL_MAX_PACKET_LEN)
{
/** We're processing the first packet of a command */
proto->current_command = cmd;
proto->current_command = (mysql_server_cmd_t)cmd;
}
dcb->protocol_packet_length = pktlen + MYSQL_HEADER_LEN;
@ -870,6 +882,55 @@ static bool process_client_commands(DCB* dcb, int bytes_available, GWBUF** buffe
return true;
}
/**
* Sets the query classifier mode.
*
* @param session The session for which the query classifier mode is adjusted.
* @param read_buffer Pointer to a buffer, assumed to contain a statement.
* May be reallocated if not contiguous.
*/
void set_qc_mode(MXS_SESSION* session, GWBUF** read_buffer)
{
SetSqlModeParser parser;
SetSqlModeParser::sql_mode_t sql_mode;
switch (parser.get_sql_mode(read_buffer, &sql_mode))
{
case SetSqlModeParser::ERROR:
// In practice only OOM.
break;
case SetSqlModeParser::IS_SET_SQL_MODE:
switch (sql_mode)
{
case SetSqlModeParser::ORACLE:
session_set_autocommit(session, false);
session->client_protocol_data = QC_SQL_MODE_ORACLE;
break;
case SetSqlModeParser::DEFAULT:
session_set_autocommit(session, true);
session->client_protocol_data = QC_SQL_MODE_DEFAULT;
break;
case SetSqlModeParser::SOMETHING:
break;
default:
ss_dassert(!true);
}
break;
case SetSqlModeParser::NOT_SET_SQL_MODE:
break;
default:
ss_dassert(!true);
}
qc_set_sql_mode(static_cast<qc_sql_mode_t>(session->client_protocol_data));
}
/**
* @brief Client read event, process data, client already authenticated
*
@ -921,11 +982,13 @@ gw_read_normal_data(DCB *dcb, GWBUF *read_buffer, int nbytes_read)
if (rcap_type_required(capabilities, RCAP_TYPE_STMT_INPUT))
{
if (nbytes_read < 3 || nbytes_read <
(MYSQL_GET_PAYLOAD_LEN((uint8_t *) GWBUF_DATA(read_buffer)) + 4))
(int)(MYSQL_GET_PAYLOAD_LEN((uint8_t *) GWBUF_DATA(read_buffer)) + 4))
{
dcb->dcb_readqueue = read_buffer;
return 0;
}
set_qc_mode(session, &read_buffer);
}
/** The query classifier classifies according to the service's server that has
@ -1049,7 +1112,7 @@ mysql_client_auth_error_handling(DCB *dcb, int auth_val, int packet_number)
/** Send error 1049 to client */
message_len = 25 + MYSQL_DATABASE_MAXLEN;
fail_str = MXS_CALLOC(1, message_len + 1);
fail_str = (char*)MXS_CALLOC(1, message_len + 1);
MXS_ABORT_IF_NULL(fail_str);
snprintf(fail_str, message_len, "Unknown database '%s'", session->db);
@ -1381,7 +1444,7 @@ static int route_by_statement(MXS_SESSION* session, uint64_t capabilities, GWBUF
{
CHK_GWBUF(packetbuf);
MySQLProtocol* proto = session->client_dcb->protocol;
MySQLProtocol* proto = (MySQLProtocol*)session->client_dcb->protocol;
proto->current_command = (mysql_server_cmd_t)GWBUF_DATA(packetbuf)[4];
/**
@ -1458,9 +1521,9 @@ static int route_by_statement(MXS_SESSION* session, uint64_t capabilities, GWBUF
}
else if ((type & QUERY_TYPE_COMMIT) || (type & QUERY_TYPE_ROLLBACK))
{
mxs_session_trx_state_t trx_state = session_get_trx_state(session);
uint32_t trx_state = session_get_trx_state(session);
trx_state |= SESSION_TRX_ENDING_BIT;
session_set_trx_state(session, trx_state);
session_set_trx_state(session, (mxs_session_trx_state_t)trx_state);
if (type & QUERY_TYPE_ENABLE_AUTOCOMMIT)
{
@ -1523,7 +1586,7 @@ static bool ensure_complete_packet(DCB *dcb, GWBUF **read_buffer, int nbytes_rea
{
uint8_t* data = (uint8_t *) GWBUF_DATA(*read_buffer);
if (nbytes_read < 3 || nbytes_read < MYSQL_GET_PAYLOAD_LEN(data) + 4)
if (nbytes_read < 3 || nbytes_read < (int)MYSQL_GET_PAYLOAD_LEN(data) + 4)
{
dcb->dcb_readqueue = gwbuf_append(dcb->dcb_readqueue, *read_buffer);
return false;
@ -1559,7 +1622,7 @@ static spec_com_res_t process_special_commands(DCB *dcb, GWBUF *read_buffer, int
* The option is stored as a two byte integer with the values 0 for enabling
* multi-statements and 1 for disabling it.
*/
MySQLProtocol *proto = dcb->protocol;
MySQLProtocol *proto = (MySQLProtocol*)dcb->protocol;
uint8_t opt;
if (proto->current_command == MYSQL_COM_SET_OPTION &&

View File

@ -0,0 +1,651 @@
#pragma once
/*
* Copyright (c) 2016 MariaDB Corporation Ab
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file and at www.mariadb.com/bsl11.
*
* Change Date: 2019-07-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2 or later of the General
* Public License.
*/
#include <maxscale/cppdefs.hh>
#include <maxscale/customparser.hh>
#include <maxscale/protocol/mysql.h>
class SetSqlModeParser : public maxscale::CustomParser
{
public:
enum sql_mode_t
{
DEFAULT, // "set sql_mode=DEFAULT"
ORACLE, // "set sql_mode=ORACLE", "set sql_mode='PIPES_AS_CONCAT,ORACLE', autocommit=false", etc.
SOMETHING // "set sql_mode=PIPES_AS_CONCAT"
};
enum result_t
{
ERROR, // Some fatal error occurred; mem alloc failed, parsing failed, etc.
IS_SET_SQL_MODE, // The COM_QUERY is "set sql_mode=..."
NOT_SET_SQL_MODE // The COM_QUERY is NOT "set sql_mode=..."
};
enum
{
UNUSED_FIRST = 0xFF,
TK_DEFAULT,
TK_GLOBAL,
TK_GLOBAL_VAR,
TK_ORACLE,
TK_SESSION,
TK_SESSION_VAR,
TK_SET,
TK_SQL_MODE,
};
SetSqlModeParser()
{
}
/**
* Return whether the statement is a "SET SQL_MODE=" statement and if so,
* whether the state is ORACLE, DEFAULT or something else.
*
* @param ppBuffer Address of pointer to buffer containing statement.
* The GWBUF must contain a complete statement, but the
* buffer need not be contiguous.
* @param pSql_mode Pointer to variable receiving the sql_mode state, if
* the statement is a "SET SQL_MODE=" statement.
*
* @return ERROR if a fatal error occurred during parsing
* IS_SET_SQL_MODE if the statement is a "SET SQL_MODE=" statement
* NOT_SET_SQL_MODE if the statement is not a "SET SQL_MODE="
* statement
*
* @attention If the result cannot be deduced without parsing the statement,
* then the buffer will be made contiguous and the value of
* @c *ppBuffer will be updated accordingly.
*/
result_t get_sql_mode(GWBUF** ppBuffer, sql_mode_t* pSql_mode)
{
result_t rv = NOT_SET_SQL_MODE;
GWBUF* pBuffer = *ppBuffer;
ss_dassert(gwbuf_length(pBuffer) >= MYSQL_HEADER_LEN);
size_t buf_len = GWBUF_LENGTH(pBuffer);
size_t payload_len;
if (buf_len >= MYSQL_HEADER_LEN)
{
// The first buffer in the chain contains the header so we
// can read the length directly.
payload_len = MYSQL_GET_PAYLOAD_LEN(GWBUF_DATA(pBuffer));
}
else
{
// The first buffer in the chain does not contain the full
// header so we need to copy it first.
uint8_t header[MYSQL_HEADER_LEN];
gwbuf_copy_data(pBuffer, 0, sizeof(header), header);
payload_len = MYSQL_GET_PAYLOAD_LEN(header);
}
if (payload_len >= 20) // sizeof(command byte) + strlen("SET sql_mode=ORACLE"), the minimum needed.
{
// We need 4 bytes from the payload to deduce whether more investigations are needed.
uint8_t payload[4];
uint8_t* pPayload;
if (buf_len >= MYSQL_HEADER_LEN + sizeof(payload))
{
// Enough data in the first buffer of the chain, we can access directly.
pPayload = GWBUF_DATA(pBuffer) + MYSQL_HEADER_LEN;
}
else
{
// Not enough, we copy what we need.
gwbuf_copy_data(pBuffer, MYSQL_HEADER_LEN, sizeof(payload), payload);
pPayload = payload;
}
uint8_t command = pPayload[0];
if (command == MYSQL_COM_QUERY)
{
const uint8_t* pStmt = &pPayload[1];
if (is_alpha(*pStmt))
{
// First character is alphabetic, we can check whether it is "SET".
if (is_set(pStmt))
{
// It is, so we must parse further and must therefore ensure that
// the buffer is contiguous. We get the same buffer back if it
// already is.
pBuffer = gwbuf_make_contiguous(*ppBuffer);
if (pBuffer)
{
*ppBuffer = pBuffer;
initialize(pBuffer);
rv = parse(pSql_mode);
}
else
{
rv = ERROR;
}
}
}
else
{
// If the first character is not an alphabetic character we assume there
// is a comment and make the buffer contiguous to make it possible to
// efficiently bypass the whitespace.
pBuffer = gwbuf_make_contiguous(*ppBuffer);
if (pBuffer)
{
*ppBuffer = pBuffer;
initialize(pBuffer);
bypass_whitespace();
if (is_set(m_pI))
{
rv = parse(pSql_mode);
}
}
else
{
rv = ERROR;
}
}
}
}
return rv;
}
/**
* Returns a @c sql_mode_t as a string.
*
* @param sql_mode An SQL mode.
*
* @return The corresponding string.
*/
static const char* to_string(sql_mode_t sql_mode)
{
switch (sql_mode)
{
case DEFAULT:
return "DEFAULT";
case ORACLE:
return "ORACLE";
case SOMETHING:
return "SOMETHING";
default:
ss_dassert(!true);
return "UNKNOWN";
}
}
/**
* Returns a @c result_t as a string.
*
* @param result_t A result.
*
* @return The corresponding string.
*/
static const char* to_string(result_t result)
{
switch (result)
{
case ERROR:
return "ERROR";
case IS_SET_SQL_MODE:
return "IS_SET_SQL_MODE";
case NOT_SET_SQL_MODE:
return "NOT_SET_SQL_MODE";
default:
ss_dassert(!true);
return "UNKNOWN";
}
}
private:
static bool is_set(const char* pStmt)
{
return
(pStmt[0] == 's' || pStmt[0] == 'S') &&
(pStmt[1] == 'e' || pStmt[1] == 'E') &&
(pStmt[2] == 't' || pStmt[2] == 'T');
}
static bool is_set(const uint8_t* pStmt)
{
return is_set(reinterpret_cast<const char*>(pStmt));
}
static bool is_error(result_t rv)
{
return (rv == ERROR);
}
result_t initialize(GWBUF* pBuffer)
{
ss_dassert(GWBUF_IS_CONTIGUOUS(pBuffer));
result_t rv = ERROR;
char* pSql;
if (modutil_extract_SQL(pBuffer, &pSql, &m_len))
{
m_pSql = pSql;
m_pI = m_pSql;
m_pEnd = m_pI + m_len;
}
return ERROR;
}
bool consume_id()
{
// Consumes "[a-zA-Z]([a-zA-Z0-9_])*
bool rv = false;
if (is_alpha(*m_pI))
{
rv = true;
++m_pI;
while ((m_pI < m_pEnd) && (is_alpha(*m_pI) || is_number(*m_pI) || (*m_pI == '_')))
{
++m_pI;
}
}
return rv;
}
void consume_value()
{
// Consumes everything until a ',' outside of a commented string, or eol is
// encountered.
bool rv = false;
bool consumed = false;
while ((m_pI < m_pEnd) && (*m_pI != ','))
{
switch (*m_pI)
{
case '\'':
case '"':
case '`':
{
char quote = *m_pI;
++m_pI;
while ((m_pI < m_pEnd) && (*m_pI != quote))
{
++m_pI;
}
}
break;
default:
++m_pI;
}
}
}
result_t parse(sql_mode_t* pSql_mode)
{
result_t rv = NOT_SET_SQL_MODE;
token_t token = next_token();
switch (token)
{
case TK_SET:
rv = parse_set(pSql_mode);
break;
case PARSER_EXHAUSTED:
log_exhausted();
break;
case PARSER_UNKNOWN_TOKEN:
default:
log_unexpected();
break;
}
return rv;
}
result_t parse_set(sql_mode_t* pSql_mode)
{
result_t rv = NOT_SET_SQL_MODE;
char c;
do
{
token_t token = next_token();
switch (token)
{
case TK_GLOBAL:
rv = parse_set(pSql_mode);
break;
case TK_SESSION:
rv = parse_set(pSql_mode);
break;
case TK_GLOBAL_VAR:
case TK_SESSION_VAR:
if (next_token() == '.')
{
rv = parse_set(pSql_mode);
}
else
{
rv = ERROR;
}
break;
case TK_SQL_MODE:
if (next_token() == '=')
{
rv = parse_set_sql_mode(pSql_mode);
}
else
{
rv = ERROR;
}
break;
case PARSER_EXHAUSTED:
log_exhausted();
rv = ERROR;
break;
case PARSER_UNKNOWN_TOKEN:
// Might be something like "SET A=B, C=D, SQL_MODE=ORACLE", so we first consume
// the identifier and if it is followed by a "=" we consume the value.
{
char c;
if (consume_id())
{
bypass_whitespace();
if (peek_current_char(&c) && (c == '='))
{
++m_pI;
consume_value();
}
}
else
{
log_unexpected();
rv = ERROR;
}
}
break;
default:
log_unexpected();
rv = ERROR;
break;
}
c = 0;
if (rv != ERROR)
{
bypass_whitespace();
if (peek_current_char(&c))
{
if (c == ',')
{
++m_pI;
}
else
{
c = 0;
}
}
else
{
c = 0;
}
}
}
while (c == ',');
return rv;
}
result_t parse_set_sql_mode(sql_mode_t* pSql_mode)
{
result_t rv = IS_SET_SQL_MODE;
token_t token = next_token();
switch (token)
{
case '\'':
case '"':
case '`':
rv = parse_set_sql_mode_string(token, pSql_mode);
break;
case TK_DEFAULT:
*pSql_mode = DEFAULT;
break;
case TK_ORACLE:
*pSql_mode = ORACLE;
break;
case PARSER_UNKNOWN_TOKEN:
if (consume_id())
{
*pSql_mode = SOMETHING;
}
else
{
rv = ERROR;
}
break;
default:
rv = ERROR;
}
return rv;
}
result_t parse_set_sql_mode_string(char quote, sql_mode_t* pSql_mode)
{
result_t rv = IS_SET_SQL_MODE;
char c;
do
{
rv = parse_set_sql_mode_setting(pSql_mode);
if (!is_error(rv))
{
bypass_whitespace();
if (peek_current_char(&c) && (c == ','))
{
++m_pI;
}
}
}
while (!is_error(rv) && (c == ','));
return rv;
}
result_t parse_set_sql_mode_setting(sql_mode_t* pSql_mode)
{
result_t rv = IS_SET_SQL_MODE;
token_t token = next_token();
switch (token)
{
case TK_ORACLE:
*pSql_mode = ORACLE;
break;
case PARSER_UNKNOWN_TOKEN:
if (consume_id())
{
*pSql_mode = SOMETHING;
}
else
{
rv = ERROR;
}
break;
case PARSER_EXHAUSTED:
log_exhausted();
rv = ERROR;
break;
default:
log_unexpected();
rv = ERROR;
}
return rv;
}
token_t next_token(token_required_t required = TOKEN_NOT_REQUIRED)
{
token_t token = PARSER_UNKNOWN_TOKEN;
bypass_whitespace();
if (m_pI == m_pEnd)
{
token = PARSER_EXHAUSTED;
}
else if (*m_pI == ';')
{
++m_pI;
while ((m_pI != m_pEnd) && isspace(*m_pI))
{
++m_pI;
}
if (m_pI != m_pEnd)
{
MXS_WARNING("Non-space data found after semi-colon: '%.*s'.",
(int)(m_pEnd - m_pI), m_pI);
}
token = PARSER_EXHAUSTED;
}
else
{
switch (*m_pI)
{
case '@':
if (is_next_alpha('S', 2))
{
token = expect_token(MXS_CP_EXPECT_TOKEN("@@SESSION"), TK_SESSION_VAR);
}
else if (is_next_alpha('G', 2))
{
token = expect_token(MXS_CP_EXPECT_TOKEN("@@GLOBAL"), TK_GLOBAL_VAR);
}
else if (is_next_alpha('L', 2))
{
token = expect_token(MXS_CP_EXPECT_TOKEN("@@LOCAL"), TK_SESSION_VAR);
}
break;
case '.':
case '\'':
case '"':
case '`':
case ',':
case '=':
token = *m_pI;
++m_pI;
break;
case 'd':
case 'D':
token = expect_token(MXS_CP_EXPECT_TOKEN("DEFAULT"), TK_DEFAULT);
break;
case 'g':
case 'G':
token = expect_token(MXS_CP_EXPECT_TOKEN("GLOBAL"), TK_GLOBAL);
break;
case 'l':
case 'L':
token = expect_token(MXS_CP_EXPECT_TOKEN("LOCAL"), TK_SESSION);
break;
case 'o':
case 'O':
token = expect_token(MXS_CP_EXPECT_TOKEN("ORACLE"), TK_ORACLE);
break;
case 's':
case 'S':
if (is_next_alpha('E'))
{
if (is_next_alpha('S', 2))
{
token = expect_token(MXS_CP_EXPECT_TOKEN("SESSION"), TK_SESSION);
}
else
{
token = expect_token(MXS_CP_EXPECT_TOKEN("SET"), TK_SET);
}
}
else if (is_next_alpha('Q'))
{
token = expect_token(MXS_CP_EXPECT_TOKEN("SQL_MODE"), TK_SQL_MODE);
}
break;
default:
;
}
}
if ((token == PARSER_EXHAUSTED) && (required == TOKEN_REQUIRED))
{
log_exhausted();
}
return token;
}
};

View File

@ -0,0 +1,4 @@
add_executable(test_setsqlmodeparser test_setsqlmodeparser.cc)
target_link_libraries(test_setsqlmodeparser maxscale-common)
add_test(test_setsqlmodeparser test_setsqlmodeparser)

View File

@ -0,0 +1,361 @@
/*
* Copyright (c) 2016 MariaDB Corporation Ab
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file and at www.mariadb.com/bsl11.
*
* Change Date: 2019-07-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2 or later of the General
* Public License.
*/
#include "../setsqlmodeparser.hh"
#include <stdlib.h>
#include <time.h>
#include <iostream>
#include <maxscale/buffer.h>
#include <maxscale/paths.h>
using namespace std;
namespace
{
GWBUF* gwbuf_create_com_query(const char* zStmt)
{
size_t len = strlen(zStmt);
size_t payload_len = len + 1;
size_t gwbuf_len = MYSQL_HEADER_LEN + payload_len;
GWBUF* pBuf = gwbuf_alloc(gwbuf_len);
*((unsigned char*)((char*)GWBUF_DATA(pBuf))) = payload_len;
*((unsigned char*)((char*)GWBUF_DATA(pBuf) + 1)) = (payload_len >> 8);
*((unsigned char*)((char*)GWBUF_DATA(pBuf) + 2)) = (payload_len >> 16);
*((unsigned char*)((char*)GWBUF_DATA(pBuf) + 3)) = 0x00;
*((unsigned char*)((char*)GWBUF_DATA(pBuf) + 4)) = 0x03;
memcpy((char*)GWBUF_DATA(pBuf) + 5, zStmt, len);
return pBuf;
}
}
namespace
{
typedef SetSqlModeParser P;
struct TEST_CASE
{
const char* zStmt;
SetSqlModeParser::result_t result;
SetSqlModeParser::sql_mode_t sql_mode;
} test_cases[] =
{
{
"SET SQL_MODE=DEFAULT",
P::IS_SET_SQL_MODE,
P::DEFAULT
},
{
"SET SQL_MODE=DEFAULT;",
P::IS_SET_SQL_MODE,
P::DEFAULT
},
{
"SET SQL_MODE=DEFAULT; ",
P::IS_SET_SQL_MODE,
P::DEFAULT
},
{
"-- This is a comment\nSET SQL_MODE=DEFAULT",
P::IS_SET_SQL_MODE,
P::DEFAULT
},
{
"#This is a comment\nSET SQL_MODE=DEFAULT",
P::IS_SET_SQL_MODE,
P::DEFAULT
},
{
"/*blah*/ SET /*blah*/ SQL_MODE /*blah*/ = /*blah*/ DEFAULT /*blah*/ ",
P::IS_SET_SQL_MODE,
P::DEFAULT
},
{
"SET SQL_MODE=ORACLE",
P::IS_SET_SQL_MODE,
P::ORACLE
},
{
"SET SQL_MODE=BLAH", // So short that it cannot be DEFAULT|ORACLE
P::NOT_SET_SQL_MODE,
P::ORACLE
},
{
"SET SQL_MODE='BLAH'",
P::IS_SET_SQL_MODE,
P::SOMETHING
},
{
"SET SQL_MODE=BLAHBLAH",
P::IS_SET_SQL_MODE,
P::SOMETHING
},
{
"SET SQL_MODE='ORACLE'",
P::IS_SET_SQL_MODE,
P::ORACLE
},
{
"SET SQL_MODE='BLAH, A, B, ORACLE'",
P::IS_SET_SQL_MODE,
P::ORACLE
},
{
"SET SQL_MODE='BLAH, A, B, XYZ_123'",
P::IS_SET_SQL_MODE,
P::SOMETHING
},
{
"SET VAR1=1234, VAR2=3456, SQL_MODE='A,B, ORACLE'",
P::IS_SET_SQL_MODE,
P::ORACLE
},
{
"SET SQL_MODE=ORACLE, VAR1=3456, VAR2='A=b, c=d', SQL_MODE='A,B, ORACLE'",
P::IS_SET_SQL_MODE,
P::ORACLE
},
{
"SET GLOBAL SQL_MODE=ORACLE",
P::IS_SET_SQL_MODE,
P::ORACLE
},
{
"SET SESSION SQL_MODE=ORACLE",
P::IS_SET_SQL_MODE,
P::ORACLE
},
{
"SET LOCAL SQL_MODE=ORACLE",
P::IS_SET_SQL_MODE,
P::ORACLE
},
{
"SET @@GLOBAL.SQL_MODE=ORACLE",
P::IS_SET_SQL_MODE,
P::ORACLE
},
{
"SET @@SESSION.SQL_MODE=ORACLE",
P::IS_SET_SQL_MODE,
P::ORACLE
},
{
"SET @@LOCAL.SQL_MODE=ORACLE",
P::IS_SET_SQL_MODE,
P::ORACLE
},
{
"SET @@LOCAL . SQL_MODE = ORACLE",
P::IS_SET_SQL_MODE,
P::ORACLE
},
{
"SET @@SESSION.blah = 1234, @@GLOBAL.blahblah = something, sql_mode=ORACLE",
P::IS_SET_SQL_MODE,
P::ORACLE
},
};
const int N_TEST_CASES = sizeof(test_cases)/sizeof(test_cases[0]);
int test(GWBUF** ppStmt,
SetSqlModeParser::sql_mode_t expected_sql_mode,
SetSqlModeParser::result_t expected_result)
{
int rv = EXIT_SUCCESS;
SetSqlModeParser parser;
SetSqlModeParser::sql_mode_t sql_mode;
SetSqlModeParser::result_t result = parser.get_sql_mode(ppStmt, &sql_mode);
if (result == expected_result)
{
if (result == SetSqlModeParser::IS_SET_SQL_MODE)
{
if (sql_mode == expected_sql_mode)
{
cout << "OK";
}
else
{
cout << "ERROR: Expected "
<< "'" << SetSqlModeParser::to_string(expected_sql_mode) << "'"
<< ", got "
<< "'" << SetSqlModeParser::to_string(sql_mode) << "'"
<< ".";
rv = EXIT_FAILURE;
}
}
else
{
cout << "OK";
}
}
else
{
cout << "ERROR: Expected "
<< "'" << SetSqlModeParser::to_string(expected_result) << "'"
<< ", got "
<< "'" << SetSqlModeParser::to_string(result) << "'"
<< ".";
rv = EXIT_FAILURE;
}
cout << endl;
return rv;
}
int test(const TEST_CASE& test_case)
{
int rv = EXIT_SUCCESS;
cout << test_case.zStmt << ": ";
GWBUF* pStmt = gwbuf_create_com_query(test_case.zStmt);
ss_dassert(pStmt);
rv = test(&pStmt, test_case.sql_mode, test_case.result);
gwbuf_free(pStmt);
return rv;
}
int test_contiguous()
{
int rv = EXIT_SUCCESS;
cout << "Test contiguous statements\n"
<< "--------------------------" << endl;
for (int i = 0; i < N_TEST_CASES; ++i)
{
if (test(test_cases[i]) == EXIT_FAILURE)
{
rv = EXIT_FAILURE;
}
}
cout << endl;
return rv;
}
int test_non_contiguous()
{
int rv = EXIT_SUCCESS;
cout << "Test non-contiguous statements\n"
<< "------------------------------" << endl;
for (int i = 0; i < N_TEST_CASES; ++i)
{
TEST_CASE& test_case = test_cases[i];
cout << test_case.zStmt << "(" << strlen(test_case.zStmt) << ": ";
GWBUF* pTail = gwbuf_create_com_query(test_case.zStmt);
ss_dassert(pTail);
GWBUF* pStmt = NULL;
while (pTail)
{
size_t n = MYSQL_HEADER_LEN + rand() % 10; // Between 4 and 13 bytes long chunks.
GWBUF* pHead = gwbuf_split(&pTail, n);
cout << GWBUF_LENGTH(pHead);
pStmt = gwbuf_append(pStmt, pHead);
if (pTail)
{
cout << ", ";
}
}
cout << "): " << flush;
if (test(&pStmt, test_case.sql_mode, test_case.result) == EXIT_FAILURE)
{
rv = EXIT_FAILURE;
}
gwbuf_free(pStmt);
}
cout << endl;
return rv;
}
int test()
{
int rv = EXIT_SUCCESS;
if (test_contiguous() != EXIT_SUCCESS)
{
rv = EXIT_FAILURE;
}
if (test_non_contiguous() != EXIT_SUCCESS)
{
rv = EXIT_FAILURE;
}
if (rv == EXIT_SUCCESS)
{
cout << "OK" << endl;
}
else
{
cout << "ERROR" << endl;
}
return rv;
}
}
int main(int argc, char* argv[])
{
int rv = EXIT_SUCCESS;
srand(time(NULL));
set_datadir(strdup("/tmp"));
set_langdir(strdup("."));
set_process_datadir(strdup("/tmp"));
if (mxs_log_init(NULL, ".", MXS_LOG_TARGET_DEFAULT))
{
rv = test();
mxs_log_finish();
}
else
{
cerr << "error: Could not initialize log." << endl;
}
return rv;
}

View File

@ -1,4 +1,4 @@
add_executable(test_parse_kill test_parse_kill.c)
add_executable(test_parse_kill test_parse_kill.cc)
target_link_libraries(test_parse_kill maxscale-common MySQLCommon)
add_test(test_parse_kill test_parse_kill)

View File

@ -1,8 +1,8 @@
#include <maxscale/cdefs.h>
#include "../MySQLClient/mysql_client.c"
#include "../MySQLClient/mysql_client.cc"
int test_one_query(char *query, bool should_succeed, uint64_t expected_tid,
int test_one_query(const char *query, bool should_succeed, uint64_t expected_tid,
kill_type_t expected_kt)
{
char *query_copy = MXS_STRDUP_A(query);
@ -46,7 +46,7 @@ int test_one_query(char *query, bool should_succeed, uint64_t expected_tid,
}
typedef struct test_t
{
char *query;
const char *query;
bool should_succeed;
uint64_t correct_id;
kill_type_t correct_kt;
@ -79,7 +79,7 @@ int main(int argc, char **argv)
int arr_size = sizeof(tests) / sizeof(test_t);
for (int i = 0; i < arr_size; i++)
{
char *query = tests[i].query;
const char *query = tests[i].query;
bool should_succeed = tests[i].should_succeed;
uint64_t expected_tid = tests[i].correct_id;
kill_type_t expected_kt = tests[i].correct_kt;

View File

@ -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

View File

@ -911,6 +911,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
@ -1020,7 +1029,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))
{
@ -1053,7 +1071,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)
{

View File

@ -131,13 +131,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;
@ -164,10 +164,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;
@ -294,7 +294,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)
{

View File

@ -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

View File

@ -310,6 +310,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);