Develop merge

Develop merge
This commit is contained in:
MassimilianoPinto 2017-06-29 15:34:22 +02:00
parent 4993fd683c
commit cb57e10761
122 changed files with 16937 additions and 1627 deletions

View File

@ -78,8 +78,10 @@ if(NOT OPENSSL_FOUND)
else()
if(OPENSSL_VERSION VERSION_LESS 1 AND NOT FORCE_OPENSSL100)
add_definitions("-DOPENSSL_0_9")
else()
elseif(OPENSSL_VERSION VERSION_LESS 1.1)
add_definitions("-DOPENSSL_1_0")
else()
add_definitions("-DOPENSSL_1_1")
endif()
endif()

View File

@ -124,7 +124,6 @@ Sending of binary data with `LOAD DATA LOCAL INFILE` is not supported.
Read queries are routed to the master server in the following situations:
* query is executed inside an open transaction
* query is a prepared statement
* statement includes a stored procedure or an UDF call
* if there are multiple statements inside one query e.g. `INSERT INTO ... ; SELECT
LAST_INSERT_ID();`
@ -140,19 +139,14 @@ requests without waiting for the protocol to be idle. If you are using the
MariaDB Connector/J, add `useBatchMultiSend=false` to the JDBC connection string
to disable batched statement execution.
#### Backend write timeout handling
#### Prepared Statement Limitations
The backend connections opened by readwritesplit will not be kept alive if they
aren't used. To keep all of the connections alive, a session command must be
periodically executed (for example `SET @a = 1;`).
Readwritesplit does not support the parallel execution of binary protocol
prepared statements that use cursors. In practice this means that only one
open cursor is allowed when readwritesplit is used.
If the backend server is configured with a low
[wait_timeout](https://mariadb.com/kb/en/mariadb/server-system-variables/#wait_timeout),
it is possible that connections get closed during long sessions. It is
recommended to set the `wait_timeout` to a high value and let MariaDB MaxScale
handle the client timeouts. This can be achieved by using the
[_connection_timeout_](../Getting-Started/Configuration-Guide.md#connection_timeout)
parameter for the service.
Opening more than one cursor will cause the execution of the prepared
statements to stall.
#### Limitations in multi-statement handling

View File

@ -21,6 +21,7 @@
* MaxScale now supports IPv6
For more details, please refer to:
* [MariaDB MaxScale 2.1.4 Release Notes](Release-Notes/MaxScale-2.1.4-Release-Notes.md)
* [MariaDB MaxScale 2.1.3 Release Notes](Release-Notes/MaxScale-2.1.3-Release-Notes.md)
* [MariaDB MaxScale 2.1.2 Release Notes](Release-Notes/MaxScale-2.1.2-Release-Notes.md)
* [MariaDB MaxScale 2.1.1 Release Notes](Release-Notes/MaxScale-2.1.1-Release-Notes.md)

View File

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

View File

@ -201,6 +201,19 @@ external agent that automatically reintegrates failed servers into the
cluster. One of these agents is the _replication-manager_ which automatically
configures the failed servers as new slaves of the current master.
### `allow_external_slaves`
Allow the use of external slaves. This option is enabled by default.
If a slave server is replicating from a master that is not being monitored by
the MySQL monitor, the slaves will be assigned the _Slave of External Server_
status (a status mainly for informational purposes).
When the `allow_external_slaves` option is enabled, the server will also be
assigned the _Slave_ status which allows them to be used like normal slave
servers. When the option is disabled, the servers will only receive the _Slave
of External Server_ status and they will not be used.
### `journal_max_age`
The maximum journal file age in seconds. The default value is 28800 seconds.

View File

@ -0,0 +1,65 @@
# MariaDB MaxScale 2.1.4 Release Notes
Release 2.1.4 is a GA release.
This document describes the changes in release 2.1.4, when compared to
release [2.1.3](MaxScale-2.1.3-Release-Notes.md).
If you are upgrading from release 2.0, please also read the following
release notes:
[2.1.3](./MaxScale-2.1.3-Release-Notes.md)
[2.1.2](./MaxScale-2.1.2-Release-Notes.md)
[2.1.1](./MaxScale-2.1.1-Release-Notes.md)
[2.1.0](./MaxScale-2.1.0-Release-Notes.md)
For any problems you encounter, please consider submitting a bug
report at [Jira](https://jira.mariadb.org).
## Changed Features
### Masking
* The masking filter now has a default fill character `X`, which
is used if only a _value_ has been specified and the length of
the value does not match the length of the value received from
the server.
Please refer to the
[masking documentation](../Filters/Masking.md)
for details.
### maxadmin
* Error message for failed login attempt has been improved.
## Bug fixes
[Here is a list of bugs fixed since the release of MaxScale 2.1.3.](https://jira.mariadb.org/issues/?jql=project%20%3D%20MXS%20AND%20issuetype%20%3D%20Bug%20AND%20status%20%3D%20Closed%20AND%20fixVersion%20%3D%202.1.4)
* [MXS-1304](https://jira.mariadb.org/browse/MXS-1304) Invalid write in gw_str_xor
* [MXS-1299](https://jira.mariadb.org/browse/MXS-1299) CREATE TABLE LIKE fails with avrorouter
* [MXS-1296](https://jira.mariadb.org/browse/MXS-1296) Lowercase start transaction is not detected
* [MXS-1294](https://jira.mariadb.org/browse/MXS-1294) cdc_schema.py uses Python 3
* [MXS-1287](https://jira.mariadb.org/browse/MXS-1287) Slaves of external servers can't be used as slaves
* [MXS-1279](https://jira.mariadb.org/browse/MXS-1279) Runtime changes to monitor credentials expect wrong parameter names
* [MXS-1271](https://jira.mariadb.org/browse/MXS-1271) cdc.py consuming 100% of CPU and never sending to kafka
## Known Issues and Limitations
There are some limitations and known issues within this version of MaxScale.
For more information, please refer to the [Limitations](../About/Limitations.md) document.
## Packaging
RPM and Debian packages are provided for the Linux distributions supported
by MariaDB Enterprise.
Packages can be downloaded [here](https://mariadb.com/resources/downloads).
## Source Code
The source code of MaxScale is tagged at GitHub with a tag, which is identical
with the version of MaxScale. For instance, the tag of version X.Y.Z of MaxScale
is X.Y.Z.
The source code is available [here](https://github.com/mariadb-corporation/MaxScale).

View File

@ -6,7 +6,8 @@ MariaDB MaxScale from version 2.0 to 2.1.
For more information about MariaDB MaxScale 2.1, please refer to the
[ChangeLog](../Changelog.md).
For a complete list of changes in MaxScale 2.1.0, refer to the
For a complete list of changes in MaxScale 2.1, refer to the
[MaxScale 2.1.4 Release Notes](../Release-Notes/MaxScale-2.1.4-Release-Notes.md).
[MaxScale 2.1.3 Release Notes](../Release-Notes/MaxScale-2.1.3-Release-Notes.md).
[MaxScale 2.1.2 Release Notes](../Release-Notes/MaxScale-2.1.2-Release-Notes.md).
[MaxScale 2.1.1 Release Notes](../Release-Notes/MaxScale-2.1.1-Release-Notes.md).

View File

@ -182,7 +182,7 @@ json_t* maxavro_record_read_json(MAXAVRO_FILE *file)
{
long pos = ftell(file->file);
MXS_ERROR("Failed to read field value '%s', type '%s' at "
"file offset %ld, record numer %lu.",
"file offset %ld, record number %lu.",
file->schema->fields[i].name,
type_to_string(file->schema->fields[i].type),
pos, file->records_read);

View File

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

View File

@ -159,7 +159,7 @@ public:
*
* @return True if data was written successfully
*/
bool write(GWBUF* buffer, response_type type = EXPECT_RESPONSE);
virtual bool write(GWBUF* buffer, response_type type = EXPECT_RESPONSE);
/**
* @brief Write an authentication switch request to the backend server

View File

@ -212,6 +212,21 @@ extern void gwbuf_free(GWBUF *buf);
*/
extern GWBUF *gwbuf_clone(GWBUF *buf);
/**
* @brief Deep clone a GWBUF
*
* Clone the data inside a GWBUF into a new buffer. The created buffer has its
* own internal buffer and any modifications to the deep cloned buffer will not
* reflect on the original one. Any buffer objects attached to the original buffer
* will not be copied. Only the buffer type of the original buffer will be copied
* over to the cloned buffer.
*
* @param buf Buffer to clone
*
* @return Deep copy of @c buf or NULL on error
*/
extern GWBUF* gwbuf_deep_clone(const GWBUF* buf);
/**
* Compare two GWBUFs. Two GWBUFs are considered identical if their
* content is identical, irrespective of whether one is segmented and

View File

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

View File

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

View File

@ -0,0 +1,27 @@
#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: 2020-01-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/cdefs.h>
#include <openssl/evp.h>
MXS_BEGIN_DECLS
EVP_CIPHER_CTX* mxs_evp_cipher_ctx_alloc();
void mxs_evp_cipher_ctx_free(EVP_CIPHER_CTX* ctx);
uint8_t* mxs_evp_cipher_ctx_buf(EVP_CIPHER_CTX* ctx);
uint8_t* mxs_evp_cipher_ctx_oiv(EVP_CIPHER_CTX* ctx);
MXS_END_DECLS

View File

@ -84,9 +84,13 @@ MXS_BEGIN_DECLS
* [10-11] warning_count (2) -- number of warnings
*/
#define MYSQL_PS_ID_OFFSET MYSQL_HEADER_LEN + 1
#define MYSQL_PS_ID_SIZE 4
#define MYSQL_PS_COLS_OFFSET MYSQL_HEADER_LEN + 5
#define MYSQL_PS_COLS_SIZE 2
#define MYSQL_PS_PARAMS_OFFSET MYSQL_HEADER_LEN + 7
#define MYSQL_PS_PARAMS_SIZE 2
#define MYSQL_PS_WARN_OFFSET MYSQL_HEADER_LEN + 10
#define MYSQL_PS_WARN_SIZE 2
/** Name of the default server side authentication plugin */
#define DEFAULT_MYSQL_AUTH_PLUGIN "mysql_native_password"
@ -308,6 +312,14 @@ typedef struct
#endif
} MySQLProtocol;
typedef struct
{
uint32_t id;
uint16_t columns;
uint16_t parameters;
uint16_t warnings;
} MXS_PS_RESPONSE;
/** Defines for response codes */
#define MYSQL_REPLY_ERR 0xff
#define MYSQL_REPLY_OK 0x00
@ -504,4 +516,44 @@ const char* mxs_mysql_get_current_db(MXS_SESSION* session);
*/
void mxs_mysql_set_current_db(MXS_SESSION* session, const char* db);
/**
* @brief Get the command byte
*
* @param buffer Buffer containing a complete MySQL packet
*
* @return The command byte
*/
uint8_t mxs_mysql_get_command(GWBUF* buffer);
/**
* @brief Extract PS response values
*
* @param buffer Buffer containing a complete response to a binary protocol
* preparation of a prepared statement
* @param out Destination where the values are extracted
*
* @return True if values were extracted successfully
*/
bool mxs_mysql_extract_ps_response(GWBUF* buffer, MXS_PS_RESPONSE* out);
/**
* @brief Extract the ID from a COM_STMT command
*
* All the COM_STMT type commands store the statement ID in the same place.
*
* @param buffer Buffer containing one of the COM_STMT commands (not COM_STMT_PREPARE)
*
* @return The statement ID
*/
uint32_t mxs_mysql_extract_ps_id(GWBUF* buffer);
/**
* @brief Determine if a packet contains a one way message
*
* @param cmd Command to inspect
*
* @return True if a response is expected from the server
*/
bool mxs_mysql_command_will_respond(uint8_t cmd);
MXS_END_DECLS

View File

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

View File

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

View File

@ -54,10 +54,11 @@ public:
uint64_t get_position() const;
/**
* @brief Creates a copy of the internal buffer
* @return A copy of the internal buffer
* @brief Creates a deep copy of the internal buffer
*
* @return A deep copy of the internal buffer or NULL on error
*/
mxs::Buffer copy_buffer() const;
GWBUF* deep_copy_buffer();
/**
* @brief Create a new session command

View File

@ -344,6 +344,10 @@ add_test_executable(mxs280_select_outfile.cpp mxs280_select_outfile replication
# Tries prepared stmt 'SELECT 1,1,1,1...." with different nu,ber of '1'
add_test_executable(mxs314.cpp mxs314 galera LABELS MySQLProtocol LIGHT GALERA_BACKEND)
# Binary protocol prepared statement tests
add_test_executable(binary_ps.cpp binary_ps replication LABELS readwritesplit LIGHT REPL_BACKEND)
add_test_executable(binary_ps_cursor.cpp binary_ps_cursor replication LABELS readwritesplit LIGHT REPL_BACKEND)
# Creates and closes a lot of connections, checks that 'maxadmin list servers' shows 0 connections at the end
add_test_executable(mxs321.cpp mxs321 replication LABELS maxscale REPL_BACKEND)
@ -591,6 +595,8 @@ add_test_script(ssl sql_queries ssl LABELS maxscale readwritesplit REPL_BACKEND)
#add_test_script(ssl_load load_balancing ssl_load LABELS maxscale readwritesplit REPL_BACKEND)
# Check load balancing, client ssl is ON, Galera backend
# Galera version is also disabled due to C/C 3.0 strangeness
#add_test_script(ssl_load_galera load_balancing_galera ssl_load_galera LABELS maxscale readwritesplit GALERA_BACKEND)
# Testing slaves who have lost their master and how MaxScale works with them

View File

@ -0,0 +1,67 @@
/**
* Test binary protocol prepared statement routing
*/
#include "testconnections.h"
int main(int argc, char** argv)
{
TestConnections test(argc, argv);
char server_id[test.galera->N][1024];
test.repl->connect();
/** Get server_id for each node */
for (int i = 0; i < test.repl->N; i++)
{
sprintf(server_id[i], "%d", test.repl->get_server_id(i));
}
test.connect_maxscale();
test.set_timeout(20);
MYSQL_STMT* stmt = mysql_stmt_init(test.conn_rwsplit);
const char* write_query = "SELECT @@server_id, @@last_insert_id";
const char* read_query = "SELECT @@server_id";
char buffer[100] = "";
char buffer2[100] = "";
my_bool err = false;
my_bool isnull = false;
MYSQL_BIND bind[2] = {};
bind[0].buffer_length = sizeof(buffer);
bind[0].buffer = buffer;
bind[0].error = &err;
bind[0].is_null = &isnull;
bind[1].buffer_length = sizeof(buffer2);
bind[1].buffer = buffer2;
bind[1].error = &err;
bind[1].is_null = &isnull;
// Execute a write, should return the master's server ID
test.add_result(mysql_stmt_prepare(stmt, write_query, strlen(write_query)), "Failed to prepare");
test.add_result(mysql_stmt_execute(stmt), "Failed to execute");
test.add_result(mysql_stmt_bind_result(stmt, bind), "Failed to bind result");
test.add_result(mysql_stmt_fetch(stmt), "Failed to fetch result");
test.add_result(strcmp(buffer, server_id[0]), "Expected server_id '%s', got '%s'", server_id[0], buffer);
mysql_stmt_close(stmt);
stmt = mysql_stmt_init(test.conn_rwsplit);
// Execute read, should return a slave server ID
test.add_result(mysql_stmt_prepare(stmt, read_query, strlen(read_query)), "Failed to prepare");
test.add_result(mysql_stmt_execute(stmt), "Failed to execute");
test.add_result(mysql_stmt_bind_result(stmt, bind), "Failed to bind result");
test.add_result(mysql_stmt_fetch(stmt), "Failed to fetch result");
test.add_result(strcmp(buffer, server_id[1]) && strcmp(buffer, server_id[2]) && strcmp(buffer, server_id[3]),
"Expected one of the slave server IDs (%s, %s or %s), not '%s'",
server_id[1], server_id[2], server_id[3], buffer);
mysql_stmt_close(stmt);
test.close_maxscale_connections();
return test.global_result;
}

View File

@ -0,0 +1,191 @@
/**
* Test that binary protocol cursors work as expected
*/
#include "testconnections.h"
#include <iostream>
using std::cout;
using std::endl;
void test1(TestConnections& test)
{
test.connect_maxscale();
test.set_timeout(20);
MYSQL_STMT* stmt = mysql_stmt_init(test.conn_rwsplit);
const char* query = "SELECT @@server_id";
char buffer[100] = "";
my_bool err = false;
my_bool isnull = false;
MYSQL_BIND bind[1] = {};
bind[0].buffer_length = sizeof(buffer);
bind[0].buffer = buffer;
bind[0].error = &err;
bind[0].is_null = &isnull;
cout << "Prepare" << endl;
test.add_result(mysql_stmt_prepare(stmt, query, strlen(query)), "Failed to prepare");
unsigned long cursor_type = CURSOR_TYPE_READ_ONLY;
unsigned long rows = 0;
test.add_result(mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, &cursor_type), "Failed to set attributes");
test.add_result(mysql_stmt_attr_set(stmt, STMT_ATTR_PREFETCH_ROWS, &rows), "Failed to set attributes");
cout << "Execute" << endl;
test.add_result(mysql_stmt_execute(stmt), "Failed to execute");
cout << "Bind result" << endl;
test.add_result(mysql_stmt_bind_result(stmt, bind), "Failed to bind result");
cout << "Fetch row" << endl;
test.add_result(mysql_stmt_fetch(stmt), "Failed to fetch result");
test.add_result(strlen(buffer) == 0, "Expected result buffer to not be empty");
cout << "Close statement" << endl;
mysql_stmt_close(stmt);
test.close_maxscale_connections();
}
void test2(TestConnections& test)
{
test.set_timeout(20);
MYSQL* conn = open_conn_db_timeout(test.rwsplit_port, test.maxscale_ip(), "test",
test.maxscale_user, test.maxscale_password, 1, false);
MYSQL_STMT* stmt1 = mysql_stmt_init(conn);
MYSQL_STMT* stmt2 = mysql_stmt_init(conn);
const char* query1 = "SELECT @@server_id";
const char* query2 = "SELECT @@server_id, @@last_insert_id";
char buffer1[100] = "";
char buffer2[100] = "";
char buffer2_2[100] = "";
my_bool err = false;
my_bool isnull = false;
MYSQL_BIND bind1[1] = {};
MYSQL_BIND bind2[2] = {};
bind1[0].buffer_length = sizeof(buffer1);
bind1[0].buffer = buffer1;
bind1[0].error = &err;
bind1[0].is_null = &isnull;
bind2[0].buffer_length = sizeof(buffer2);
bind2[0].buffer = buffer2;
bind2[0].error = &err;
bind2[0].is_null = &isnull;
bind2[1].buffer_length = sizeof(buffer2);
bind2[1].buffer = buffer2_2;
bind2[1].error = &err;
bind2[1].is_null = &isnull;
cout << "First prepare, should go to slave" << endl;
test.add_result(mysql_stmt_prepare(stmt1, query1, strlen(query1)), "Failed to prepare");
unsigned long cursor_type = CURSOR_TYPE_READ_ONLY;
unsigned long rows = 0;
test.add_result(mysql_stmt_attr_set(stmt1, STMT_ATTR_CURSOR_TYPE, &cursor_type), "Failed to set attributes");
test.add_result(mysql_stmt_attr_set(stmt1, STMT_ATTR_PREFETCH_ROWS, &rows), "Failed to set attributes");
test.add_result(mysql_stmt_execute(stmt1), "Failed to execute");
test.add_result(mysql_stmt_bind_result(stmt1, bind1), "Failed to bind result");
int rc1 = mysql_stmt_fetch(stmt1);
test.add_result(rc1, "Failed to fetch result: %d %s %s", rc1, mysql_stmt_error(stmt1), mysql_error(conn));
mysql_stmt_close(stmt1);
cout << "Second prepare, should go to master" << endl;
test.add_result(mysql_stmt_prepare(stmt2, query2, strlen(query2)), "Failed to prepare");
test.add_result(mysql_stmt_attr_set(stmt2, STMT_ATTR_CURSOR_TYPE, &cursor_type), "Failed to set attributes");
test.add_result(mysql_stmt_attr_set(stmt2, STMT_ATTR_PREFETCH_ROWS, &rows), "Failed to set attributes");
test.add_result(mysql_stmt_execute(stmt2), "Failed to execute");
test.add_result(mysql_stmt_bind_result(stmt2, bind2), "Failed to bind result");
int rc2 = mysql_stmt_fetch(stmt2);
test.add_result(rc2, "Failed to fetch result: %d %s %s", rc2, mysql_stmt_error(stmt2), mysql_error(conn));
mysql_stmt_close(stmt2);
/** Get the master's server_id */
char server_id[1024];
test.repl->connect();
sprintf(server_id, "%d", test.repl->get_server_id(0));
test.add_result(strcmp(buffer1, buffer2) == 0, "Expected results to differ");
test.add_result(strcmp(buffer2, server_id) != 0,
"Expected prepare 2 to go to the master (%s) but it's %s",
server_id[0], buffer2);
}
void test3(TestConnections& test)
{
test.connect_maxscale();
test.set_timeout(20);
MYSQL_STMT* stmt = mysql_stmt_init(test.conn_rwsplit);
const char* query = "SELECT @@server_id";
char buffer[100] = "";
my_bool err = false;
my_bool isnull = false;
MYSQL_BIND bind[1] = {};
bind[0].buffer_length = sizeof(buffer);
bind[0].buffer = buffer;
bind[0].error = &err;
bind[0].is_null = &isnull;
test.add_result(mysql_stmt_prepare(stmt, query, strlen(query)), "Failed to prepare");
cout << "Start transaction" << endl;
test.add_result(mysql_query(test.conn_rwsplit, "START TRANSACTION"),
"START TRANSACTION should succeed: %s",
mysql_error(test.conn_rwsplit));
unsigned long cursor_type = CURSOR_TYPE_READ_ONLY;
unsigned long rows = 0;
test.add_result(mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, &cursor_type), "Failed to set attributes");
test.add_result(mysql_stmt_attr_set(stmt, STMT_ATTR_PREFETCH_ROWS, &rows), "Failed to set attributes");
cout << "Execute" << endl;
test.add_result(mysql_stmt_execute(stmt), "Failed to execute");
test.add_result(mysql_stmt_bind_result(stmt, bind), "Failed to bind result");
test.add_result(mysql_stmt_fetch(stmt), "Failed to fetch result");
test.add_result(strlen(buffer) == 0, "Expected result buffer to not be empty");
cout << "Commit" << endl;
test.add_result(mysql_query(test.conn_rwsplit, "COMMIT"),
"COMMIT should succeed: %s",
mysql_error(test.conn_rwsplit));
mysql_stmt_close(stmt);
test.close_maxscale_connections();
char server_id[1024];
test.repl->connect();
sprintf(server_id, "%d", test.repl->get_server_id(0));
test.add_result(strcmp(buffer, server_id) != 0,
"Expected the execute inside a transaction to go to the master (%s) but it's %s",
server_id[0], buffer);
}
int main(int argc, char** argv)
{
TestConnections test(argc, argv);
cout << "Test 1: Testing simple cursor usage" << endl;
test1(test);
cout << "Done" << endl << endl;
cout << "Test 2: Testing read-write splitting with cursors" << endl;
test2(test);
cout << "Done" << endl << endl;
cout << "Test 3: Testing transactions with cursors" << endl;
test3(test);
cout << "Done" << endl << endl;
return test.global_result;
}

View File

@ -1672,7 +1672,7 @@ void *timeout_thread( void *ptr )
Test->timeout--;
}
Test->tprintf("\n **** Timeout! *** \n");
delete Test;
Test->~TestConnections();
exit(250);
}

View File

@ -29,7 +29,6 @@
#if defined(MYSQL_CLIENT)
#undef MYSQL_CLIENT
#endif
#include <my_config.h>
#include <mysql.h>
#include <my_sys.h>
@ -55,8 +54,11 @@
#include <strfunc.h>
#include <item_func.h>
#include <pthread.h>
#include <maxscale/debug.h>
#include <maxscale/log_manager.h>
#include <maxscale/platform.h>
#include <maxscale/query_classifier.h>
// <maxscale/protocol/mysql.h> assumes it is being compiled agains Connector-C,
// so we need to make certain Connector-C constants visible.
@ -72,6 +74,47 @@
#include <string.h>
#include <stdarg.h>
/**
* Defines what a particular name should be mapped to.
*/
typedef struct name_mapping
{
const char* from;
const char* to;
} NAME_MAPPING;
static NAME_MAPPING function_name_mappings_default[] =
{
{ NULL, NULL }
};
static NAME_MAPPING function_name_mappings_oracle[] =
{
{ "concat_operator_oracle", "concat" },
{ "case", "decode" },
{ NULL, NULL }
};
static const char* map_function_name(NAME_MAPPING* function_name_mappings, const char* from)
{
NAME_MAPPING* map = function_name_mappings;
const char* to = NULL;
while (!to && map->from)
{
if (strcasecmp(from, map->from) == 0)
{
to = map->to;
}
else
{
++map;
}
}
return to ? to : from;
}
#define MYSQL_COM_QUERY_HEADER_SIZE 5 /*< 3 bytes size, 1 sequence, 1 command */
#define MAX_QUERYBUF_SIZE 2048
typedef struct parsing_info_st
@ -89,19 +132,14 @@ typedef struct parsing_info_st
size_t function_infos_len;
size_t function_infos_capacity;
GWBUF* preparable_stmt;
qc_parse_result_t result;
int32_t type_mask;
NAME_MAPPING* function_name_mappings;
#if defined(SS_DEBUG)
skygw_chk_t pi_chk_tail;
#endif
} parsing_info_t;
static thread_local struct
{
// The version information is not used; the embedded library parses according
// to the version of the embedded library it has been linked with. However, we
// need to store the information so that qc_[get|set]_server_version will work.
uint64_t version;
} this_thread;
#define QTYPE_LESS_RESTRICTIVE_THAN_WRITE(t) (t<QUERY_TYPE_WRITE ? true : false)
static THD* get_or_create_thd_for_parsing(MYSQL* mysql, char* query_str);
@ -119,8 +157,50 @@ static TABLE_LIST* skygw_get_affected_tables(void* lexptr);
static bool ensure_query_is_parsed(GWBUF* query);
static bool parse_query(GWBUF* querybuf);
static bool query_is_parsed(GWBUF* buf);
int32_t qc_mysql_get_field_info(GWBUF* buf, const QC_FIELD_INFO** infos, uint32_t* n_infos);
#if MYSQL_VERSION_MAJOR >= 10 && MYSQL_VERSION_MINOR >= 3
inline void get_string_and_length(const LEX_CSTRING& ls, const char** s, size_t* length)
{
*s = ls.str;
*length = ls.length;
}
#else
inline void get_string_and_length(const char* cs, const char** s, size_t* length)
{
*s = cs;
*length = cs ? strlen(cs) : 0;
}
#endif
static struct
{
qc_sql_mode_t sql_mode;
pthread_mutex_t sql_mode_mutex;
NAME_MAPPING* function_name_mappings;
} this_unit =
{
QC_SQL_MODE_DEFAULT,
PTHREAD_MUTEX_INITIALIZER,
function_name_mappings_default
};
static thread_local struct
{
qc_sql_mode_t sql_mode;
NAME_MAPPING* function_name_mappings;
// The version information is not used; the embedded library parses according
// to the version of the embedded library it has been linked with. However, we
// need to store the information so that qc_[get|set]_server_version will work.
uint64_t version;
} this_thread =
{
QC_SQL_MODE_DEFAULT,
function_name_mappings_default,
0
};
/**
* Ensures that the query is parsed. If it is not already parsed, it
* will be parsed.
@ -133,8 +213,38 @@ bool ensure_query_is_parsed(GWBUF* query)
if (!parsed)
{
// Instead of modifying global_system_variables, from which
// thd->variables.sql_mode will be initialied, we should modify
// thd->variables.sql_mode _after_ it has been created and
// initialized.
//
// However, for whatever reason, the offset of that variable is
// different when accessed from within libmysqld and qc_mysqlembedded,
// so we will not modify the right variable even if it appears we do.
//
// So, for the time being we modify global_system_variables.sql_mode and
// serialize the parsing. That's ok, since qc_mysqlembedded is only
// used for verifying the behaviour of qc_sqlite.
ss_debug(int rv);
ss_debug(rv = )pthread_mutex_lock(&this_unit.sql_mode_mutex);
ss_dassert(rv == 0);
if (this_thread.sql_mode == QC_SQL_MODE_ORACLE)
{
global_system_variables.sql_mode |= MODE_ORACLE;
}
else
{
global_system_variables.sql_mode &= ~MODE_ORACLE;
}
parsed = parse_query(query);
ss_debug(rv = )pthread_mutex_unlock(&this_unit.sql_mode_mutex);
ss_dassert(rv == 0);
if (!parsed)
{
MXS_ERROR("Unable to parse query, out of resources?");
@ -156,7 +266,11 @@ int32_t qc_mysql_parse(GWBUF* querybuf, uint32_t collect, int32_t* result)
if (parsed)
{
*result = QC_QUERY_PARSED;
parsing_info_t* pi = (parsing_info_t*) gwbuf_get_buffer_object_data(querybuf,
GWBUF_PARSING_INFO);
ss_dassert(pi);
*result = pi->result;
}
else
{
@ -168,6 +282,8 @@ int32_t qc_mysql_parse(GWBUF* querybuf, uint32_t collect, int32_t* result)
int32_t qc_mysql_get_type_mask(GWBUF* querybuf, uint32_t* type_mask)
{
int32_t rv = QC_RESULT_OK;
*type_mask = QUERY_TYPE_UNKNOWN;
MYSQL* mysql;
bool succp;
@ -198,12 +314,25 @@ int32_t qc_mysql_get_type_mask(GWBUF* querybuf, uint32_t* type_mask)
if (mysql != NULL)
{
*type_mask = resolve_query_type(pi, (THD *) mysql->thd);
#if MYSQL_VERSION_MAJOR >= 10 && MYSQL_VERSION_MINOR >= 3
// If in 10.3 mode we need to ensure that sequence related functions
// are taken into account. That we can ensure by querying for the fields.
const QC_FIELD_INFO* field_infos;
uint32_t n_field_infos;
rv = qc_mysql_get_field_info(querybuf, &field_infos, &n_field_infos);
if (rv == QC_RESULT_OK)
{
*type_mask |= pi->type_mask;
}
#endif
}
}
}
retblock:
return QC_RESULT_OK;
return rv;
}
/**
@ -278,7 +407,11 @@ static bool parse_query(GWBUF* querybuf)
* Create parse_tree inside thd.
* thd and lex are readable even if creating parse tree fails.
*/
create_parse_tree(thd);
if (create_parse_tree(thd))
{
pi->result = QC_QUERY_PARSED;
}
/** Add complete parsing info struct to the query buffer */
gwbuf_add_buffer_object(querybuf,
GWBUF_PARSING_INFO,
@ -462,7 +595,7 @@ static bool create_parse_tree(THD* thd)
}
return_here:
return failp;
return !failp;
}
/**
@ -601,6 +734,12 @@ static uint32_t resolve_query_type(parsing_info_t *pi, THD* thd)
goto return_qtype;
}
if (lex->describe)
{
type = QUERY_TYPE_READ;
goto return_qtype;
}
if (skygw_stmt_causes_implicit_commit(lex, &set_autocommit_stmt))
{
if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO))
@ -716,6 +855,10 @@ static uint32_t resolve_query_type(parsing_info_t *pi, THD* thd)
}
}
}
else
{
type |= QUERY_TYPE_READ;
}
goto return_qtype;
}
@ -770,7 +913,6 @@ static uint32_t resolve_query_type(parsing_info_t *pi, THD* thd)
break;
case SQLCOM_SELECT:
case SQLCOM_SHOW_SLAVE_STAT:
type |= QUERY_TYPE_READ;
break;
@ -811,15 +953,30 @@ static uint32_t resolve_query_type(parsing_info_t *pi, THD* thd)
goto return_qtype;
break;
case SQLCOM_SHOW_FIELDS:
type |= QUERY_TYPE_READ;
break;
case SQLCOM_SHOW_TABLES:
type |= QUERY_TYPE_SHOW_TABLES;
goto return_qtype;
break;
case SQLCOM_SHOW_CREATE:
case SQLCOM_SHOW_CREATE_DB:
case SQLCOM_SHOW_CREATE_FUNC:
case SQLCOM_SHOW_CREATE_PROC:
case SQLCOM_SHOW_FIELDS:
case SQLCOM_SHOW_FUNC_CODE:
case SQLCOM_SHOW_GRANTS:
case SQLCOM_SHOW_PROC_CODE:
case SQLCOM_SHOW_SLAVE_HOSTS:
case SQLCOM_SHOW_SLAVE_STAT:
case SQLCOM_SHOW_STATUS:
type |= QUERY_TYPE_READ;
goto return_qtype;
break;
case SQLCOM_END:
goto return_qtype;
break;
default:
type |= QUERY_TYPE_WRITE;
break;
@ -857,8 +1014,6 @@ static uint32_t resolve_query_type(parsing_info_t *pi, THD* thd)
Item::Type itype;
itype = item->type();
MXS_DEBUG("%lu [resolve_query_type] Item %s:%s",
pthread_self(), item->name, STRITEMTYPE(itype));
if (itype == Item::SUBSELECT_ITEM)
{
@ -952,10 +1107,18 @@ static uint32_t resolve_query_type(parsing_info_t *pi, THD* thd)
/** System session variable */
case Item_func::GSYSVAR_FUNC:
{
const char* name = item->name;
const char* name;
size_t length;
get_string_and_length(item->name, &name, &length);
const char last_insert_id[] = "@@last_insert_id";
const char identity[] = "@@identity";
if (name &&
((strcasecmp(name, "@@last_insert_id") == 0) ||
(strcasecmp(name, "@@identity") == 0)))
(((length == sizeof(last_insert_id) - 1) &&
(strcasecmp(name, last_insert_id) == 0)) ||
((length == sizeof(identity) - 1) &&
(strcasecmp(name, identity) == 0))))
{
func_qtype |= QUERY_TYPE_MASTER_READ;
}
@ -1273,6 +1436,33 @@ static TABLE_LIST* skygw_get_affected_tables(void* lexptr)
return tbl;
}
static bool is_show_command(int sql_command)
{
bool rv = false;
switch (sql_command)
{
case SQLCOM_SHOW_CREATE:
case SQLCOM_SHOW_DATABASES:
case SQLCOM_SHOW_FIELDS:
case SQLCOM_SHOW_KEYS:
case SQLCOM_SHOW_MASTER_STAT:
case SQLCOM_SHOW_SLAVE_STAT:
case SQLCOM_SHOW_STATUS:
case SQLCOM_SHOW_TABLES:
case SQLCOM_SHOW_TABLE_STATUS:
case SQLCOM_SHOW_VARIABLES:
case SQLCOM_SHOW_WARNS:
rv = true;
break;
default:
break;
}
return rv;
}
int32_t qc_mysql_get_table_names(GWBUF* querybuf, int32_t fullnames, char*** tablesp, int32_t* tblsize)
{
LEX* lex;
@ -1295,6 +1485,11 @@ int32_t qc_mysql_get_table_names(GWBUF* querybuf, int32_t fullnames, char*** tab
goto retblock;
}
if (lex->describe || is_show_command(lex->sql_command))
{
goto retblock;
}
lex->current_select = lex->all_selects_list;
while (lex->current_select)
@ -1428,16 +1623,19 @@ int32_t qc_mysql_query_has_clause(GWBUF* buf, int32_t* has_clause)
if (lex)
{
SELECT_LEX* current = lex->all_selects_list;
while (current && !*has_clause)
if (!lex->describe && !is_show_command(lex->sql_command))
{
if (current->where || current->having)
{
*has_clause = true;
}
SELECT_LEX* current = lex->all_selects_list;
current = current->next_select_in_list();
while (current && !*has_clause)
{
if (current->where || current->having)
{
*has_clause = true;
}
current = current->next_select_in_list();
}
}
}
}
@ -1498,6 +1696,9 @@ static parsing_info_t* parsing_info_init(void (*donefun)(void *))
/** Set handle and free function to parsing info struct */
pi->pi_handle = mysql;
pi->pi_done_fp = donefun;
pi->result = QC_QUERY_INVALID;
ss_dassert(this_thread.function_name_mappings);
pi->function_name_mappings = this_thread.function_name_mappings;
retblock:
return pi;
@ -1600,6 +1801,11 @@ int32_t qc_mysql_get_database_names(GWBUF* querybuf, char*** databasesp, int* si
goto retblock;
}
if (lex->describe || is_show_command(lex->sql_command))
{
goto retblock;
}
lex->current_select = lex->all_selects_list;
while (lex->current_select)
@ -1663,94 +1869,128 @@ int32_t qc_mysql_get_operation(GWBUF* querybuf, int32_t* operation)
if (lex)
{
switch (lex->sql_command)
if (lex->describe)
{
case SQLCOM_SELECT:
*operation = QUERY_OP_SELECT;
break;
*operation = QUERY_OP_EXPLAIN;
}
else
{
switch (lex->sql_command)
{
case SQLCOM_SELECT:
*operation = QUERY_OP_SELECT;
break;
case SQLCOM_CREATE_DB:
case SQLCOM_CREATE_EVENT:
case SQLCOM_CREATE_FUNCTION:
case SQLCOM_CREATE_INDEX:
case SQLCOM_CREATE_PROCEDURE:
case SQLCOM_CREATE_SERVER:
case SQLCOM_CREATE_SPFUNCTION:
case SQLCOM_CREATE_TABLE:
case SQLCOM_CREATE_TRIGGER:
case SQLCOM_CREATE_USER:
case SQLCOM_CREATE_VIEW:
*operation = QUERY_OP_CREATE;
break;
case SQLCOM_CREATE_DB:
case SQLCOM_CREATE_EVENT:
case SQLCOM_CREATE_FUNCTION:
case SQLCOM_CREATE_INDEX:
case SQLCOM_CREATE_PROCEDURE:
#if MYSQL_VERSION_MAJOR >= 10 && MYSQL_VERSION_MINOR >= 3
case SQLCOM_CREATE_SEQUENCE:
#endif
case SQLCOM_CREATE_SERVER:
case SQLCOM_CREATE_SPFUNCTION:
case SQLCOM_CREATE_TABLE:
case SQLCOM_CREATE_TRIGGER:
case SQLCOM_CREATE_USER:
case SQLCOM_CREATE_VIEW:
*operation = QUERY_OP_CREATE;
break;
case SQLCOM_ALTER_DB:
case SQLCOM_ALTER_DB_UPGRADE:
case SQLCOM_ALTER_EVENT:
case SQLCOM_ALTER_FUNCTION:
case SQLCOM_ALTER_PROCEDURE:
case SQLCOM_ALTER_SERVER:
case SQLCOM_ALTER_TABLE:
case SQLCOM_ALTER_TABLESPACE:
*operation = QUERY_OP_ALTER;
break;
case SQLCOM_ALTER_DB:
case SQLCOM_ALTER_DB_UPGRADE:
case SQLCOM_ALTER_EVENT:
case SQLCOM_ALTER_FUNCTION:
case SQLCOM_ALTER_PROCEDURE:
case SQLCOM_ALTER_SERVER:
case SQLCOM_ALTER_TABLE:
case SQLCOM_ALTER_TABLESPACE:
*operation = QUERY_OP_ALTER;
break;
case SQLCOM_UPDATE:
case SQLCOM_UPDATE_MULTI:
*operation = QUERY_OP_UPDATE;
break;
case SQLCOM_UPDATE:
case SQLCOM_UPDATE_MULTI:
*operation = QUERY_OP_UPDATE;
break;
case SQLCOM_INSERT:
case SQLCOM_INSERT_SELECT:
case SQLCOM_REPLACE:
case SQLCOM_REPLACE_SELECT:
*operation = QUERY_OP_INSERT;
break;
case SQLCOM_INSERT:
case SQLCOM_INSERT_SELECT:
case SQLCOM_REPLACE:
case SQLCOM_REPLACE_SELECT:
*operation = QUERY_OP_INSERT;
break;
case SQLCOM_DELETE:
case SQLCOM_DELETE_MULTI:
*operation = QUERY_OP_DELETE;
break;
case SQLCOM_DELETE:
case SQLCOM_DELETE_MULTI:
*operation = QUERY_OP_DELETE;
break;
case SQLCOM_TRUNCATE:
*operation = QUERY_OP_TRUNCATE;
break;
case SQLCOM_TRUNCATE:
*operation = QUERY_OP_TRUNCATE;
break;
case SQLCOM_DROP_DB:
case SQLCOM_DROP_EVENT:
case SQLCOM_DROP_FUNCTION:
case SQLCOM_DROP_INDEX:
case SQLCOM_DROP_PROCEDURE:
case SQLCOM_DROP_SERVER:
case SQLCOM_DROP_TABLE:
case SQLCOM_DROP_TRIGGER:
case SQLCOM_DROP_USER:
case SQLCOM_DROP_VIEW:
*operation = QUERY_OP_DROP;
break;
case SQLCOM_DROP_DB:
case SQLCOM_DROP_EVENT:
case SQLCOM_DROP_FUNCTION:
case SQLCOM_DROP_INDEX:
case SQLCOM_DROP_PROCEDURE:
#if MYSQL_VERSION_MAJOR >= 10 && MYSQL_VERSION_MINOR >= 3
case SQLCOM_DROP_SEQUENCE:
#endif
case SQLCOM_DROP_SERVER:
case SQLCOM_DROP_TABLE:
case SQLCOM_DROP_TRIGGER:
case SQLCOM_DROP_USER:
case SQLCOM_DROP_VIEW:
*operation = QUERY_OP_DROP;
break;
case SQLCOM_CHANGE_DB:
*operation = QUERY_OP_CHANGE_DB;
break;
case SQLCOM_CHANGE_DB:
*operation = QUERY_OP_CHANGE_DB;
break;
case SQLCOM_LOAD:
*operation = QUERY_OP_LOAD;
break;
case SQLCOM_LOAD:
*operation = QUERY_OP_LOAD;
break;
case SQLCOM_GRANT:
*operation = QUERY_OP_GRANT;
break;
case SQLCOM_GRANT:
*operation = QUERY_OP_GRANT;
break;
case SQLCOM_REVOKE:
case SQLCOM_REVOKE_ALL:
*operation = QUERY_OP_REVOKE;
break;
case SQLCOM_REVOKE:
case SQLCOM_REVOKE_ALL:
*operation = QUERY_OP_REVOKE;
break;
case SQLCOM_EXECUTE:
*operation = QUERY_OP_EXECUTE;
break;
case SQLCOM_SHOW_CREATE:
case SQLCOM_SHOW_CREATE_DB:
case SQLCOM_SHOW_CREATE_FUNC:
case SQLCOM_SHOW_CREATE_PROC:
case SQLCOM_SHOW_DATABASES:
case SQLCOM_SHOW_FIELDS:
case SQLCOM_SHOW_FUNC_CODE:
case SQLCOM_SHOW_GRANTS:
case SQLCOM_SHOW_KEYS:
case SQLCOM_SHOW_MASTER_STAT:
case SQLCOM_SHOW_PROC_CODE:
case SQLCOM_SHOW_SLAVE_HOSTS:
case SQLCOM_SHOW_SLAVE_STAT:
case SQLCOM_SHOW_STATUS:
case SQLCOM_SHOW_TABLES:
case SQLCOM_SHOW_TABLE_STATUS:
case SQLCOM_SHOW_VARIABLES:
case SQLCOM_SHOW_WARNS:
*operation = QUERY_OP_SHOW;
break;
default:
*operation = QUERY_OP_UNDEFINED;
case SQLCOM_EXECUTE:
*operation = QUERY_OP_EXECUTE;
break;
default:
*operation = QUERY_OP_UNDEFINED;
}
}
}
}
@ -1769,15 +2009,18 @@ int32_t qc_mysql_get_prepare_name(GWBUF* stmt, char** namep)
{
LEX* lex = get_lex(stmt);
if ((lex->sql_command == SQLCOM_PREPARE) ||
(lex->sql_command == SQLCOM_EXECUTE) ||
(lex->sql_command == SQLCOM_DEALLOCATE_PREPARE))
if (!lex->describe)
{
name = (char*)malloc(lex->prepared_stmt_name.length + 1);
if (name)
if ((lex->sql_command == SQLCOM_PREPARE) ||
(lex->sql_command == SQLCOM_EXECUTE) ||
(lex->sql_command == SQLCOM_DEALLOCATE_PREPARE))
{
memcpy(name, lex->prepared_stmt_name.str, lex->prepared_stmt_name.length);
name[lex->prepared_stmt_name.length] = 0;
name = (char*)malloc(lex->prepared_stmt_name.length + 1);
if (name)
{
memcpy(name, lex->prepared_stmt_name.str, lex->prepared_stmt_name.length);
name[lex->prepared_stmt_name.length] = 0;
}
}
}
}
@ -1796,14 +2039,21 @@ int32_t qc_mysql_get_preparable_stmt(GWBUF* stmt, GWBUF** preparable_stmt)
{
LEX* lex = get_lex(stmt);
if (lex->sql_command == SQLCOM_PREPARE)
if ((lex->sql_command == SQLCOM_PREPARE) && !lex->describe)
{
parsing_info_t* pi = get_pinfo(stmt);
if (!pi->preparable_stmt)
{
const char* preparable_stmt = lex->prepared_stmt_code.str;
size_t preparable_stmt_len = lex->prepared_stmt_code.length;
const char* preparable_stmt;
size_t preparable_stmt_len;
#if MYSQL_VERSION_MINOR >= 3
preparable_stmt = lex->prepared_stmt_code->name.str;
preparable_stmt_len = lex->prepared_stmt_code->name.length;
#else
preparable_stmt = lex->prepared_stmt_code.str;
preparable_stmt_len = lex->prepared_stmt_code.length;
#endif
size_t payload_len = preparable_stmt_len + 1;
size_t packet_len = MYSQL_HEADER_LEN + payload_len;
@ -1861,9 +2111,13 @@ static bool should_exclude(const char* name, List<Item>* excludep)
while (!exclude && (exclude_item = ilist++))
{
const char* exclude_name = exclude_item->name;
const char* exclude_name;
size_t length;
get_string_and_length(exclude_item->name, &exclude_name, &length);
if (exclude_name && (strcasecmp(name, exclude_name) == 0))
if (exclude_name &&
(strlen(name) == length) &&
(strcasecmp(name, exclude_name) == 0))
{
exclude = true;
}
@ -1981,6 +2235,8 @@ static void add_function_info(parsing_info_t* info,
{
ss_dassert(name);
name = map_function_name(info->function_name_mappings, name);
QC_FUNCTION_INFO item = { (char*)name, usage };
size_t i;
@ -2036,7 +2292,12 @@ static void add_field_info(parsing_info_t* pi, Item_field* item, uint32_t usage,
{
const char* database = item->db_name;
const char* table = item->table_name;
const char* column = item->field_name;
const char* s;
size_t l;
get_string_and_length(item->field_name, &s, &l);
char column[l + 1];
strncpy(column, s, l);
column[l] = 0;
LEX* lex = get_lex(pi);
@ -2125,7 +2386,12 @@ static void add_field_info(parsing_info_t* pi, Item* item, uint32_t usage, List<
{
const char* database = NULL;
const char* table = NULL;
const char* column = item->name;
const char* s;
size_t l;
get_string_and_length(item->name, &s, &l);
char column[l + 1];
strncpy(column, s, l);
column[l] = 0;
add_field_info(pi, database, table, column, usage, excludep);
}
@ -2161,6 +2427,43 @@ static void remove_surrounding_back_ticks(char* s)
}
}
static bool should_function_be_ignored(parsing_info_t* pi, const char* func_name)
{
bool rv = false;
// We want to ignore functions that do not really appear as such in an
// actual SQL statement. E.g. "SELECT @a" appears as a function "get_user_var".
if ((strcasecmp(func_name, "decimal_typecast") == 0) ||
(strcasecmp(func_name, "cast_as_char") == 0) ||
(strcasecmp(func_name, "cast_as_date") == 0) ||
(strcasecmp(func_name, "cast_as_datetime") == 0) ||
(strcasecmp(func_name, "cast_as_time") == 0) ||
(strcasecmp(func_name, "cast_as_signed") == 0) ||
(strcasecmp(func_name, "cast_as_unsigned") == 0) ||
(strcasecmp(func_name, "get_user_var") == 0) ||
(strcasecmp(func_name, "get_system_var") == 0) ||
(strcasecmp(func_name, "set_user_var") == 0) ||
(strcasecmp(func_name, "set_system_var") == 0))
{
rv = true;
}
// Any sequence related functions should be ignored as well.
#if MYSQL_VERSION_MAJOR >= 10 && MYSQL_VERSION_MINOR >= 3
if (!rv)
{
if ((strcasecmp(func_name, "lastval") == 0) ||
(strcasecmp(func_name, "nextval") == 0))
{
pi->type_mask |= QUERY_TYPE_WRITE;
rv = true;
}
}
#endif
return rv;
}
static void update_field_infos(parsing_info_t* pi,
collect_source_t source,
Item* item,
@ -2278,24 +2581,17 @@ static void update_field_infos(parsing_info_t* pi,
// We want to ignore functions that do not really appear as such in an
// actual SQL statement. E.g. "SELECT @a" appears as a function "get_user_var".
if ((strcasecmp(func_name, "decimal_typecast") != 0) &&
(strcasecmp(func_name, "cast_as_char") != 0) &&
(strcasecmp(func_name, "cast_as_date") != 0) &&
(strcasecmp(func_name, "cast_as_datetime") != 0) &&
(strcasecmp(func_name, "cast_as_time") != 0) &&
(strcasecmp(func_name, "cast_as_signed") != 0) &&
(strcasecmp(func_name, "cast_as_unsigned") != 0) &&
(strcasecmp(func_name, "get_user_var") != 0) &&
(strcasecmp(func_name, "get_system_var") != 0) &&
(strcasecmp(func_name, "set_user_var") != 0) &&
(strcasecmp(func_name, "set_system_var") != 0))
if (!should_function_be_ignored(pi, func_name))
{
if (strcmp(func_name, "%") == 0)
{
// Embedded library silently changes "mod" into "%". We need to check
// what it originally was, so that the result agrees with that of
// qc_sqlite.
if (func_item->name && (strncasecmp(func_item->name, "mod", 3) == 0))
const char* s;
size_t l;
get_string_and_length(func_item->name, &s, &l);
if (s && (strncasecmp(s, "mod", 3) == 0))
{
strcpy(func_name, "mod");
}
@ -2311,7 +2607,10 @@ static void update_field_infos(parsing_info_t* pi,
// Embedded library silently changes "substring" into "substr". We need
// to check what it originally was, so that the result agrees with
// that of qc_sqlite. We reserved space for this above.
if (func_item->name && (strncasecmp(func_item->name, "substring", 9) == 0))
const char* s;
size_t l;
get_string_and_length(func_item->name, &s, &l);
if (s && (strncasecmp(s, "substring", 9) == 0))
{
strcpy(func_name, "substring");
}
@ -2472,6 +2771,9 @@ static void update_field_infos(parsing_info_t* pi,
int32_t qc_mysql_get_field_info(GWBUF* buf, const QC_FIELD_INFO** infos, uint32_t* n_infos)
{
*infos = NULL;
*n_infos = 0;
if (!buf)
{
return QC_RESULT_OK;
@ -2479,7 +2781,7 @@ int32_t qc_mysql_get_field_info(GWBUF* buf, const QC_FIELD_INFO** infos, uint32_
if (!ensure_query_is_parsed(buf))
{
return QC_RESULT_ERROR;;
return QC_RESULT_ERROR;
}
parsing_info_t* pi = get_pinfo(buf);
@ -2495,6 +2797,13 @@ int32_t qc_mysql_get_field_info(GWBUF* buf, const QC_FIELD_INFO** infos, uint32_
return QC_RESULT_ERROR;
}
if (lex->describe || is_show_command(lex->sql_command))
{
*infos = NULL;
*n_infos = 0;
return QC_RESULT_OK;
}
uint32_t usage = 0;
switch (lex->sql_command)
@ -2587,19 +2896,27 @@ int32_t qc_mysql_get_function_info(GWBUF* buf,
*function_infos = NULL;
*n_function_infos = 0;
const QC_FIELD_INFO* field_infos;
uint32_t n_field_infos;
int32_t rv = QC_RESULT_OK;
// We ensure the information has been collected by querying the fields first.
qc_mysql_get_field_info(buf, &field_infos, &n_field_infos);
if (buf)
{
const QC_FIELD_INFO* field_infos;
uint32_t n_field_infos;
parsing_info_t* pi = get_pinfo(buf);
ss_dassert(pi);
// We ensure the information has been collected by querying the fields first.
rv = qc_mysql_get_field_info(buf, &field_infos, &n_field_infos);
*function_infos = pi->function_infos;
*n_function_infos = pi->function_infos_len;
if (rv == QC_RESULT_OK)
{
parsing_info_t* pi = get_pinfo(buf);
ss_dassert(pi);
return QC_RESULT_OK;
*function_infos = pi->function_infos;
*n_function_infos = pi->function_infos_len;
}
}
return rv;
}
void qc_mysql_set_server_version(uint64_t version)
@ -2622,7 +2939,10 @@ const char* server_options[] =
"--no-defaults",
"--datadir=",
"--language=",
#if MYSQL_VERSION_MINOR < 3
// TODO: 10.3 understands neither "--skip-innodb" or "--innodb=OFF", although it should.
"--skip-innodb",
#endif
"--default-storage-engine=myisam",
NULL
};
@ -2668,12 +2988,19 @@ void configure_options(const char* datadir, const char* langdir)
}
int32_t qc_mysql_setup(const char* args)
int32_t qc_mysql_setup(qc_sql_mode_t sql_mode, const char* zArgs)
{
if (args)
this_unit.sql_mode = sql_mode;
if (sql_mode == QC_SQL_MODE_ORACLE)
{
this_unit.function_name_mappings = function_name_mappings_oracle;
}
if (zArgs)
{
MXS_WARNING("'%s' provided as arguments, "
"even though no arguments are supported.", args);
"even though no arguments are supported.", zArgs);
}
return QC_RESULT_OK;
@ -2699,6 +3026,10 @@ int32_t qc_mysql_process_init(void)
if (rc != 0)
{
this_thread.sql_mode = this_unit.sql_mode;
ss_dassert(this_unit.function_name_mappings);
this_thread.function_name_mappings = this_unit.function_name_mappings;
MXS_ERROR("mysql_library_init() failed. Error code: %d", rc);
}
else
@ -2721,6 +3052,10 @@ void qc_mysql_process_end(void)
int32_t qc_mysql_thread_init(void)
{
this_thread.sql_mode = this_unit.sql_mode;
ss_dassert(this_unit.function_name_mappings);
this_thread.function_name_mappings = this_unit.function_name_mappings;
bool inited = (mysql_thread_init() == 0);
if (!inited)
@ -2736,6 +3071,35 @@ void qc_mysql_thread_end(void)
mysql_thread_end();
}
int32_t qc_mysql_get_sql_mode(qc_sql_mode_t* sql_mode)
{
*sql_mode = this_thread.sql_mode;
return QC_RESULT_OK;
}
int32_t qc_mysql_set_sql_mode(qc_sql_mode_t sql_mode)
{
int32_t rv = QC_RESULT_OK;
switch (sql_mode)
{
case QC_SQL_MODE_DEFAULT:
this_thread.sql_mode = sql_mode;
this_thread.function_name_mappings = function_name_mappings_default;
break;
case QC_SQL_MODE_ORACLE:
this_thread.sql_mode = sql_mode;
this_thread.function_name_mappings = function_name_mappings_oracle;
break;
default:
rv = QC_RESULT_ERROR;
}
return rv;
}
/**
* EXPORTS
*/
@ -2767,6 +3131,8 @@ extern "C"
qc_mysql_get_preparable_stmt,
qc_mysql_set_server_version,
qc_mysql_get_server_version,
qc_mysql_get_sql_mode,
qc_mysql_set_sql_mode,
};
static MXS_MODULE info =

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -64,6 +64,7 @@ enum
{
QUERY_TYPE_READ = 0x000002, /*< Read database data:any */
QUERY_TYPE_WRITE = 0x000004, /*< Master data will be modified:master */
QUERY_TYPE_USERVAR_READ = 0x000040, /*< Read a user variable:master or any */
};
typedef enum qc_field_usage
@ -85,7 +86,7 @@ typedef enum qc_field_usage
extern void mxs_sqlite3AlterFinishAddColumn(Parse *, Token *);
extern void mxs_sqlite3AlterBeginAddColumn(Parse *, SrcList *);
extern void mxs_sqlite3Analyze(Parse *, SrcList *);
extern void mxs_sqlite3BeginTransaction(Parse*, int);
extern void mxs_sqlite3BeginTransaction(Parse*, int token, int type);
extern void mxs_sqlite3CommitTransaction(Parse*);
extern void mxs_sqlite3CreateIndex(Parse*,Token*,Token*,SrcList*,ExprList*,int,Token*,
Expr*, int, int);
@ -107,18 +108,21 @@ extern void mxs_sqlite3Update(Parse*, SrcList*, ExprList*, Expr*, int);
extern void maxscaleCollectInfoFromSelect(Parse*, Select*, int);
extern void maxscaleAlterTable(Parse*, mxs_alter_t command, SrcList*, Token*);
extern void maxscaleCall(Parse*, SrcList* pName);
extern void maxscaleCall(Parse*, SrcList* pName, ExprList* pExprList);
extern void maxscaleCheckTable(Parse*, SrcList* pTables);
extern void maxscaleCreateSequence(Parse*, Token* pDatabase, Token* pTable);
extern void maxscaleDeclare(Parse* pParse);
extern void maxscaleDeallocate(Parse*, Token* pName);
extern void maxscaleDo(Parse*, ExprList* pEList);
extern void maxscaleDrop(Parse*, MxsDrop* pDrop);
extern void maxscaleExecute(Parse*, Token* pName);
extern void maxscaleExplain(Parse*, SrcList* pName);
extern void maxscaleDrop(Parse*, int what, Token* pDatabase, Token* pName);
extern void maxscaleExecute(Parse*, Token* pName, int type_mask);
extern void maxscaleExecuteImmediate(Parse*, Token* pName, ExprSpan* pExprSpan, int type_mask);
extern void maxscaleExplain(Parse*, Token* pNext);
extern void maxscaleFlush(Parse*, Token* pWhat);
extern void maxscaleHandler(Parse*, mxs_handler_t, SrcList* pFullName, Token* pName);
extern void maxscaleLoadData(Parse*, SrcList* pFullName);
extern void maxscaleLock(Parse*, mxs_lock_t, SrcList*);
extern void maxscalePrepare(Parse*, Token* pName, Token* pStmt);
extern void maxscalePrepare(Parse*, Token* pName, Expr* pStmt);
extern void maxscalePrivileges(Parse*, int kind);
extern void maxscaleRenameTable(Parse*, SrcList* pTables);
extern void maxscaleSet(Parse*, int scope, mxs_set_t kind, ExprList*);
@ -276,33 +280,23 @@ input ::= cmdlist.
cmdlist ::= cmdlist ecmd.
cmdlist ::= ecmd.
ecmd ::= SEMI.
ecmd ::= explain cmdx SEMI.
ecmd ::= explain SEMI.
ecmd ::= cmdx SEMI.
ecmd ::= oracle_assignment SEMI.
%ifdef MAXSCALE
explain_kw ::= EXPLAIN. // Also covers DESCRIBE
explain_kw ::= DESC.
ecmd ::= explain_kw fullname(X) SEMI. {
pParse->explain = 1;
maxscaleExplain(pParse, X);
}
explain ::= explain_kw. { pParse->explain = 1; }
// deferred_id is defined later, after the id token_class has been defined.
ecmd ::= explain FOR deferred_id INTEGER SEMI. { // FOR CONNECTION connection_id
explain ::= explain_kw deferred_id(A). { maxscaleExplain(pParse, &A); }
explain ::= explain_kw deferred_id(A) DOT deferred_id. { maxscaleExplain(pParse, &A); }
ecmd ::= explain FOR(A) deferred_id INTEGER SEMI. { // FOR CONNECTION connection_id
pParse->explain = 1;
maxscaleExplain(pParse, 0);
maxscaleExplain(pParse, &A);
}
%endif
explain ::= .
%ifndef SQLITE_OMIT_EXPLAIN
%ifdef MAXSCALE
explain_type_opt ::= .
explain_type_opt ::= deferred_id. // EXTENDED | PARTITIONS
explain_type_opt ::= deferred_id eq deferred_id. // FORMAT = {TRADITIONAL|JSON}
explain ::= explain_kw explain_type_opt. { pParse->explain = 1; }
%endif
%ifndef MAXSCALE
explain ::= EXPLAIN. { pParse->explain = 1; }
%endif
%ifndef MAXSCALE
explain ::= EXPLAIN QUERY PLAN. { pParse->explain = 2; }
%endif
@ -315,7 +309,7 @@ cmdx ::= cmd. { sqlite3FinishCoding(pParse); }
%ifdef MAXSCALE
work_opt ::= WORK.
work_opt ::= .
cmd ::= BEGIN work_opt. {mxs_sqlite3BeginTransaction(pParse, 0);} // BEGIN [WORK]
cmd ::= BEGIN work_opt. {mxs_sqlite3BeginTransaction(pParse, TK_BEGIN, 0);} // BEGIN [WORK]
%endif
%ifndef MAXSCALE
cmd ::= BEGIN transtype(Y) trans_opt. {sqlite3BeginTransaction(pParse, Y);}
@ -590,14 +584,14 @@ columnid(A) ::= nm(X). {
%endif
%ifdef MAXSCALE
/*ABORT*/ ACTION AFTER ALGORITHM /*ANALYZE*/ /*ASC*/ /*ATTACH*/
/*BEFORE*/ BEGIN BY
/*BEFORE*/ /*BEGIN*/ BY
// TODO: BINARY is a reserved word and should not automatically convert into an identifer.
// TODO: However, if not here then rules such as CAST need to be modified.
BINARY
/*CASCADE*/ CAST CLOSE COLUMNKW COLUMNS COMMENT CONCURRENT /*CONFLICT*/
DATA /*DATABASE*/ DEALLOCATE DEFERRED /*DESC*/ /*DETACH*/ DUMPFILE
/*EACH*/ END ENUM EXCLUSIVE /*EXPLAIN*/
FIRST FLUSH /*FOR*/
FIRST FLUSH /*FOR*/ FORMAT
GLOBAL
// TODO: IF is a reserved word and should not automatically convert into an identifer.
IF IMMEDIATE INITIALLY INSTEAD
@ -607,10 +601,10 @@ columnid(A) ::= nm(X). {
NO
OF OFFSET OPEN
QUICK
RAISE RECURSIVE /*REINDEX*/ RELEASE /*RENAME*/ REPLACE RESTRICT ROLLBACK ROLLUP ROW
SAVEPOINT SELECT_OPTIONS_KW SLAVE START STATUS
RAISE RECURSIVE /*REINDEX*/ RELEASE /*RENAME*/ /*REPLACE*/ RESTRICT ROLLBACK ROLLUP ROW
SAVEPOINT SELECT_OPTIONS_KW /*SEQUENCE*/ SLAVE /*START*/ STATUS
TABLES TEMP TEMPTABLE /*TRIGGER*/
TRUNCATE
/*TRUNCATE*/
// TODO: UNSIGNED is a reserved word and should not automatically convert into an identifer.
// TODO: However, if not here then rules such as CAST need to be modified.
UNSIGNED
@ -663,6 +657,10 @@ eq ::= EQ.
nm(A) ::= id(X). {A = X;}
nm(A) ::= STRING(X). {A = X;}
nm(A) ::= JOIN_KW(X). {A = X;}
nm(A) ::= START(X). {A = X;}
nm(A) ::= TRUNCATE(X). {A = X;}
nm(A) ::= BEGIN(X). {A = X;}
nm(A) ::= REPLACE(X). {A = X;}
// A typetoken is really one or more tokens that form a type name such
// as can be found after the column name in a CREATE TABLE statement.
@ -1122,6 +1120,7 @@ select_into(A) ::= INTO OUTFILE STRING. {A = sqlite3ExprListAppend(pParse, 0, 0)
%type select_options {int}
select_options(A) ::= . {A = 0;}
select_options(A) ::= select_options DISTINCT. {A = SF_Distinct;}
select_options(A) ::= select_options UNIQUE. {A = SF_Distinct;}
select_options(A) ::= select_options ALL. {A = SF_All;}
select_options(A) ::= select_options(X) HIGH_PRIORITY. {A = X;}
select_options(A) ::= select_options(X) SELECT_OPTIONS_KW. {A = X;}
@ -1131,6 +1130,9 @@ select_options(A) ::= select_options(X) STRAIGHT_JOIN. {A = X;}
// present and false (0) if it is not.
//
%type distinct {int}
%ifdef MAXSCALE
distinct(A) ::= UNIQUE. {A = SF_Distinct;}
%endif
distinct(A) ::= DISTINCT. {A = SF_Distinct;}
distinct(A) ::= ALL. {A = SF_All;}
distinct(A) ::= . {A = 0;}
@ -1771,13 +1773,18 @@ term(A) ::= DEFAULT(X). {spanExpr(&A, pParse, @X, &X);}
%endif
term(A) ::= NULL(X). {spanExpr(&A, pParse, @X, &X);}
expr(A) ::= id(X). {spanExpr(&A, pParse, TK_ID, &X);}
expr(A) ::= JOIN_KW(X). {spanExpr(&A, pParse, TK_ID, &X);}
expr(A) ::= nm(X) DOT nm(Y). {
Expr *temp1 = sqlite3PExpr(pParse, TK_ID, 0, 0, &X);
Expr *temp2 = sqlite3PExpr(pParse, TK_ID, 0, 0, &Y);
A.pExpr = sqlite3PExpr(pParse, TK_DOT, temp1, temp2, 0);
spanSet(&A,&X,&Y);
}
expr(A) ::= DOT nm(X) DOT nm(Y). {
Expr *temp1 = sqlite3PExpr(pParse, TK_ID, 0, 0, &X);
Expr *temp2 = sqlite3PExpr(pParse, TK_ID, 0, 0, &Y);
A.pExpr = sqlite3PExpr(pParse, TK_DOT, temp1, temp2, 0);
spanSet(&A,&X,&Y);
}
expr(A) ::= nm(X) DOT nm(Y) DOT nm(Z). {
Expr *temp1 = sqlite3PExpr(pParse, TK_ID, 0, 0, &X);
Expr *temp2 = sqlite3PExpr(pParse, TK_ID, 0, 0, &Y);
@ -2675,7 +2682,7 @@ type_options(A) ::= type_options CHARACTER SET ids. {A|=8;}
type_options(A) ::= type_options CHARSET ids. {A|=8;}
// deferred_id is used instead of id before the token_class id has been defined.
deferred_id ::= id.
deferred_id(A) ::= id(X). {A=X;}
as_opt ::= .
as_opt ::= AS.
@ -2690,31 +2697,18 @@ default_opt ::= DEFAULT.
//
cmd ::= call.
call_arg ::= INTEGER.
call_arg ::= FLOAT.
call_arg ::= STRING.
call_arg ::= id.
call_arg ::= VARIABLE.
%type call_args_opt {ExprList*}
call_args_opt(A) ::= . {A=0;}
call_args_opt(A) ::= LP exprlist(X) RP. {A=X;}
call_args ::= call_arg.
call_args ::= call_args COMMA call_arg.
call_args_opt ::= .
call_args_opt ::= LP RP.
call_args_opt ::= LP call_args RP.
call ::= CALL fullname(X) call_args_opt. {
maxscaleCall(pParse, X);
call ::= CALL fullname(X) call_args_opt(Y). {
maxscaleCall(pParse, X, Y);
}
//////////////////////// DROP FUNCTION statement ////////////////////////////////////
//
cmd ::= DROP FUNCTION_KW ifexists nm(X). {
MxsDrop drop;
drop.what = MXS_DROP_FUNCTION;
drop.token = X;
maxscaleDrop(pParse, &drop);
maxscaleDrop(pParse, MXS_DROP_FUNCTION, NULL, &X);
}
//////////////////////// The CHECK TABLE statement ////////////////////////////////////
@ -2845,19 +2839,32 @@ cmd ::= prepare.
cmd ::= execute.
cmd ::= deallocate.
prepare ::= PREPARE nm(X) FROM STRING(Y).
prepare ::= PREPARE nm(X) FROM expr(Y).
{
maxscalePrepare(pParse, &X, &Y);
maxscalePrepare(pParse, &X, Y.pExpr);
}
execute_variables ::= VARIABLE.
execute_variables ::= execute_variables COMMA VARIABLE.
%type execute_variable {int}
execute_variable(A) ::= INTEGER. {A=0;} // For Oracle
execute_variable(A) ::= VARIABLE. {A=QUERY_TYPE_USERVAR_READ;}
execute_variables_opt ::= .
execute_variables_opt ::= USING execute_variables.
%type execute_variables {int}
execute_variables(A) ::= execute_variable(X). {A=X;}
execute_variables(A) ::= execute_variables(X) COMMA execute_variable(Y). {
A = X|Y;
}
execute ::= EXECUTE nm(X) execute_variables_opt. {
maxscaleExecute(pParse, &X);
%type execute_variables_opt {int}
execute_variables_opt(A) ::= . {A=0;}
execute_variables_opt(A) ::= USING execute_variables(X). {A=X;}
execute ::= EXECUTE nm(X) execute_variables_opt(Y). {
maxscaleExecute(pParse, &X, Y);
}
execute ::= EXECUTE id(X) expr(Y) execute_variables_opt(Z). {
maxscaleExecuteImmediate(pParse, &X, &Y, Z);
}
dod ::= DEALLOCATE.
@ -3092,6 +3099,19 @@ show(A) ::= SHOW CREATE VIEW nm(X) dbnm(Y). {
}
}
show(A) ::= SHOW CREATE SEQUENCE nm(X) dbnm(Y). {
A.what = MXS_SHOW_CREATE_SEQUENCE;
A.data = 0;
if (Y.z) {
A.pName = &Y;
A.pDatabase = &X;
}
else {
A.pName = &X;
A.pDatabase = NULL;
}
}
show(A) ::= SHOW DATABASES_KW like_or_where_opt. {
A.what = MXS_SHOW_DATABASES;
A.data = 0;
@ -3219,7 +3239,7 @@ start_transaction_characteristics(A) ::=
}
cmd ::= START TRANSACTION start_transaction_characteristics(X). {
mxs_sqlite3BeginTransaction(pParse, X);
mxs_sqlite3BeginTransaction(pParse, TK_START, X);
}
//////////////////////// The TRUNCATE statement ////////////////////////////////////
@ -3243,4 +3263,55 @@ cmd ::= TRUNCATE table_opt nm(X) dbnm(Y). {
maxscaleTruncate(pParse, pDatabase, pName);
}
//////////////////////// ORACLE Assignment ////////////////////////////////////
//
oracle_assignment ::= id(X) EQ expr(Y). {
Expr* pX = sqlite3PExpr(pParse, TK_ID, 0, 0, &X);
Expr* pExpr = sqlite3PExpr(pParse, TK_EQ, pX, Y.pExpr, 0);
ExprList* pExprList = sqlite3ExprListAppend(pParse, 0, pExpr);
maxscaleSet(pParse, 0, MXS_SET_VARIABLES, pExprList);
}
//////////////////////// ORACLE CREATE SEQUENCE ////////////////////////////////////
//
cmd ::= CREATE SEQUENCE nm(X) dbnm(Y).{ // CREATE SEQUENCE db
Token* pDatabase;
Token* pTable;
if (Y.z)
{
pDatabase = &X;
pTable = &Y;
}
else
{
pDatabase = NULL;
pTable = &X;
}
maxscaleCreateSequence(pParse, pDatabase, pTable);
}
//////////////////////// ORACLE CREATE SEQUENCE ////////////////////////////////////
//
cmd ::= DROP SEQUENCE nm(X) dbnm(Y).{ // CREATE SEQUENCE db
Token* pDatabase;
Token* pTable;
if (Y.z)
{
pDatabase = &X;
pTable = &Y;
}
else
{
pDatabase = NULL;
pTable = &X;
}
maxscaleDrop(pParse, MXS_DROP_SEQUENCE, pDatabase, pTable);
}
//////////////////////// ORACLE DECLARE ////////////////////////////////////
//
cmd ::= DECLARE. {
maxscaleDeclare(pParse);
}
%endif

View File

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

View File

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

View File

@ -218,6 +218,7 @@ static Keyword aKeywordTable[] = {
#ifdef MAXSCALE
{ "DATABASES", "TK_DATABASES_KW", ALWAYS },
{ "DEALLOCATE", "TK_DEALLOCATE", ALWAYS },
{ "DECLARE", "TK_DECLARE", ALWAYS },
#endif
{ "DEFAULT", "TK_DEFAULT", ALWAYS },
{ "DEFERRED", "TK_DEFERRED", ALWAYS },
@ -268,6 +269,9 @@ static Keyword aKeywordTable[] = {
{ "FORCE", "TK_FORCE", ALWAYS },
#endif
{ "FOREIGN", "TK_FOREIGN", FKEY },
#ifdef MAXSCALE
{ "FORMAT", "TK_FORMAT", ALWAYS },
#endif
{ "FROM", "TK_FROM", ALWAYS },
{ "FULL", "TK_JOIN_KW", ALWAYS },
#ifdef MAXSCALE
@ -396,6 +400,7 @@ static Keyword aKeywordTable[] = {
{ "SAVEPOINT", "TK_SAVEPOINT", ALWAYS },
#ifdef MAXSCALE
{ "SCHEMAS", "TK_DATABASES_KW", ALWAYS },
{ "SEQUENCE", "TK_SEQUENCE", ALWAYS },
#endif
{ "SELECT", "TK_SELECT", ALWAYS },
#ifdef MAXSCALE

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,37 @@
--source include/not_embedded.inc
--source include/have_binlog_format_statement.inc
--disable_query_log
#qc_sqlite: reset master; # get rid of previous tests binlog
--enable_query_log
SET sql_mode=ORACLE;
--echo #
--echo # MDEV-10801 sql_mode: dynamic SQL placeholders
--echo #
CREATE TABLE t1 (a INT, b INT);
SET @a=10, @b=20;
PREPARE stmt FROM 'INSERT INTO t1 VALUES (?,?)';
EXECUTE stmt USING @a, @b;
PREPARE stmt FROM 'INSERT INTO t1 VALUES (:a,:b)';
EXECUTE stmt USING @a, @b;
PREPARE stmt FROM 'INSERT INTO t1 VALUES (:aaa,:bbb)';
EXECUTE stmt USING @a, @b;
PREPARE stmt FROM 'INSERT INTO t1 VALUES (:"a",:"b")';
EXECUTE stmt USING @a, @b;
PREPARE stmt FROM 'INSERT INTO t1 VALUES (:"aaa",:"bbb")';
EXECUTE stmt USING @a, @b;
PREPARE stmt FROM 'INSERT INTO t1 VALUES (:1,:2)';
EXECUTE stmt USING @a, @b;
PREPARE stmt FROM 'INSERT INTO t1 VALUES (:222,:111)';
EXECUTE stmt USING @a, @b;
PREPARE stmt FROM 'INSERT INTO t1 VALUES (:0,:65535)';
EXECUTE stmt USING @a, @b;
PREPARE stmt FROM 'INSERT INTO t1 VALUES (:65535,:0)';
EXECUTE stmt USING @a, @b;
SELECT * FROM t1;
--let $binlog_file = LAST
source include/show_binlog_events.inc;
DROP TABLE t1;

View File

@ -0,0 +1,196 @@
--source include/not_embedded.inc
--source include/have_binlog_format_statement.inc
--disable_query_log
call mtr.add_suppression("Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT");
#qc_sqlite: reset master; # get rid of previous tests binlog
--enable_query_log
SET sql_mode=ORACLE;
--echo #
--echo # MDEV-10914 ROW data type for stored routine variables
--echo #
CREATE TABLE t1 (a INT, b INT);
DELIMITER $$;
CREATE PROCEDURE p1
AS
rec ROW(a INT,b INT);
BEGIN
rec.a:=100;
rec.b:=200;
INSERT INTO t1 VALUES (rec.a,rec.b);
INSERT INTO t1 VALUES (10, rec=ROW(100,200));
INSERT INTO t1 VALUES (10, ROW(100,200)=rec);
INSERT INTO t1 SELECT 10, 20 FROM DUAL WHERE rec=ROW(100,200);
INSERT INTO t1 SELECT 10, 21 FROM DUAL WHERE ROW(100,200)=rec;
rec.a:=NULL;
INSERT INTO t1 VALUES (11, rec=ROW(100,200));
INSERT INTO t1 VALUES (11, rec=ROW(100,201));
INSERT INTO t1 VALUES (11, ROW(100,200)=rec);
INSERT INTO t1 VALUES (11, ROW(100,201)=rec);
INSERT INTO t1 SELECT 11, 20 FROM DUAL WHERE rec=ROW(100,200);
INSERT INTO t1 SELECT 11, 21 FROM DUAL WHERE ROW(100,200)=rec;
rec.b:=NULL;
INSERT INTO t1 VALUES (12, rec=ROW(100,200));
INSERT INTO t1 VALUES (12, ROW(100,200)=rec);
INSERT INTO t1 SELECT 12, 20 FROM DUAL WHERE rec=ROW(100,200);
INSERT INTO t1 SELECT 12, 21 FROM DUAL WHERE ROW(100,200)=rec;
END;
$$
DELIMITER ;$$
CALL p1();
SELECT * FROM t1;
DROP TABLE t1;
DROP PROCEDURE p1;
--let $binlog_file = LAST
source include/show_binlog_events.inc;
--echo #
--echo # Testing ROW fields in LIMIT
--echo #
FLUSH LOGS;
CREATE TABLE t1 (a INT);
INSERT INTO t1 VALUES (10),(10);
CREATE TABLE t2 (a INT);
DELIMITER $$;
CREATE PROCEDURE p1()
AS
a INT:= 1;
rec ROW(a INT);
BEGIN
rec.a:= 1;
INSERT INTO t2 SELECT 1 FROM t1 LIMIT a;
INSERT INTO t2 SELECT 2 FROM t1 LIMIT rec.a;
END;
$$
DELIMITER ;$$
CALL p1();
DROP TABLE t1,t2;
DROP PROCEDURE p1;
--let $binlog_file = LAST
source include/show_binlog_events.inc;
--echo #
--echo # End of MDEV-10914 ROW data type for stored routine variables
--echo #
--echo #
--echo # MDEV-12133 sql_mode=ORACLE: table%ROWTYPE in variable declarations
--echo #
CREATE TABLE t1 (a INT, b INT);
DELIMITER $$;
CREATE PROCEDURE p1
AS
rec t1%ROWTYPE;
BEGIN
rec.a:=100;
rec.b:=200;
SELECT rec=ROW(100,200) AS true1, ROW(100,200)=rec AS true2;
INSERT INTO t1 VALUES (rec.a,rec.b);
INSERT INTO t1 VALUES (10, rec=ROW(100,200));
INSERT INTO t1 VALUES (10, ROW(100,200)=rec);
INSERT INTO t1 SELECT 10, 20 FROM DUAL WHERE rec=ROW(100,200);
INSERT INTO t1 SELECT 10, 21 FROM DUAL WHERE ROW(100,200)=rec;
rec.a:=NULL;
INSERT INTO t1 VALUES (11, rec=ROW(100,200));
INSERT INTO t1 VALUES (11, rec=ROW(100,201));
INSERT INTO t1 VALUES (11, ROW(100,200)=rec);
INSERT INTO t1 VALUES (11, ROW(100,201)=rec);
INSERT INTO t1 SELECT 11, 20 FROM DUAL WHERE rec=ROW(100,200);
INSERT INTO t1 SELECT 11, 21 FROM DUAL WHERE ROW(100,200)=rec;
rec.b:=NULL;
INSERT INTO t1 VALUES (12, rec=ROW(100,200));
INSERT INTO t1 VALUES (12, ROW(100,200)=rec);
INSERT INTO t1 SELECT 12, 20 FROM DUAL WHERE rec=ROW(100,200);
INSERT INTO t1 SELECT 12, 21 FROM DUAL WHERE ROW(100,200)=rec;
END;
$$
DELIMITER ;$$
CALL p1();
SELECT * FROM t1;
DROP TABLE t1;
DROP PROCEDURE p1;
--let $binlog_file = LAST
source include/show_binlog_events.inc;
--echo #
--echo # MDEV-12291 Allow ROW variables as SELECT INTO targets
--echo #
FLUSH LOGS;
CREATE TABLE t1 (a INT, b VARCHAR(32));
INSERT INTO t1 VALUES (10, 'b10');
CREATE TABLE t2 LIKE t1;
DELIMITER $$;
CREATE PROCEDURE p1
AS
rec1 ROW(a INT, b VARCHAR(32));
BEGIN
SELECT * INTO rec1 FROM t1;
INSERT INTO t2 VALUES (rec1.a, rec1.b);
END;
$$
DELIMITER ;$$
CALL p1();
SELECT * FROM t1;
DROP TABLE t1;
DROP TABLE t2;
DROP PROCEDURE p1;
--let $binlog_file = LAST
source include/show_binlog_events.inc;
FLUSH LOGS;
CREATE TABLE t1 (a INT, b VARCHAR(32));
INSERT INTO t1 VALUES (10, 'b10');
CREATE TABLE t2 LIKE t1;
DELIMITER $$;
CREATE PROCEDURE p1
AS
rec1 t1%ROWTYPE;
BEGIN
SELECT * INTO rec1 FROM t1;
INSERT INTO t2 VALUES (rec1.a, rec1.b);
END;
$$
DELIMITER ;$$
CALL p1();
SELECT * FROM t1;
DROP TABLE t1;
DROP TABLE t2;
DROP PROCEDURE p1;
--let $binlog_file = LAST
source include/show_binlog_events.inc;
FLUSH LOGS;
CREATE TABLE t1 (a INT, b VARCHAR(32));
INSERT INTO t1 VALUES (10, 'b10');
CREATE TABLE t2 LIKE t1;
DELIMITER $$;
CREATE PROCEDURE p1
AS
CURSOR cur1 IS SELECT * FROM t1;
rec1 cur1%ROWTYPE;
BEGIN
SELECT * INTO rec1 FROM t1;
INSERT INTO t2 VALUES (rec1.a, rec1.b);
END;
$$
DELIMITER ;$$
CALL p1();
SELECT * FROM t1;
DROP TABLE t1;
DROP TABLE t2;
DROP PROCEDURE p1;
--let $binlog_file = LAST
source include/show_binlog_events.inc;

View File

@ -0,0 +1,457 @@
SET sql_mode=ORACLE;
--echo #
--echo # sql_mode=ORACLE: Predefined exceptions: TOO_MANY_ROWS, NO_DATA_FOUND, DUP_VAL_ON_INDEX
--echo #
--echo #
--echo # Testing NO_DATA_FOUND and TOO_MANY_ROWS
--echo #
CREATE TABLE t1 (a INT);
INSERT INTO t1 VALUES (10),(20);
DELIMITER $$;
CREATE PROCEDURE p1(lim INT, res OUT VARCHAR)
AS
a INT;
BEGIN
SELECT a INTO a FROM t1 LIMIT lim;
EXCEPTION
WHEN TOO_MANY_ROWS THEN res:='--- too_many_rows cought ---';
WHEN NO_DATA_FOUND THEN res:='--- no_data_found cought ---';
END;
$$
DELIMITER ;$$
SET @res='';
CALL p1(0, @res);
SELECT @res;
CALL p1(2, @res);
SELECT @res;
DROP PROCEDURE p1;
DROP TABLE t1;
--echo #
--echo # Testing DUP_VAL_ON_INDEX
--echo #
CREATE TABLE t1 (a INT PRIMARY KEY);
DELIMITER $$;
CREATE PROCEDURE p1(res OUT VARCHAR)
AS
BEGIN
INSERT INTO t1 VALUES (10);
INSERT INTO t1 VALUES (10);
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN res:='--- dup_val_on_index cought ---';
END;
$$
DELIMITER ;$$
SET @res='';
CALL p1(@res);
SELECT @res;
SELECT * FROM t1;
DROP PROCEDURE p1;
DROP TABLE t1;
--echo #
--echo # MDEV-10840 sql_mode=ORACLE: RAISE statement for predefined exceptions
--echo #
--echo #
--echo # RAISE outside of an SP context
--echo #
--error ER_SP_COND_MISMATCH
RAISE NO_DATA_FOUND;
--error ER_SP_COND_MISMATCH
RAISE INVALID_CURSOR;
--error ER_SP_COND_MISMATCH
RAISE DUP_VAL_ON_INDEX;
--error ER_SP_COND_MISMATCH
RAISE TOO_MANY_ROWS;
--error ER_RESIGNAL_WITHOUT_ACTIVE_HANDLER
RAISE;
--echo #
--echo # RAISE for an undefinite exception
--echo #
DELIMITER $$;
--error ER_SP_COND_MISMATCH
CREATE PROCEDURE p1
AS
BEGIN
RAISE xxx;
END;
$$
DELIMITER ;$$
--echo #
--echo # RAISE for predefined exceptions
--echo #
DELIMITER $$;
CREATE PROCEDURE p1
AS
BEGIN
RAISE no_data_found;
END;
$$
DELIMITER ;$$
CALL p1();
DROP PROCEDURE p1;
DELIMITER $$;
CREATE PROCEDURE p1
AS
BEGIN
RAISE invalid_cursor;
END;
$$
DELIMITER ;$$
--error ER_SP_CURSOR_NOT_OPEN
CALL p1();
DROP PROCEDURE p1;
DELIMITER $$;
CREATE PROCEDURE p1
AS
BEGIN
RAISE dup_val_on_index;
END;
$$
DELIMITER ;$$
--error ER_DUP_ENTRY
CALL p1();
DROP PROCEDURE p1;
DELIMITER $$;
CREATE PROCEDURE p1
AS
BEGIN
raise too_many_rows;
END;
$$
DELIMITER ;$$
--error ER_TOO_MANY_ROWS
CALL p1();
DROP PROCEDURE p1;
--echo #
--echo # RAISE with no exception name (resignal)
--echo #
DELIMITER $$;
CREATE PROCEDURE p1()
AS
BEGIN
RAISE;
END;
$$
DELIMITER ;$$
--error ER_RESIGNAL_WITHOUT_ACTIVE_HANDLER
CALL p1();
DROP PROCEDURE p1;
CREATE TABLE t1 (a INT);
INSERT INTO t1 VALUES (10),(20);
DELIMITER $$;
CREATE PROCEDURE p1(lim INT)
AS
a INT;
BEGIN
SELECT a INTO a FROM t1 LIMIT lim;
EXCEPTION
WHEN TOO_MANY_ROWS THEN RAISE;
WHEN NO_DATA_FOUND THEN RAISE;
END;
$$
DELIMITER ;$$
CALL p1(0);
--error ER_TOO_MANY_ROWS
CALL p1(2);
DROP PROCEDURE p1;
DROP TABLE t1;
CREATE TABLE t1 (a INT);
INSERT INTO t1 VALUES (10),(20);
DELIMITER $$;
CREATE PROCEDURE p1(lim INT)
AS
a INT;
BEGIN
SELECT a INTO a FROM t1 LIMIT lim;
EXCEPTION
WHEN OTHERS THEN RAISE;
END;
$$
DELIMITER ;$$
CALL p1(0);
--error ER_TOO_MANY_ROWS
CALL p1(2);
DROP PROCEDURE p1;
DROP TABLE t1;
CREATE TABLE t1 (a INT);
INSERT INTO t1 VALUES (10),(20);
DELIMITER $$;
CREATE PROCEDURE p1()
AS
a INT;
CURSOR c IS SELECT a FROM t1;
BEGIN
FETCH c INTO a;
EXCEPTION
WHEN INVALID_CURSOR THEN RAISE;
END;
$$
DELIMITER ;$$
--error ER_SP_CURSOR_NOT_OPEN
CALL p1();
DROP PROCEDURE p1;
DROP TABLE t1;
CREATE TABLE t1 (a INT);
INSERT INTO t1 VALUES (10),(20);
DELIMITER $$;
CREATE PROCEDURE p1()
AS
a INT;
CURSOR c IS SELECT a FROM t1;
BEGIN
FETCH c INTO a;
EXCEPTION
WHEN OTHERS THEN RAISE;
END;
$$
DELIMITER ;$$
--error ER_SP_CURSOR_NOT_OPEN
CALL p1();
DROP PROCEDURE p1;
DROP TABLE t1;
--echo #
--echo # Testing that warning-alike errors are caught by OTHERS
--echo #
CREATE TABLE t1 (a INT);
DELIMITER $$;
CREATE FUNCTION f1 RETURN VARCHAR
AS
a INT:=10;
BEGIN
SELECT a INTO a FROM t1;
RETURN 'OK';
EXCEPTION
WHEN OTHERS THEN RETURN 'Exception';
END;
$$
DELIMITER ;$$
SELECT f1() FROM DUAL;
DROP FUNCTION f1;
DROP TABLE t1;
--echo #
--echo # End of MDEV-10840 sql_mode=ORACLE: RAISE statement for predefined exceptions
--echo #
--echo #
--echo # MDEV-10587 sql_mode=ORACLE: User defined exceptions
--echo #
--echo #
--echo # Checking that duplicate WHEN clause is not allowed
--echo #
DELIMITER $$;
--error ER_SP_DUP_HANDLER
CREATE FUNCTION f1() RETURN VARCHAR
AS
e EXCEPTION;
BEGIN
RETURN 'Got no exceptions';
EXCEPTION
WHEN e THEN RETURN 'Got exception e';
WHEN e THEN RETURN 'Got exception e';
END;
$$
DELIMITER ;$$
--echo #
--echo # Checking that raised user exceptions are further caught by name
--echo #
DELIMITER $$;
CREATE FUNCTION f1(c VARCHAR) RETURN VARCHAR
AS
e EXCEPTION;
f EXCEPTION;
BEGIN
IF c = 'e' THEN RAISE e; END IF;
IF c = 'f' THEN RAISE f; END IF;
RETURN 'Got no exceptions';
EXCEPTION
WHEN e THEN RETURN 'Got exception e';
END;
$$
DELIMITER ;$$
SELECT f1('');
SELECT f1('e');
--error ER_SIGNAL_EXCEPTION
SELECT f1('f');
DROP FUNCTION f1;
--echo #
--echo # Checking that raised user exceptions are further caught by OTHERS
--echo #
DELIMITER $$;
CREATE FUNCTION f1(c VARCHAR) RETURN VARCHAR
AS
e EXCEPTION;
f EXCEPTION;
BEGIN
IF c = 'e' THEN RAISE e; END IF;
IF c = 'f' THEN RAISE f; END IF;
RETURN 'Got no exceptions';
EXCEPTION
WHEN OTHERS THEN RETURN 'Got some exception';
END;
$$
DELIMITER ;$$
SELECT f1('');
SELECT f1('e');
SELECT f1('f');
DROP FUNCTION f1;
--echo #
--echo # Checking that 'WHEN e .. WHEN f' does not produce ER_SP_DUP_HANDLER
--echo #
DELIMITER $$;
CREATE FUNCTION f1(c VARCHAR) RETURN VARCHAR
AS
e EXCEPTION;
f EXCEPTION;
a VARCHAR(64):='';
BEGIN
BEGIN
IF c = 'e' THEN RAISE e; END IF;
IF c = 'f' THEN RAISE f; END IF;
EXCEPTION
WHEN e THEN BEGIN a:='Got EXCEPTION1/e; '; RAISE e; END;
WHEN f THEN BEGIN a:='Got EXCEPTION1/f; '; RAISE f; END;
END;
RETURN 'Got no exceptions';
EXCEPTION
WHEN OTHERS THEN RETURN a || 'Got EXCEPTION2/OTHERS;';
END;
$$
DELIMITER ;$$
SELECT f1('');
SELECT f1('e');
SELECT f1('f');
DROP FUNCTION f1;
--echo #
--echo # Checking that resignaled user exceptions are further caught by name
--echo #
DELIMITER $$;
CREATE FUNCTION f1(c VARCHAR) RETURN VARCHAR
AS
e EXCEPTION;
f EXCEPTION;
a VARCHAR(64):='';
BEGIN
BEGIN
IF c = 'e' THEN RAISE e; END IF;
IF c = 'f' THEN RAISE f; END IF;
EXCEPTION
WHEN e THEN BEGIN a:='Got EXCEPTION1/e; '; RAISE; END;
WHEN f THEN BEGIN a:='Got EXCEPTION1/f; '; RAISE; END;
END;
RETURN 'Got no exceptions';
EXCEPTION
WHEN e THEN RETURN a || 'Got EXCEPTION2/e;';
END;
$$
DELIMITER ;$$
SELECT f1('');
SELECT f1('e');
--error ER_SIGNAL_EXCEPTION
SELECT f1('f');
DROP FUNCTION f1;
--echo #
--echo # Checking that resignaled user exceptions are further caught by OTHERS
--echo #
DELIMITER $$;
CREATE FUNCTION f1(c VARCHAR) RETURN VARCHAR
AS
e EXCEPTION;
f EXCEPTION;
a VARCHAR(64):='';
BEGIN
BEGIN
IF c = 'e' THEN RAISE e; END IF;
IF c = 'f' THEN RAISE f; END IF;
EXCEPTION
WHEN e THEN BEGIN a:='Got EXCEPTION1/e; '; RAISE; END;
WHEN f THEN BEGIN a:='Got EXCEPTION1/f; '; RAISE; END;
END;
RETURN 'Got no exceptions';
EXCEPTION
WHEN OTHERS THEN RETURN a || 'Got EXCEPTION2/OTHERS;';
END;
$$
DELIMITER ;$$
SELECT f1('');
SELECT f1('e');
SELECT f1('f');
DROP FUNCTION f1;
--echo #
--echo # End of MDEV-10587 sql_mode=ORACLE: User defined exceptions
--echo #
--echo #
--echo # MDEV-12088 sql_mode=ORACLE: Do not require BEGIN..END in multi-statement exception handlers in THEN clause
--echo #
CREATE TABLE t1 (a INT PRIMARY KEY);
INSERT INTO t1 VALUES (10),(20),(30);
DELIMITER $$;
CREATE PROCEDURE p1(a INT) AS
BEGIN
INSERT INTO t1 (a) VALUES (a);
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN
a:= a+1;
INSERT INTO t1 VALUES (a);
WHEN OTHERS THEN
NULL;
NULL;
END;
$$
DELIMITER ;$$
CALL p1(30);
SELECT * FROM t1;
DROP PROCEDURE p1;
DROP TABLE t1;

View File

@ -0,0 +1,9 @@
#
# Testing CASE and its abbreviations
#
SET sql_mode=ORACLE;
SELECT NVL(NULL, 'a'), NVL('a', 'b');
SELECT NVL2(NULL, 'a', 'b'), NVL2('a', 'b', 'c');

View File

@ -0,0 +1,116 @@
#
# Testing CONCAT with null values
#
SET sql_mode=ORACLE;
EXPLAIN EXTENDED SELECT 'a'||'b'||'c';
EXPLAIN EXTENDED SELECT CONCAT('a'||'b'||'c');
SELECT '' || '';
SELECT '' || 'b';
SELECT '' || NULL;
SELECT 'a' || '';
SELECT 'a' || 'b';
SELECT 'a' || NULL;
SELECT NULL || '';
SELECT NULL || 'b';
SELECT NULL || NULL;
SELECT '' || '' || '';
SELECT '' || '' || 'c';
SELECT '' || '' || NULL;
SELECT '' || 'b' || '';
SELECT '' || 'b' || 'c';
SELECT '' || 'b' || NULL;
SELECT '' || NULL || '';
SELECT '' || NULL || 'c';
SELECT '' || NULL || NULL;
SELECT 'a' || '' || '';
SELECT 'a' || '' || 'c';
SELECT 'a' || '' || NULL;
SELECT 'a' || 'b' || '';
SELECT 'a' || 'b' || 'c';
SELECT 'a' || 'b' || NULL;
SELECT 'a' || NULL || '';
SELECT 'a' || NULL || 'c';
SELECT 'a' || NULL || NULL;
SELECT NULL || '' || '';
SELECT NULL || '' || 'c';
SELECT NULL || '' || NULL;
SELECT NULL || 'b' || '';
SELECT NULL || 'b' || 'c';
SELECT NULL || 'b' || NULL;
SELECT NULL || NULL || '';
SELECT NULL || NULL || 'c';
SELECT NULL || NULL || NULL;
CREATE TABLE t1 (a VARCHAR(10), b VARCHAR(10), c VARCHAR(10));
INSERT INTO t1 VALUES ('', '', '');
INSERT INTO t1 VALUES ('', '', 'c');
INSERT INTO t1 VALUES ('', '', NULL);
INSERT INTO t1 VALUES ('', 'b', '');
INSERT INTO t1 VALUES ('', 'b', 'c');
INSERT INTO t1 VALUES ('', 'b', NULL);
INSERT INTO t1 VALUES ('', NULL, '');
INSERT INTO t1 VALUES ('', NULL, 'c');
INSERT INTO t1 VALUES ('', NULL, NULL);
INSERT INTO t1 VALUES ('a', '', '');
INSERT INTO t1 VALUES ('a', '', 'c');
INSERT INTO t1 VALUES ('a', '', NULL);
INSERT INTO t1 VALUES ('a', 'b', '');
INSERT INTO t1 VALUES ('a', 'b', 'c');
INSERT INTO t1 VALUES ('a', 'b', NULL);
INSERT INTO t1 VALUES ('a', NULL, '');
INSERT INTO t1 VALUES ('a', NULL, 'c');
INSERT INTO t1 VALUES ('a', NULL, NULL);
INSERT INTO t1 VALUES (NULL, '', '');
INSERT INTO t1 VALUES (NULL, '', 'c');
INSERT INTO t1 VALUES (NULL, '', NULL);
INSERT INTO t1 VALUES (NULL, 'b', '');
INSERT INTO t1 VALUES (NULL, 'b', 'c');
INSERT INTO t1 VALUES (NULL, 'b', NULL);
INSERT INTO t1 VALUES (NULL, NULL, '');
INSERT INTO t1 VALUES (NULL, NULL, 'c');
INSERT INTO t1 VALUES (NULL, NULL, NULL);
SELECT LENGTH(a||b||c), a||b||c FROM t1 ORDER BY a,b,c;
SELECT LENGTH(CONCAT(a||b||c)), CONCAT(a||b||c) FROM t1 ORDER BY a,b,c;
DROP TABLE t1;
--echo #
--echo # MDEV-12478 CONCAT function inside view casts values incorrectly with Oracle sql_mode
--echo #
SET sql_mode=ORACLE;
CREATE VIEW v1 AS SELECT 'foo'||NULL||'bar' AS test;
SHOW CREATE VIEW v1;
SELECT * FROM v1;
SET sql_mode=DEFAULT;
SHOW CREATE VIEW v1;
SELECT * FROM v1;
DROP VIEW v1;
SET sql_mode=DEFAULT;
CREATE VIEW v1 AS SELECT CONCAT('foo',NULL,'bar') AS test;
SHOW CREATE VIEW v1;
SELECT * FROM v1;
SET sql_mode=ORACLE;
SHOW CREATE VIEW v1;
SELECT * FROM v1;
DROP VIEW v1;
SET sql_mode=DEFAULT;
CREATE VIEW v1 AS SELECT '0'||'1' AS test;
SHOW CREATE VIEW v1;
SELECT * FROM v1;
SET sql_mode=ORACLE;
SHOW CREATE VIEW v1;
SELECT * FROM v1;
DROP VIEW v1;

View File

@ -0,0 +1,21 @@
SET sql_mode=ORACLE;
--error ER_PARSE_ERROR
SELECT DECODE(10);
--error ER_PARSE_ERROR
SELECT DECODE(10,10);
SELECT DECODE(10,10,'x10');
SELECT DECODE(11,10,'x10');
SELECT DECODE(10,10,'x10','def');
SELECT DECODE(11,10,'x10','def');
SELECT DECODE(10,10,'x10',11,'x11','def');
SELECT DECODE(11,10,'x10',11,'x11','def');
SELECT DECODE(12,10,'x10',11,'x11','def');
EXPLAIN EXTENDED SELECT DECODE(12,10,'x10',11,'x11','def');
CREATE TABLE decode (decode int);
DROP TABLE decode;

View File

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

View File

@ -0,0 +1,346 @@
SET sql_mode=ORACLE;
--echo #
--echo # MDEV-10578 sql_mode=ORACLE: SP control functions SQLCODE, SQLERRM
--echo #
--echo #
--echo # Using SQLCODE and SQLERRM outside of an SP
--echo #
--error ER_BAD_FIELD_ERROR
SELECT SQLCODE;
--error ER_BAD_FIELD_ERROR
SELECT SQLERRM;
CREATE TABLE t1 (SQLCODE INT, SQLERRM VARCHAR(10));
INSERT INTO t1 VALUES (10, 'test');
SELECT SQLCODE, SQLERRM FROM t1;
DROP TABLE t1;
--echo #
--echo # Normal SQLCODE and SQLERRM usage
--echo #
DELIMITER $$;
CREATE PROCEDURE p1(stmt VARCHAR)
AS
BEGIN
EXECUTE IMMEDIATE stmt;
SELECT 'Error1: ' || SQLCODE || ' ' || SQLERRM;
EXCEPTION
WHEN OTHERS THEN
SELECT 'Error2: ' || SQLCODE || ' ' || SQLERRM;
END;
$$
DELIMITER ;$$
CALL p1('SELECT 1');
CALL p1('xxx');
CALL p1('SELECT 1');
DROP PROCEDURE p1;
--echo #
--echo # SQLCODE and SQLERRM hidden by local variables
--echo #
DELIMITER $$;
CREATE PROCEDURE p1()
AS
sqlcode INT:= 10;
sqlerrm VARCHAR(64) := 'test';
BEGIN
SELECT 'Error: ' || SQLCODE || ' ' || SQLERRM;
END;
$$
DELIMITER ;$$
CALL p1;
DROP PROCEDURE p1;
DELIMITER $$;
CREATE PROCEDURE p1()
AS
sqlcode INT;
sqlerrm VARCHAR(64);
BEGIN
SQLCODE:= 10;
sqlerrm:= 'test';
SELECT 'Error: ' || SQLCODE || ' ' || SQLERRM;
END;
$$
DELIMITER ;$$
CALL p1;
DROP PROCEDURE p1;
--echo #
--echo # SQLCODE and SQLERRM hidden by parameters
--echo #
DELIMITER $$;
CREATE PROCEDURE p1(sqlcode INT, sqlerrm VARCHAR)
AS
BEGIN
SELECT 'Error: ' || SQLCODE || ' ' || SQLERRM;
END;
$$
DELIMITER ;$$
CALL p1(10, 'test');
DROP PROCEDURE p1;
--echo #
--echo # SQLCODE and SQLERRM in CREATE..SELECT
--echo #
DELIMITER $$;
CREATE PROCEDURE p1
AS
BEGIN
CREATE TABLE t1 AS SELECT SQLCODE, SQLERRM;
END;
$$
DELIMITER ;$$
CALL p1;
SHOW CREATE TABLE t1;
DROP TABLE t1;
DROP PROCEDURE p1;
--echo #
--echo # SQLCODE and SQLERRM in EXPLAIN EXTENDED SELECT
--echo #
DELIMITER $$;
CREATE PROCEDURE p1
AS
BEGIN
EXPLAIN EXTENDED SELECT SQLCode, SQLErrm;
END;
$$
DELIMITER ;$$
CALL p1;
DROP PROCEDURE p1;
--echo #
--echo # Warning-alike errors in stored functions
--echo #
CREATE TABLE t1 (a INT);
DELIMITER $$;
CREATE FUNCTION f1 RETURN VARCHAR
AS
a INT;
BEGIN
SELECT a INTO a FROM t1;
RETURN 'No exception ' || SQLCODE || ' ' || SQLERRM;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RETURN 'Exception ' || SQLCODE || ' ' || SQLERRM;
END;
$$
DELIMITER ;$$
SELECT f1() FROM DUAL;
DROP FUNCTION f1;
DROP TABLE t1;
CREATE TABLE t1 (a INT);
DELIMITER $$;
CREATE FUNCTION f1 RETURN VARCHAR
AS
a INT;
BEGIN
SELECT a INTO a FROM t1;
RETURN 'No exception ' || SQLCODE || ' ' || SQLERRM;
EXCEPTION
WHEN OTHERS THEN
RETURN 'Exception ' || SQLCODE || ' ' || SQLERRM;
END;
$$
DELIMITER ;$$
SELECT f1() FROM DUAL;
DROP FUNCTION f1;
DROP TABLE t1;
--echo #
--echo # Warning-alike errors in stored procedures
--echo #
CREATE TABLE t1 (a INT);
DELIMITER $$;
CREATE PROCEDURE p1(res OUT VARCHAR)
AS
a INT;
BEGIN
SELECT a INTO a FROM t1;
res:= 'No exception ' || SQLCODE || ' ' || SQLERRM;
EXCEPTION
WHEN NO_DATA_FOUND THEN
res:= 'Exception ' || SQLCODE || ' ' || SQLERRM;
END;
$$
DELIMITER ;$$
CALL p1(@a);
SELECT @a;
DROP PROCEDURE p1;
DROP TABLE t1;
CREATE TABLE t1 (a INT);
DELIMITER $$;
CREATE PROCEDURE p1(res OUT VARCHAR)
AS
a INT;
BEGIN
SELECT a INTO a FROM t1;
res:= 'No exception ' || SQLCODE || ' ' || SQLERRM;
EXCEPTION
WHEN OTHERS THEN
res:= 'Exception ' || SQLCODE || ' ' || SQLERRM;
END;
$$
DELIMITER ;$$
CALL p1(@a);
SELECT @a;
DROP PROCEDURE p1;
DROP TABLE t1;
--echo #
--echo # SQLCODE and SQLERRM are cleared on RETURN
--echo #
CREATE TABLE t1 (a INT);
DELIMITER $$;
CREATE FUNCTION f1 RETURN VARCHAR
AS
a INT:=10;
BEGIN
SELECT a INTO a FROM t1;
RETURN 'Value=' || a;
EXCEPTION
WHEN NO_DATA_FOUND THEN RETURN 'Exception|' || SQLCODE || ' ' || SQLERRM;
END;
$$
CREATE FUNCTION f2 RETURN VARCHAR
AS
a VARCHAR(128);
BEGIN
RETURN f1() || '|' || SQLCODE || ' ' || SQLERRM;
END;
$$
DELIMITER ;$$
SELECT f1() FROM DUAL;
SELECT f2() FROM DUAL;
DROP TABLE t1;
DROP FUNCTION f2;
DROP FUNCTION f1;
CREATE TABLE t1 (a INT);
DELIMITER $$;
CREATE FUNCTION f1 RETURN VARCHAR
AS
a INT:=10;
BEGIN
SELECT a INTO a FROM t1;
RETURN 'Value=' || a;
EXCEPTION
WHEN OTHERS THEN RETURN 'Exception|' || SQLCODE || ' ' || SQLERRM;
END;
$$
CREATE FUNCTION f2 RETURN VARCHAR
AS
a VARCHAR(128);
BEGIN
RETURN f1() || '|' || SQLCODE || ' ' || SQLERRM;
END;
$$
DELIMITER ;$$
SELECT f1() FROM DUAL;
SELECT f2() FROM DUAL;
DROP TABLE t1;
DROP FUNCTION f2;
DROP FUNCTION f1;
--echo #
--echo # SQLCODE and SQLERRM are cleared on a return from a PROCEDURE
--echo #
CREATE TABLE t1 (a INT);
DELIMITER $$;
CREATE PROCEDURE p1(res OUT VARCHAR)
AS
a INT:=10;
BEGIN
SELECT a INTO a FROM t1;
res:='Value=' || a;
EXCEPTION
WHEN NO_DATA_FOUND THEN res:='Exception|' || SQLCODE || ' ' || SQLERRM;
END;
$$
CREATE FUNCTION f2 RETURN VARCHAR
AS
res VARCHAR(128);
BEGIN
CALL p1(res);
RETURN res || '|' || SQLCODE || ' ' || SQLERRM;
END;
$$
DELIMITER ;$$
SELECT f2() FROM DUAL;
DROP FUNCTION f2;
DROP PROCEDURE p1;
DROP TABLE t1;
CREATE TABLE t1 (a INT);
DELIMITER $$;
CREATE PROCEDURE p1(res OUT VARCHAR)
AS
a INT:=10;
BEGIN
SELECT a INTO a FROM t1;
res:='Value=' || a;
EXCEPTION
WHEN OTHERS THEN res:='Exception|' || SQLCODE || ' ' || SQLERRM;
END;
$$
CREATE FUNCTION f2 RETURN VARCHAR
AS
res VARCHAR(128);
BEGIN
CALL p1(res);
RETURN res || '|' || SQLCODE || ' ' || SQLERRM;
END;
$$
DELIMITER ;$$
SELECT f2() FROM DUAL;
DROP FUNCTION f2;
DROP PROCEDURE p1;
DROP TABLE t1;
--echo #
--echo # End of MDEV-10578 sql_mode=ORACLE: SP control functions SQLCODE, SQLERRM
--echo #
--echo #
--echo # MDEV-12854 Synchronize CREATE..SELECT data type and result set metadata data type for INT functions
--echo #
--enable_metadata
--disable_ps_protocol
DELIMITER $$;
BEGIN
SELECT SQLCODE;
END
$$
DELIMITER ;$$
--enable_ps_protocol
--disable_metadata

View File

@ -0,0 +1,10 @@
SET sql_mode=ORACLE;
--echo #
--echo # MDEV-12086 sql_mode=ORACLE: allow SELECT UNIQUE as a synonym for SELECT DISTINCT
--echo #
CREATE TABLE t1 (a INT);
INSERT INTO t1 VALUES (10),(20),(20),(30),(30),(30);
SELECT UNIQUE a FROM t1;
DROP TABLE t1;

View File

@ -0,0 +1,266 @@
SET sql_mode=ORACLE;
--echo #
--echo # MDEV-10801 sql_mode: dynamic SQL placeholders
--echo #
SET @a=10, @b=20;
PREPARE stmt FROM 'SELECT ?,?';
EXECUTE stmt USING @a, @b;
PREPARE stmt FROM 'SELECT :a,:b';
EXECUTE stmt USING @a, @b;
PREPARE stmt FROM 'SELECT :aaa,:bbb';
EXECUTE stmt USING @a, @b;
#qc_mysqlembedded: PREPARE stmt FROM 'SELECT :"a",:"b"';
EXECUTE stmt USING @a, @b;
#qc_mysqlembedded: PREPARE stmt FROM 'SELECT :"aaa",:"bbb"';
EXECUTE stmt USING @a, @b;
PREPARE stmt FROM 'SELECT :1,:2';
EXECUTE stmt USING @a, @b;
PREPARE stmt FROM 'SELECT :222,:111';
EXECUTE stmt USING @a, @b;
PREPARE stmt FROM 'SELECT :0,:65535';
EXECUTE stmt USING @a, @b;
PREPARE stmt FROM 'SELECT :65535,:0';
EXECUTE stmt USING @a, @b;
--echo #
--echo # MDEV-10709 Expressions as parameters to Dynamic SQL
--echo #
--echo #
--echo # Testing disallowed expressions in USING
--echo #
PREPARE stmt FROM 'SELECT :1 FROM DUAL';
--error ER_PARSE_ERROR
EXECUTE stmt USING (SELECT 1);
DEALLOCATE PREPARE stmt;
DELIMITER $$;
CREATE FUNCTION f1() RETURN VARCHAR
AS
BEGIN
RETURN 'test';
END;
$$
DELIMITER ;$$
PREPARE stmt FROM 'SELECT ? FROM DUAL';
--error ER_SUBQUERIES_NOT_SUPPORTED
EXECUTE stmt USING f1();
DEALLOCATE PREPARE stmt;
DROP FUNCTION f1;
--echo #
--echo # Using a user variable as a EXECUTE..USING out parameter
--echo #
DELIMITER /;
CREATE PROCEDURE p1(a OUT INT)
AS
BEGIN
a:= 10;
END;
/
DELIMITER ;/
SET @a=1;
CALL p1(@a);
SELECT @a;
SET @a=2;
PREPARE stmt FROM 'CALL p1(?)';
EXECUTE stmt USING @a;
SELECT @a;
DROP PROCEDURE p1;
--echo #
--echo # Using an SP variable as a EXECUTE..USING out parameter
--echo #
DELIMITER /;
CREATE PROCEDURE p1 (a OUT INT)
AS
BEGIN
a:=10;
END;
/
CREATE PROCEDURE p2 (a OUT INT)
AS
BEGIN
PREPARE stmt FROM 'CALL p1(?)';
EXECUTE stmt USING a;
END;
/
DELIMITER ;/
SET @a= 1;
CALL p2(@a);
SELECT @a;
DROP PROCEDURE p2;
DROP PROCEDURE p1;
--echo #
--echo # Using a trigger field as a EXECUTE..USING out parameter
--echo #
DELIMITER /;
CREATE PROCEDURE p1 (a OUT INT)
AS
BEGIN
a:= 10;
END;
/
DELIMITER ;/
CREATE TABLE t1 (a INT);
CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW CALL p1(:NEW.a);
INSERT INTO t1 VALUES (1);
SELECT * FROM t1;
DROP TABLE t1;
DROP PROCEDURE p1;
--echo #
--echo # Testing re-prepare on a table metadata update between PREPARE and EXECUTE
--echo #
CREATE TABLE t1 (a INT);
DELIMITER /;
CREATE PROCEDURE p1(a IN INT)
AS
BEGIN
INSERT INTO t1 VALUES (a);
END;
/
DELIMITER ;/
PREPARE stmt FROM 'CALL p1(?)';
EXECUTE stmt USING 10;
SELECT * FROM t1;
CREATE TRIGGER tr1 BEFORE INSERT ON t1 FOR EACH ROW NEW.a:=NEW.a+1;
EXECUTE stmt USING 20;
SELECT * FROM t1;
DEALLOCATE PREPARE stmt;
DROP PROCEDURE p1;
DROP TABLE t1;
--echo #
--echo # End of MDEV-10709 Expressions as parameters to Dynamic SQL
--echo #
--echo #
--echo # MDEV-10585 EXECUTE IMMEDIATE statement
--echo #
--echo #
--echo # Testing disallowed expressions in USING
--echo #
--error ER_PARSE_ERROR
EXECUTE IMMEDIATE 'SELECT :1 FROM DUAL' USING (SELECT 1);
DELIMITER $$;
CREATE FUNCTION f1() RETURN VARCHAR
AS
BEGIN
RETURN 'test';
END;
$$
DELIMITER ;$$
--error ER_SUBQUERIES_NOT_SUPPORTED
EXECUTE IMMEDIATE 'SELECT ? FROM DUAL' USING f1();
DROP FUNCTION f1;
--echo #
--echo # Testing simple expressions
--echo #
EXECUTE IMMEDIATE 'SELECT :1 FROM DUAL' USING 10;
--echo #
--echo # MDEV-10866 Extend PREPARE and EXECUTE IMMEDIATE to understand expressions
--echo #
--echo #
--echo # Testing erroneous and diallowed prepare source
--echo #
--error ER_CANT_AGGREGATE_2COLLATIONS
EXECUTE IMMEDIATE _latin1'SELECT 1 AS c FROM ' || _latin2 'DUAL';
--error ER_CANT_AGGREGATE_2COLLATIONS
PREPARE stmt FROM _latin1'SELECT 1 AS c FROM ' || _latin2 'DUAL';
--error ER_PARSE_ERROR
EXECUTE IMMEDIATE (SELECT 'SELECT 1');
--error ER_PARSE_ERROR
PREPARE stmt FROM (SELECT 'SELECT 1');
--error ER_BAD_FIELD_ERROR
EXECUTE IMMEDIATE a;
--error ER_BAD_FIELD_ERROR
PREPARE stmt FROM a;
--error ER_PARSE_ERROR
EXECUTE IMMEDIATE NULL;
--error ER_PARSE_ERROR
PREPARE stmt FROM NULL;
--error ER_PARSE_ERROR
EXECUTE IMMEDIATE COALESCE(NULL);
--error ER_PARSE_ERROR
PREPARE stmt FROM COALESCE(NULL);
DELIMITER $$;
CREATE FUNCTION f1() RETURN VARCHAR
AS
BEGIN
RETURN 't1';
END;
$$
DELIMITER ;$$
--error ER_SUBQUERIES_NOT_SUPPORTED
EXECUTE IMMEDIATE f1();
--error ER_SUBQUERIES_NOT_SUPPORTED
PREPARE stmt FROM f1();
DROP FUNCTION f1;
--echo #
--echo # Testing user variables in prepare source
--echo #
SET @table_name='DUAL';
EXECUTE IMMEDIATE 'SELECT 1 AS a FROM ' || @table_name;
#qc: PREPARE stmt FROM 'SELECT 1 AS a FROM ' || @table_name;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
--echo #
--echo # Testing SP parameters and variables in prepare source
--echo #
DELIMITER $$;
CREATE PROCEDURE p1(table_name VARCHAR)
AS
BEGIN
EXECUTE IMMEDIATE 'SELECT 1 AS c FROM '|| table_name;
END;
$$
DELIMITER ;$$
CALL p1('DUAL');
DROP PROCEDURE p1;
DELIMITER $$;
CREATE PROCEDURE p1()
AS
table_name VARCHAR(64):='DUAL';
BEGIN
EXECUTE IMMEDIATE 'SELECT 1 AS c FROM ' || table_name;
END;
$$
DELIMITER ;$$
CALL p1();
DROP PROCEDURE p1;
--echo #
--echo # End of MDEV-10866 Extend PREPARE and EXECUTE IMMEDIATE to understand expressions
--echo #

View File

@ -0,0 +1,43 @@
--source include/have_binlog_format_row.inc
SET sql_mode=ORACLE;
CREATE SEQUENCE s1;
SHOW CREATE SEQUENCE s1;
SELECT s1.currval;
SELECT s1.nextval;
SELECT s1.nextval;
SELECT s1.nextval;
EXPLAIN EXTENDED SELECT s1.nextval;
SELECT nextval(s1);
EXPLAIN EXTENDED SELECT s1.currval;
SELECT lastval(s1);
DROP SEQUENCE s1;
CREATE SEQUENCE s1;
CREATE VIEW v1 AS SELECT s1.nextval AS a;
SELECT VIEW_DEFINITION FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_NAME='v1';
SELECT * FROM v1;
SHOW CREATE VIEW v1;
DROP VIEW v1;
DROP SEQUENCE s1;
CREATE SEQUENCE s1;
CREATE VIEW v1 AS SELECT s1.currval AS a;
SELECT VIEW_DEFINITION FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_NAME='v1';
SELECT * FROM v1;
SHOW CREATE VIEW v1;
DROP VIEW v1;
DROP SEQUENCE s1;
--echo #
--echo # MDEV-12533 sql_mode=ORACLE: Add support for database qualified sequence names in NEXTVAL and CURRVAL
--echo #
CREATE SEQUENCE s1;
SELECT test.s1.nextval;
SELECT test.s1.currval;
SELECT .s1.nextval;
SELECT .s1.currval;
DROP SEQUENCE s1;

View File

@ -0,0 +1,244 @@
--source include/have_innodb.inc
SET sql_mode=ORACLE;
--echo #
--echo # MDEV-10655 Anonymous blocks
--echo #
--echo # Testing BEGIN NOT ATOMIC with no declarations
DELIMITER /;
BEGIN NOT ATOMIC
SELECT 1 AS a;
END
/
DELIMITER ;/
--echo # Testing BEGIN NOT ATOMIC with declarations
--echo # DECLARE starts a new block and thus must be followed by BEGIN .. END
DELIMITER /;
BEGIN NOT ATOMIC
DECLARE
i INT DEFAULT 5;
x INT DEFAULT 10;
BEGIN
<<label>>
WHILE i > 3 LOOP
i:= i - 1;
SELECT i;
END LOOP label;
END;
END
/
DELIMITER ;/
--echo # Anonymous blocks with no declarations and no exceptions
DELIMITER $$;
BEGIN
SELECT 1 AS a;
END
$$
DELIMITER ;$$
SET AUTOCOMMIT=OFF;
CREATE TABLE t1 (a INT) ENGINE=InnoDB;
INSERT INTO t1 VALUES (10);
DELIMITER $$;
BEGIN
INSERT INTO t1 VALUES(20);
INSERT INTO t1 VALUES(30);
ROLLBACK;
END;
$$
DELIMITER ;$$
SELECT * FROM t1;
DROP TABLE t1;
SET AUTOCOMMIT=DEFAULT;
SET AUTOCOMMIT=OFF;
CREATE TABLE t1 (a INT) ENGINE=InnoDB;
INSERT INTO t1 VALUES (10);
DELIMITER $$;
BEGIN
INSERT INTO t1 VALUES(20);
INSERT INTO t1 VALUES(30);
END;
$$
DELIMITER ;$$
ROLLBACK;
SELECT * FROM t1;
DROP TABLE t1;
SET AUTOCOMMIT=DEFAULT;
SET AUTOCOMMIT=OFF;
CREATE TABLE t1 (a INT) ENGINE=InnoDB;
INSERT INTO t1 VALUES (10);
DELIMITER $$;
BEGIN
INSERT INTO t1 VALUES(20);
INSERT INTO t1 VALUES(30);
COMMIT;
END;
$$
DELIMITER ;$$
SELECT * FROM t1;
DROP TABLE t1;
SET AUTOCOMMIT=DEFAULT;
SET AUTOCOMMIT=OFF;
CREATE TABLE t1 (a INT) ENGINE=InnoDB;
INSERT INTO t1 VALUES (10);
DELIMITER $$;
BEGIN
INSERT INTO t1 VALUES(20);
INSERT INTO t1 VALUES(30);
END;
$$
DELIMITER ;$$
COMMIT;
SELECT * FROM t1;
DROP TABLE t1;
SET AUTOCOMMIT=DEFAULT;
SET AUTOCOMMIT=OFF;
CREATE TABLE t1 (a INT NOT NULL PRIMARY KEY) ENGINE=InnoDB;
INSERT INTO t1 VALUES (10);
DELIMITER $$;
--error ER_DUP_ENTRY
BEGIN
INSERT INTO t1 VALUES(20);
INSERT INTO t1 VALUES(20);
END;
$$
DELIMITER ;$$
COMMIT;
SELECT * FROM t1;
DROP TABLE t1;
SET AUTOCOMMIT=DEFAULT;
--echo # Anonymous blocks with no declarations, with exceptions
SET AUTOCOMMIT=OFF;
CREATE TABLE t1 (a INT NOT NULL PRIMARY KEY) ENGINE=InnoDB;
INSERT INTO t1 VALUES (10);
DELIMITER $$;
BEGIN
INSERT INTO t1 VALUES(20);
INSERT INTO t1 VALUES(20);
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN NULL;
END;
$$
DELIMITER ;$$
COMMIT;
SELECT * FROM t1;
DROP TABLE t1;
SET AUTOCOMMIT=DEFAULT;
--echo # Anonymous blocks with declarations, with no exceptions
SET AUTOCOMMIT=OFF;
CREATE TABLE t1 (a INT) ENGINE=InnoDB;
INSERT INTO t1 VALUES (10);
DELIMITER $$;
DECLARE
a20 INT:=20;
a30 INT:=30;
BEGIN
INSERT INTO t1 VALUES(a20);
INSERT INTO t1 VALUES(a30);
ROLLBACK;
END;
$$
DELIMITER ;$$
SELECT * FROM t1;
DROP TABLE t1;
SET AUTOCOMMIT=DEFAULT;
SET AUTOCOMMIT=OFF;
CREATE TABLE t1 (a INT) ENGINE=InnoDB;
INSERT INTO t1 VALUES (10);
DELIMITER $$;
DECLARE
a20 INT:=20;
a30 INT:=30;
BEGIN
INSERT INTO t1 VALUES(a20);
INSERT INTO t1 VALUES(a30);
END;
$$
DELIMITER ;$$
ROLLBACK;
SELECT * FROM t1;
DROP TABLE t1;
SET AUTOCOMMIT=DEFAULT;
SET AUTOCOMMIT=OFF;
CREATE TABLE t1 (a INT) ENGINE=InnoDB;
INSERT INTO t1 VALUES (10);
DELIMITER $$;
DECLARE
a20 INT:=20;
a30 INT:=30;
BEGIN
INSERT INTO t1 VALUES(a20);
INSERT INTO t1 VALUES(a30);
COMMIT;
END;
$$
DELIMITER ;$$
SELECT * FROM t1;
DROP TABLE t1;
SET AUTOCOMMIT=DEFAULT;
SET AUTOCOMMIT=OFF;
CREATE TABLE t1 (a INT) ENGINE=InnoDB;
INSERT INTO t1 VALUES (10);
DELIMITER $$;
DECLARE
a20 INT:=20;
a30 INT:=30;
BEGIN
INSERT INTO t1 VALUES(a20);
INSERT INTO t1 VALUES(a30);
END;
$$
DELIMITER ;$$
COMMIT;
SELECT * FROM t1;
DROP TABLE t1;
SET AUTOCOMMIT=DEFAULT;
--echo # Anonymous blocks with declarations, with exceptions
SET AUTOCOMMIT=OFF;
CREATE TABLE t1 (a INT NOT NULL PRIMARY KEY) ENGINE=InnoDB;
INSERT INTO t1 VALUES (10);
DELIMITER $$;
DECLARE
a20 INT:=20;
BEGIN
INSERT INTO t1 VALUES(a20);
INSERT INTO t1 VALUES(a20);
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN NULL;
END;
$$
DELIMITER ;$$
COMMIT;
SELECT * FROM t1;
DROP TABLE t1;
SET AUTOCOMMIT=DEFAULT;

File diff suppressed because it is too large Load Diff

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -76,7 +76,7 @@ private:
private:
std::istream& m_in; /*< The stream we are using. */
size_t m_line; /*< The current line. */
char m_delimiter; /*< The current delimiter. */
std::string m_delimiter; /*< The current delimiter. */
};
};

View File

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

View File

@ -9,6 +9,7 @@ add_library(maxscale-common SHARED
config.cc
config_runtime.cc
dcb.cc
encryption.cc
externcmd.cc
filter.cc
hashtable.cc

View File

@ -86,14 +86,16 @@ bool Backend::execute_session_command()
SessionCommandList::iterator iter = m_session_commands.begin();
SessionCommand& sescmd = *(*iter);
GWBUF *buffer = sescmd.copy_buffer().release();
GWBUF *buffer = sescmd.deep_copy_buffer();
bool rval = false;
switch (sescmd.get_command())
{
case MYSQL_COM_QUIT:
case MYSQL_COM_STMT_CLOSE:
/** These commands do not generate responses */
rval = write(buffer, NO_RESPONSE);
complete_session_command();
break;
case MYSQL_COM_CHANGE_USER:

View File

@ -352,6 +352,28 @@ GWBUF* gwbuf_clone(GWBUF* buf)
return rval;
}
GWBUF* gwbuf_deep_clone(const GWBUF* buf)
{
GWBUF* rval = NULL;
if (buf)
{
size_t buflen = gwbuf_length(buf);
rval = gwbuf_alloc(buflen);
if (rval && gwbuf_copy_data(buf, 0, buflen, GWBUF_DATA(rval)) == buflen)
{
rval->gwbuf_type = buf->gwbuf_type;
}
else
{
gwbuf_free(rval);
rval = NULL;
}
}
return rval;
}
static GWBUF *gwbuf_clone_portion(GWBUF *buf,
size_t start_offset,

View File

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

View File

@ -2600,6 +2600,15 @@ int dcb_listen(DCB *listener, const char *config, const char *protocol_name)
else if (port > 0)
{
listener_socket = dcb_listen_create_socket_inet(host, port);
if (listener_socket == -1 && strcmp(host, "::") == 0)
{
/** Attempt to bind to the IPv4 if the default IPv6 one is used */
MXS_WARNING("Failed to bind on default IPv6 host '::', attempting "
"to bind on IPv4 version '0.0.0.0'");
strcpy(host, "0.0.0.0");
listener_socket = dcb_listen_create_socket_inet(host, port);
}
}
else
{

55
server/core/encryption.cc Normal file
View File

@ -0,0 +1,55 @@
/*
* 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: 2020-01-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/alloc.h>
#include <maxscale/encryption.h>
EVP_CIPHER_CTX* mxs_evp_cipher_ctx_alloc()
{
#ifdef OPENSSL_1_1
return EVP_CIPHER_CTX_new();
#else
EVP_CIPHER_CTX* rval = (EVP_CIPHER_CTX*)MXS_MALLOC(sizeof(*rval));
EVP_CIPHER_CTX_init(rval);
return rval;
#endif
}
void mxs_evp_cipher_ctx_free(EVP_CIPHER_CTX* ctx)
{
#ifdef OPENSSL_1_1
EVP_CIPHER_CTX_free(ctx);
#else
MXS_FREE(ctx);
#endif
}
uint8_t* mxs_evp_cipher_ctx_buf(EVP_CIPHER_CTX* ctx)
{
#ifdef OPENSSL_1_1
return (uint8_t*)EVP_CIPHER_CTX_buf_noconst(ctx);
#else
return (uint8_t*)ctx->buf;
#endif
}
uint8_t* mxs_evp_cipher_ctx_oiv(EVP_CIPHER_CTX* ctx)
{
#ifdef OPENSSL_1_1
return (uint8_t*)EVP_CIPHER_CTX_original_iv(ctx);
#else
return (uint8_t*)ctx->oiv;
#endif
}

View File

@ -203,6 +203,7 @@ const DEBUG_ARGUMENT debug_arguments[] =
{NULL, NULL, NULL}
};
#ifndef OPENSSL_1_1
/** SSL multi-threading functions and structures */
static SPINLOCK* ssl_locks;
@ -284,6 +285,7 @@ static void maxscale_ssl_id(CRYPTO_THREADID* id)
CRYPTO_THREADID_set_numeric(id, pthread_self());
}
#endif
#endif
/**
* Handler for SIGHUP signal. Reload the configuration for the
@ -1734,6 +1736,7 @@ int main(int argc, char **argv)
SSL_load_error_strings();
OPENSSL_add_all_algorithms_noconf();
#ifndef OPENSSL_1_1
numlocks = CRYPTO_num_locks();
if ((ssl_locks = (SPINLOCK*)MXS_MALLOC(sizeof(SPINLOCK) * (numlocks + 1))) == NULL)
{
@ -1753,6 +1756,7 @@ int main(int argc, char **argv)
CRYPTO_THREADID_set_callback(maxscale_ssl_id);
#else
CRYPTO_set_id_callback(pthread_self);
#endif
#endif
/**
@ -1889,7 +1893,7 @@ int main(int argc, char **argv)
cnf = config_get_global_options();
ss_dassert(cnf);
if (!qc_setup(cnf->qc_name, cnf->qc_args))
if (!qc_setup(cnf->qc_name, cnf->qc_sql_mode, cnf->qc_args))
{
const char* logerr = "Failed to initialise query classifier library.";
print_log_n_stderr(true, true, logerr, logerr, eno);

View File

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

View File

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

View File

@ -45,7 +45,7 @@ static QUERY_CLASSIFIER* classifier;
static qc_trx_parse_using_t qc_trx_parse_using = QC_TRX_PARSE_USING_PARSER;
bool qc_setup(const char* plugin_name, const char* plugin_args)
bool qc_setup(const char* plugin_name, qc_sql_mode_t sql_mode, const char* plugin_args)
{
QC_TRACE();
ss_dassert(!classifier);
@ -61,7 +61,7 @@ bool qc_setup(const char* plugin_name, const char* plugin_args)
if (classifier)
{
rv = classifier->qc_setup(plugin_args);
rv = classifier->qc_setup(sql_mode, plugin_args);
if (rv != QC_RESULT_OK)
{
@ -494,42 +494,48 @@ const char* qc_op_to_string(qc_query_op_t op)
case QUERY_OP_UNDEFINED:
return "QUERY_OP_UNDEFINED";
case QUERY_OP_SELECT:
return "QUERY_OP_SELECT";
case QUERY_OP_UPDATE:
return "QUERY_OP_UPDATE";
case QUERY_OP_INSERT:
return "QUERY_OP_INSERT";
case QUERY_OP_DELETE:
return "QUERY_OP_DELETE";
case QUERY_OP_TRUNCATE:
return "QUERY_OP_TRUNCATE";
case QUERY_OP_ALTER:
return "QUERY_OP_ALTER";
case QUERY_OP_CREATE:
return "QUERY_OP_CREATE";
case QUERY_OP_DROP:
return "QUERY_OP_DROP";
case QUERY_OP_CHANGE_DB:
return "QUERY_OP_CHANGE_DB";
case QUERY_OP_LOAD:
return "QUERY_OP_LOAD";
case QUERY_OP_CREATE:
return "QUERY_OP_CREATE";
case QUERY_OP_DELETE:
return "QUERY_OP_DELETE";
case QUERY_OP_DROP:
return "QUERY_OP_DROP";
case QUERY_OP_EXPLAIN:
return "QUERY_OP_EXPLAIN";
case QUERY_OP_GRANT:
return "QUERY_OP_GRANT";
case QUERY_OP_INSERT:
return "QUERY_OP_INSERT";
case QUERY_OP_LOAD:
return "QUERY_OP_LOAD";
case QUERY_OP_REVOKE:
return "QUERY_OP_REVOKE";
case QUERY_OP_SELECT:
return "QUERY_OP_SELECT";
case QUERY_OP_SHOW:
return "QUERY_OP_SHOW";
case QUERY_OP_TRUNCATE:
return "QUERY_OP_TRUNCATE";
case QUERY_OP_UPDATE:
return "QUERY_OP_UPDATE";
default:
return "UNKNOWN_QUERY_OP";
}
@ -921,3 +927,25 @@ uint64_t qc_get_server_version()
return version;
}
qc_sql_mode_t qc_get_sql_mode()
{
QC_TRACE();
ss_dassert(classifier);
qc_sql_mode_t sql_mode;
ss_debug(int32_t rv =) classifier->qc_get_sql_mode(&sql_mode);
ss_dassert(rv == QC_RESULT_OK);
return sql_mode;
}
void qc_set_sql_mode(qc_sql_mode_t sql_mode)
{
QC_TRACE();
ss_dassert(classifier);
ss_debug(int32_t rv =) classifier->qc_set_sql_mode(sql_mode);
ss_dassert(rv == QC_RESULT_OK);
}

View File

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

View File

@ -38,9 +38,12 @@ uint64_t SessionCommand::get_position() const
return m_pos;
}
Buffer SessionCommand::copy_buffer() const
GWBUF* SessionCommand::deep_copy_buffer()
{
return m_buffer;
GWBUF* temp = m_buffer.release();
GWBUF* rval = gwbuf_deep_clone(temp);
m_buffer.reset(temp);
return rval;
}
SessionCommand::SessionCommand(GWBUF *buffer, uint64_t id):

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -80,6 +80,7 @@ typedef struct
bool warn_failover; /**< Log a warning when failover happens */
bool load_journal; /**< Whether journal file should be loaded */
time_t journal_max_age; /**< Maximum age of journal file */
bool allow_external_slaves; /**< Whether to allow usage of external slave servers */
MXS_MONITOR* monitor;
} MYSQL_MONITOR;

View File

@ -104,6 +104,7 @@ MXS_MODULE* MXS_CREATE_MODULE()
{"detect_standalone_master", MXS_MODULE_PARAM_BOOL, "false"},
{"failcount", MXS_MODULE_PARAM_COUNT, "5"},
{"allow_cluster_recovery", MXS_MODULE_PARAM_BOOL, "true"},
{"allow_external_slaves", MXS_MODULE_PARAM_BOOL, "true"},
{"journal_max_age", MXS_MODULE_PARAM_COUNT, DEFAULT_JOURNAL_MAX_AGE},
{
"script",
@ -264,6 +265,7 @@ startMonitor(MXS_MONITOR *monitor, const MXS_CONFIG_PARAMETER* params)
handle->script = config_copy_string(params, "script");
handle->events = config_get_enum(params, "events", mxs_monitor_event_enum_values);
handle->journal_max_age = config_get_integer(params, "journal_max_age");
handle->allow_external_slaves = config_get_bool(params, "allow_external_slaves");
if (journal_is_stale(monitor, handle->journal_max_age))
{
@ -641,7 +643,14 @@ static MXS_MONITOR_SERVERS *build_mysql51_replication_tree(MXS_MONITOR *mon)
(database->server->master_id <= 0 ||
database->server->master_id != handle->master->server->node_id))
{
monitor_set_pending_status(database, SERVER_SLAVE);
if (handle->allow_external_slaves)
{
monitor_set_pending_status(database, SERVER_SLAVE);
}
else
{
monitor_clear_pending_status(database, SERVER_SLAVE);
}
monitor_set_pending_status(database, SERVER_SLAVE_OF_EXTERNAL_MASTER);
}
database = database->next;
@ -1848,10 +1857,14 @@ static MXS_MONITOR_SERVERS *get_replication_tree(MXS_MONITOR *mon, int num_serve
{
if (current->master_id > 0)
{
/* this server is slave of another server not in MaxScale configuration
* we cannot use it as a real slave.
*/
monitor_set_pending_status(ptr, SERVER_SLAVE);
if (handle->allow_external_slaves)
{
monitor_set_pending_status(ptr, SERVER_SLAVE);
}
else
{
monitor_clear_pending_status(ptr, SERVER_SLAVE);
}
monitor_set_pending_status(ptr, SERVER_SLAVE_OF_EXTERNAL_MASTER);
}
}

View File

@ -629,26 +629,32 @@ static inline bool expecting_ps_response(MySQLProtocol *proto)
static inline bool complete_ps_response(GWBUF *buffer)
{
ss_dassert(GWBUF_IS_CONTIGUOUS(buffer));
uint16_t cols = gw_mysql_get_byte2(GWBUF_DATA(buffer) + MYSQL_PS_COLS_OFFSET);
uint16_t params = gw_mysql_get_byte2(GWBUF_DATA(buffer) + MYSQL_PS_PARAMS_OFFSET);
int expected_eof = 0;
MXS_PS_RESPONSE resp;
bool rval = false;
if (cols > 0)
if (mxs_mysql_extract_ps_response(buffer, &resp))
{
expected_eof++;
int expected_eof = 0;
if (resp.columns > 0)
{
expected_eof++;
}
if (resp.parameters > 0)
{
expected_eof++;
}
bool more;
int n_eof = modutil_count_signal_packets(buffer, 0, &more);
MXS_DEBUG("Expecting %u EOF, have %u", n_eof, expected_eof);
rval = n_eof == expected_eof;
}
if (params > 0)
{
expected_eof++;
}
bool more;
int n_eof = modutil_count_signal_packets(buffer, 0, &more);
MXS_DEBUG("Expecting %u EOF, have %u", n_eof, expected_eof);
return n_eof == expected_eof;
return rval;
}
static inline bool collecting_resultset(MySQLProtocol *proto, uint64_t capabilities)

View File

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

View File

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

View File

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

View File

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

View File

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

Some files were not shown because too many files have changed in this diff Show More