Merge branch '2.1-oracle-compat' into develop-new-merge-oracle
This commit is contained in:
commit
6cd6ded3d8
@ -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)
|
||||
|
@ -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
|
||||
|
65
Documentation/Release-Notes/MaxScale-2.1.4-Release-Notes.md
Normal file
65
Documentation/Release-Notes/MaxScale-2.1.4-Release-Notes.md
Normal 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).
|
@ -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).
|
||||
|
@ -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);
|
||||
|
@ -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()
|
||||
|
@ -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 */
|
||||
|
238
include/maxscale/customparser.hh
Normal file
238
include/maxscale/customparser.hh
Normal 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;
|
||||
};
|
||||
|
||||
}
|
@ -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.
|
||||
*
|
||||
|
@ -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 */
|
||||
|
@ -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
|
||||
|
@ -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 =
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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");
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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";
|
||||
|
||||
|
11
query_classifier/test/oracle.test
Normal file
11
query_classifier/test/oracle.test
Normal 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.
|
37
query_classifier/test/oracle/binlog_stm_ps.test
Normal file
37
query_classifier/test/oracle/binlog_stm_ps.test
Normal 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;
|
196
query_classifier/test/oracle/binlog_stm_sp.test
Normal file
196
query_classifier/test/oracle/binlog_stm_sp.test
Normal 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;
|
457
query_classifier/test/oracle/exception.test
Normal file
457
query_classifier/test/oracle/exception.test
Normal 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;
|
9
query_classifier/test/oracle/func_case.test
Normal file
9
query_classifier/test/oracle/func_case.test
Normal 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');
|
116
query_classifier/test/oracle/func_concat.test
Normal file
116
query_classifier/test/oracle/func_concat.test
Normal 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;
|
21
query_classifier/test/oracle/func_decode.test
Normal file
21
query_classifier/test/oracle/func_decode.test
Normal 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;
|
20
query_classifier/test/oracle/func_length.test
Normal file
20
query_classifier/test/oracle/func_length.test
Normal 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');
|
346
query_classifier/test/oracle/func_misc.test
Normal file
346
query_classifier/test/oracle/func_misc.test
Normal 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
|
10
query_classifier/test/oracle/misc.test
Normal file
10
query_classifier/test/oracle/misc.test
Normal 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;
|
266
query_classifier/test/oracle/ps.test
Normal file
266
query_classifier/test/oracle/ps.test
Normal 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 #
|
43
query_classifier/test/oracle/sequence.test
Normal file
43
query_classifier/test/oracle/sequence.test
Normal 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;
|
244
query_classifier/test/oracle/sp-anonymous.test
Normal file
244
query_classifier/test/oracle/sp-anonymous.test
Normal 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;
|
1057
query_classifier/test/oracle/sp-code.test
Normal file
1057
query_classifier/test/oracle/sp-code.test
Normal file
File diff suppressed because it is too large
Load Diff
295
query_classifier/test/oracle/sp-cursor-decl.test
Normal file
295
query_classifier/test/oracle/sp-cursor-decl.test
Normal 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;
|
1425
query_classifier/test/oracle/sp-cursor-rowtype.test
Normal file
1425
query_classifier/test/oracle/sp-cursor-rowtype.test
Normal file
File diff suppressed because it is too large
Load Diff
954
query_classifier/test/oracle/sp-cursor.test
Normal file
954
query_classifier/test/oracle/sp-cursor.test
Normal 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
|
872
query_classifier/test/oracle/sp-goto.test
Normal file
872
query_classifier/test/oracle/sp-goto.test
Normal 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;
|
9
query_classifier/test/oracle/sp-param.inc
Normal file
9
query_classifier/test/oracle/sp-param.inc
Normal 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;
|
37
query_classifier/test/oracle/sp-param.test
Normal file
37
query_classifier/test/oracle/sp-param.test
Normal 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
|
6
query_classifier/test/oracle/sp-row-vs-var.inc
Normal file
6
query_classifier/test/oracle/sp-row-vs-var.inc
Normal 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;
|
2290
query_classifier/test/oracle/sp-row.test
Normal file
2290
query_classifier/test/oracle/sp-row.test
Normal file
File diff suppressed because it is too large
Load Diff
346
query_classifier/test/oracle/sp-security.test
Normal file
346
query_classifier/test/oracle/sp-security.test
Normal 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 #
|
2133
query_classifier/test/oracle/sp.test
Normal file
2133
query_classifier/test/oracle/sp.test
Normal file
File diff suppressed because it is too large
Load Diff
106
query_classifier/test/oracle/trigger.test
Normal file
106
query_classifier/test/oracle/trigger.test
Normal 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;
|
16
query_classifier/test/oracle/truncate.test
Normal file
16
query_classifier/test/oracle/truncate.test
Normal 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;
|
4
query_classifier/test/oracle/type_blob.test
Normal file
4
query_classifier/test/oracle/type_blob.test
Normal file
@ -0,0 +1,4 @@
|
||||
SET sql_mode=ORACLE;
|
||||
CREATE TABLE t1 (a BLOB);
|
||||
SHOW CREATE TABLE t1;
|
||||
DROP TABLE t1;
|
10
query_classifier/test/oracle/type_clob.test
Normal file
10
query_classifier/test/oracle/type_clob.test
Normal 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;
|
4
query_classifier/test/oracle/type_date.test
Normal file
4
query_classifier/test/oracle/type_date.test
Normal file
@ -0,0 +1,4 @@
|
||||
SET sql_mode=ORACLE;
|
||||
CREATE TABLE t1 (a DATE);
|
||||
SHOW CREATE TABLE t1;
|
||||
DROP TABLE t1;
|
9
query_classifier/test/oracle/type_number.test
Normal file
9
query_classifier/test/oracle/type_number.test
Normal 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;
|
10
query_classifier/test/oracle/type_raw.test
Normal file
10
query_classifier/test/oracle/type_raw.test
Normal 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;
|
9
query_classifier/test/oracle/type_varchar.test
Normal file
9
query_classifier/test/oracle/type_varchar.test
Normal 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;
|
19
query_classifier/test/oracle/type_varchar2.test
Normal file
19
query_classifier/test/oracle/type_varchar2.test
Normal 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;
|
38
query_classifier/test/oracle/variables.test
Normal file
38
query_classifier/test/oracle/variables.test
Normal 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 #
|
@ -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));"
|
||||
|
@ -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)
|
||||
|
@ -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. */
|
||||
};
|
||||
|
||||
};
|
||||
|
@ -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))
|
||||
{
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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';
|
||||
}
|
||||
|
||||
/**********************************************************
|
||||
|
@ -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];
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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()
|
||||
|
@ -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 &&
|
651
server/modules/protocol/MySQL/MySQLClient/setsqlmodeparser.hh
Normal file
651
server/modules/protocol/MySQL/MySQLClient/setsqlmodeparser.hh
Normal 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;
|
||||
}
|
||||
};
|
@ -0,0 +1,4 @@
|
||||
add_executable(test_setsqlmodeparser test_setsqlmodeparser.cc)
|
||||
target_link_libraries(test_setsqlmodeparser maxscale-common)
|
||||
|
||||
add_test(test_setsqlmodeparser test_setsqlmodeparser)
|
@ -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;
|
||||
}
|
@ -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)
|
||||
|
||||
|
@ -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;
|
@ -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
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user