mirror of
https://github.com/MariaDB/server.git
synced 2025-07-29 05:21:33 +03:00
MDEV-20034 Add support for the pre-defined weak SYS_REFCURSOR
This patch adds support for SYS_REFCURSOR (a weakly typed cursor) for both sql_mode=ORACLE and sql_mode=DEFAULT. Works as a regular stored routine variable, parameter and return value: - can be passed as an IN parameter to stored functions and procedures - can be passed as an INOUT and OUT parameter to stored procedures - can be returned from a stored function Note, strongly typed REF CURSOR will be added separately. Note, to maintain dependencies easier, some parts of sql_class.h and item.h were moved to new header files: - select_results.h: class select_result_sink class select_result class select_result_interceptor - sp_cursor.h: class sp_cursor_statistics class sp_cursor - sp_rcontext_handler.h class Sp_rcontext_handler and its descendants The implementation consists of the following parts: - A new class sp_cursor_array deriving from Dynamic_array - A new class Statement_rcontext which contains data shared between sub-statements of a compound statement. It has a member m_statement_cursors of the sp_cursor_array data type, as well as open cursor counter. THD inherits from Statement_rcontext. - A new data type handler Type_handler_sys_refcursor in plugins/type_cursor/ It is designed to store uint16 references - positions of the cursor in THD::m_statement_cursors. - Type_handler_sys_refcursor suppresses some derived numeric features. When a SYS_REFCURSOR variable is used as an integer an error is raised. - A new abstract class sp_instr_fetch_cursor. It's needed to share the common code between "OPEN cur" (for static cursors) and "OPER cur FOR stmt" (for SYS_REFCURSORs). - New sp_instr classes: * sp_instr_copen_by_ref - OPEN sys_ref_curor FOR stmt; * sp_instr_cfetch_by_ref - FETCH sys_ref_cursor INTO targets; * sp_instr_cclose_by_ref - CLOSE sys_ref_cursor; * sp_instr_destruct_variable - to destruct SYS_REFCURSOR variables when the execution goes out of the BEGIN..END block where SYS_REFCURSOR variables are declared. - New methods in LEX: * sp_open_cursor_for_stmt - handles "OPEN sys_ref_cursor FOR stmt". * sp_add_instr_fetch_cursor - "FETCH cur INTO targets" for both static cursors and SYS_REFCURSORs. * sp_close - handles "CLOSE cur" both for static cursors and SYS_REFCURSORs. - Changes in cursor functions to handle both static cursors and SYS_REFCURSORs: * Item_func_cursor_isopen * Item_func_cursor_found * Item_func_cursor_notfound * Item_func_cursor_rowcount - A new system variable @@max_open_cursors - to limit the number of cursors (static and SYS_REFCURSORs) opened at the same time. Its allowed range is [0-65536], with 50 by default. - A new virtual method Type_handler::can_return_bool() telling if calling item->val_bool() is allowed for Items of this data type, or if otherwise the "Illegal parameter for operation" error should be raised at fix_fields() time. - New methods in Sp_rcontext_handler: * get_cursor() * get_cursor_by_ref() - A new class Sp_rcontext_handler_statement to handle top level statement wide cursors which are shared by all substatements. - A new virtual method expr_event_handler() in classes Item and Field. It's needed to close (and make available for a new OPEN) unused THD::m_statement_cursors elements which do not have any references any more. It can happen in various moments in time, e.g. * after evaluation parameters of an SQL routine * after assigning a cursor expression into a SYS_REFCURSOR variable * when leaving a BEGIN..END block with SYS_REFCURSOR variables * after setting OUT/INOUT routine actual parameters from formal parameters.
This commit is contained in:
329
sql/sql_class.h
329
sql/sql_class.h
@ -1,6 +1,6 @@
|
||||
/*
|
||||
Copyright (c) 2000, 2016, Oracle and/or its affiliates.
|
||||
Copyright (c) 2009, 2024, MariaDB Corporation.
|
||||
Copyright (c) 2009, 2025, MariaDB Corporation.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@ -855,6 +855,7 @@ typedef struct system_variables
|
||||
uint column_compression_threshold;
|
||||
uint column_compression_zlib_level;
|
||||
uint in_subquery_conversion_threshold;
|
||||
uint max_open_cursors;
|
||||
int max_user_connections;
|
||||
|
||||
/**
|
||||
@ -1202,6 +1203,9 @@ struct THD_count
|
||||
|
||||
#ifdef MYSQL_SERVER
|
||||
|
||||
#include "select_result.h"
|
||||
#include "statement_rcontext.h"
|
||||
|
||||
void free_tmp_table(THD *thd, TABLE *entry);
|
||||
|
||||
|
||||
@ -1250,6 +1254,28 @@ public:
|
||||
|
||||
enum_state state;
|
||||
|
||||
/*
|
||||
Bit-ORed mask of Item::with_flag for *some* items in free_list.
|
||||
The goal is to have the cumulated COMPLEX_DATA_TYPE flag.
|
||||
So far only only some items can can COMPLEX_DATA_TYPE:
|
||||
Item_param, Item_func, Item_sp_variable, Item_row
|
||||
For other Item types this flag is not collected.
|
||||
*/
|
||||
item_with_t with_flags_bit_or_for_complex_data_types;
|
||||
|
||||
bool with_complex_data_types() const
|
||||
{
|
||||
return (bool) (with_flags_bit_or_for_complex_data_types &
|
||||
item_with_t::COMPLEX_DATA_TYPE);
|
||||
}
|
||||
|
||||
/*
|
||||
Raise an error if free_list contains items with complex data types.
|
||||
@param op - the operation name for the error message, e.g. "CREATE VIEW"
|
||||
@return - true if the error was raised, or false otherwise
|
||||
*/
|
||||
bool check_free_list_no_complex_data_types(const char *op);
|
||||
|
||||
public:
|
||||
/* We build without RTTI, so dynamic_cast can't be used. */
|
||||
enum Type
|
||||
@ -1258,7 +1284,8 @@ public:
|
||||
};
|
||||
|
||||
Query_arena(MEM_ROOT *mem_root_arg, enum enum_state state_arg) :
|
||||
free_list(0), mem_root(mem_root_arg), state(state_arg)
|
||||
free_list(0), mem_root(mem_root_arg), state(state_arg),
|
||||
with_flags_bit_or_for_complex_data_types(item_with_t::NONE)
|
||||
{ INIT_ARENA_DBUG_INFO; }
|
||||
/*
|
||||
This constructor is used only when Query_arena is created as
|
||||
@ -1524,6 +1551,8 @@ public:
|
||||
|
||||
void set_query_arena(Query_arena *set);
|
||||
|
||||
void expr_event_handler_for_free_list(THD *thd, expr_event_t event);
|
||||
|
||||
void free_items();
|
||||
/* Close the active state associated with execution of this statement */
|
||||
virtual bool cleanup_stmt(bool /*restore_set_statement_vars*/);
|
||||
@ -1559,8 +1588,6 @@ public:
|
||||
};
|
||||
|
||||
|
||||
class Server_side_cursor;
|
||||
|
||||
/*
|
||||
Struct to catch changes in column metadata that is sent to client.
|
||||
in the "result set metadata". Used to support
|
||||
@ -2950,7 +2977,6 @@ enum class THD_WHERE
|
||||
};
|
||||
|
||||
|
||||
class THD;
|
||||
const char *thd_where(THD *thd);
|
||||
|
||||
|
||||
@ -2973,7 +2999,8 @@ class THD: public THD_count, /* this must be first */
|
||||
public Item_change_list,
|
||||
public MDL_context_owner,
|
||||
public Open_tables_state,
|
||||
public Sp_caches
|
||||
public Sp_caches,
|
||||
public Statement_rcontext
|
||||
{
|
||||
private:
|
||||
inline bool is_stmt_prepare() const
|
||||
@ -3744,6 +3771,19 @@ public:
|
||||
{
|
||||
m_row_count_func= row_count_func;
|
||||
}
|
||||
|
||||
/*
|
||||
Free all top level statement data (e.g. belonging to SYS_REFCURSORs)
|
||||
and reinit it for a new top level statement.
|
||||
It's called in the very end of the top level statement
|
||||
(it's not called for individual stored routune statements).
|
||||
*/
|
||||
void statement_rcontext_reinit()
|
||||
{
|
||||
Statement_rcontext::reinit(this);
|
||||
with_flags_bit_or_for_complex_data_types= item_with_t::NONE;
|
||||
}
|
||||
|
||||
inline void set_affected_rows(longlong row_count_func)
|
||||
{
|
||||
/*
|
||||
@ -6230,144 +6270,6 @@ public:
|
||||
|
||||
class JOIN;
|
||||
|
||||
/* Pure interface for sending tabular data */
|
||||
class select_result_sink: public Sql_alloc
|
||||
{
|
||||
public:
|
||||
THD *thd;
|
||||
select_result_sink(THD *thd_arg): thd(thd_arg) {}
|
||||
inline int send_data_with_check(List<Item> &items,
|
||||
SELECT_LEX_UNIT *u,
|
||||
ha_rows sent)
|
||||
{
|
||||
if (u->lim.check_offset(sent))
|
||||
return 0;
|
||||
|
||||
if (u->thd->killed == ABORT_QUERY)
|
||||
return 0;
|
||||
|
||||
return send_data(items);
|
||||
}
|
||||
/*
|
||||
send_data returns 0 on ok, 1 on error and -1 if data was ignored, for
|
||||
example for a duplicate row entry written to a temp table.
|
||||
*/
|
||||
virtual int send_data(List<Item> &items)=0;
|
||||
virtual ~select_result_sink() = default;
|
||||
// Used in cursors to initialize and reset
|
||||
void reinit(THD *thd_arg) { thd= thd_arg; }
|
||||
};
|
||||
|
||||
class select_result_interceptor;
|
||||
|
||||
/*
|
||||
Interface for sending tabular data, together with some other stuff:
|
||||
|
||||
- Primary purpose seems to be sending typed tabular data:
|
||||
= the DDL is sent with send_fields()
|
||||
= the rows are sent with send_data()
|
||||
Besides that,
|
||||
- there seems to be an assumption that the sent data is a result of
|
||||
SELECT_LEX_UNIT *unit,
|
||||
- nest_level is used by SQL parser
|
||||
*/
|
||||
|
||||
class select_result :public select_result_sink
|
||||
{
|
||||
protected:
|
||||
/*
|
||||
All descendant classes have their send_data() skip the first
|
||||
unit->offset_limit_cnt rows sent. Select_materialize
|
||||
also uses unit->get_column_types().
|
||||
*/
|
||||
SELECT_LEX_UNIT *unit;
|
||||
/* Something used only by the parser: */
|
||||
public:
|
||||
ha_rows est_records; /* estimated number of records in the result */
|
||||
select_result(THD *thd_arg): select_result_sink(thd_arg), est_records(0) {}
|
||||
void set_unit(SELECT_LEX_UNIT *unit_arg) { unit= unit_arg; }
|
||||
virtual ~select_result() = default;
|
||||
/**
|
||||
Change wrapped select_result.
|
||||
|
||||
Replace the wrapped result object with new_result and call
|
||||
prepare() and prepare2() on new_result.
|
||||
|
||||
This base class implementation doesn't wrap other select_results.
|
||||
|
||||
@param new_result The new result object to wrap around
|
||||
|
||||
@retval false Success
|
||||
@retval true Error
|
||||
*/
|
||||
virtual bool change_result(select_result *new_result)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
virtual int prepare(List<Item> &list, SELECT_LEX_UNIT *u)
|
||||
{
|
||||
unit= u;
|
||||
return 0;
|
||||
}
|
||||
virtual int prepare2(JOIN *join) { return 0; }
|
||||
/*
|
||||
Because of peculiarities of prepared statements protocol
|
||||
we need to know number of columns in the result set (if
|
||||
there is a result set) apart from sending columns metadata.
|
||||
*/
|
||||
virtual uint field_count(List<Item> &fields) const
|
||||
{ return fields.elements; }
|
||||
virtual bool send_result_set_metadata(List<Item> &list, uint flags)=0;
|
||||
virtual bool initialize_tables (JOIN *join) { return 0; }
|
||||
virtual bool send_eof()=0;
|
||||
/**
|
||||
Check if this query returns a result set and therefore is allowed in
|
||||
cursors and set an error message if it is not the case.
|
||||
|
||||
@retval FALSE success
|
||||
@retval TRUE error, an error message is set
|
||||
*/
|
||||
virtual bool check_simple_select() const;
|
||||
virtual void abort_result_set() {}
|
||||
virtual void reset_for_next_ps_execution();
|
||||
void set_thd(THD *thd_arg) { thd= thd_arg; }
|
||||
void reinit(THD *thd_arg)
|
||||
{
|
||||
select_result_sink::reinit(thd_arg);
|
||||
unit= NULL;
|
||||
}
|
||||
#ifdef EMBEDDED_LIBRARY
|
||||
virtual void begin_dataset() {}
|
||||
#else
|
||||
void begin_dataset() {}
|
||||
#endif
|
||||
virtual void update_used_tables() {}
|
||||
|
||||
/* this method is called just before the first row of the table can be read */
|
||||
virtual void prepare_to_read_rows() {}
|
||||
|
||||
void remove_offset_limit()
|
||||
{
|
||||
unit->lim.remove_offset();
|
||||
}
|
||||
|
||||
/*
|
||||
This returns
|
||||
- NULL if the class sends output row to the client
|
||||
- this if the output is set elsewhere (a file, @variable, or table).
|
||||
*/
|
||||
virtual select_result_interceptor *result_interceptor()=0;
|
||||
|
||||
/*
|
||||
This method is used to distinguish an normal SELECT from the cursor
|
||||
structure discovery for cursor%ROWTYPE routine variables.
|
||||
If this method returns "true", then a SELECT execution performs only
|
||||
all preparation stages, but does not fetch any rows.
|
||||
*/
|
||||
virtual bool view_structure_only() const { return false; }
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
This is a select_result_sink which simply writes all data into a (temporary)
|
||||
table. Creation/deletion of the table is outside of the scope of the class
|
||||
@ -6414,145 +6316,6 @@ private:
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
Base class for select_result descendants which intercept and
|
||||
transform result set rows. As the rows are not sent to the client,
|
||||
sending of result set metadata should be suppressed as well.
|
||||
*/
|
||||
|
||||
class select_result_interceptor: public select_result
|
||||
{
|
||||
public:
|
||||
select_result_interceptor(THD *thd_arg):
|
||||
select_result(thd_arg), suppress_my_ok(false)
|
||||
{
|
||||
DBUG_ENTER("select_result_interceptor::select_result_interceptor");
|
||||
DBUG_PRINT("enter", ("this %p", this));
|
||||
DBUG_VOID_RETURN;
|
||||
} /* Remove gcc warning */
|
||||
uint field_count(List<Item> &fields) const override { return 0; }
|
||||
bool send_result_set_metadata(List<Item> &fields, uint flag) override { return FALSE; }
|
||||
select_result_interceptor *result_interceptor() override { return this; }
|
||||
|
||||
/*
|
||||
Instruct the object to not call my_ok(). Client output will be handled
|
||||
elsewhere. (this is used by ANALYZE $stmt feature).
|
||||
*/
|
||||
void disable_my_ok_calls() { suppress_my_ok= true; }
|
||||
void reinit(THD *thd_arg)
|
||||
{
|
||||
select_result::reinit(thd_arg);
|
||||
suppress_my_ok= false;
|
||||
}
|
||||
protected:
|
||||
bool suppress_my_ok;
|
||||
};
|
||||
|
||||
|
||||
class sp_cursor_statistics
|
||||
{
|
||||
protected:
|
||||
ulonglong m_fetch_count; // Number of FETCH commands since last OPEN
|
||||
ulonglong m_row_count; // Number of successful FETCH since last OPEN
|
||||
bool m_found; // If last FETCH fetched a row
|
||||
public:
|
||||
sp_cursor_statistics()
|
||||
:m_fetch_count(0),
|
||||
m_row_count(0),
|
||||
m_found(false)
|
||||
{ }
|
||||
bool found() const
|
||||
{ return m_found; }
|
||||
|
||||
ulonglong row_count() const
|
||||
{ return m_row_count; }
|
||||
|
||||
ulonglong fetch_count() const
|
||||
{ return m_fetch_count; }
|
||||
void reset() { *this= sp_cursor_statistics(); }
|
||||
};
|
||||
|
||||
|
||||
class sp_instr_cpush;
|
||||
|
||||
/* A mediator between stored procedures and server side cursors */
|
||||
class sp_lex_keeper;
|
||||
class sp_cursor: public sp_cursor_statistics
|
||||
{
|
||||
private:
|
||||
/// An interceptor of cursor result set used to implement
|
||||
/// FETCH <cname> INTO <varlist>.
|
||||
class Select_fetch_into_spvars: public select_result_interceptor
|
||||
{
|
||||
List<sp_fetch_target> *m_fetch_target_list;
|
||||
uint field_count;
|
||||
bool m_view_structure_only;
|
||||
bool send_data_to_variable_list(List<sp_fetch_target> &vars,
|
||||
List<Item> &items);
|
||||
public:
|
||||
Select_fetch_into_spvars(THD *thd_arg, bool view_structure_only)
|
||||
:select_result_interceptor(thd_arg),
|
||||
m_view_structure_only(view_structure_only)
|
||||
{}
|
||||
void reset(THD *thd_arg)
|
||||
{
|
||||
select_result_interceptor::reinit(thd_arg);
|
||||
m_fetch_target_list= NULL;
|
||||
field_count= 0;
|
||||
}
|
||||
uint get_field_count() { return field_count; }
|
||||
void set_spvar_list(List<sp_fetch_target> *vars)
|
||||
{
|
||||
m_fetch_target_list= vars;
|
||||
}
|
||||
|
||||
bool send_eof() override { return FALSE; }
|
||||
int send_data(List<Item> &items) override;
|
||||
int prepare(List<Item> &list, SELECT_LEX_UNIT *u) override;
|
||||
bool view_structure_only() const override { return m_view_structure_only; }
|
||||
};
|
||||
|
||||
public:
|
||||
sp_cursor()
|
||||
:result(NULL, false),
|
||||
server_side_cursor(NULL)
|
||||
{ }
|
||||
sp_cursor(THD *thd_arg, bool view_structure_only)
|
||||
:result(thd_arg, view_structure_only),
|
||||
server_side_cursor(NULL)
|
||||
{}
|
||||
|
||||
virtual ~sp_cursor()
|
||||
{ destroy(); }
|
||||
|
||||
virtual sp_lex_keeper *get_lex_keeper() { return nullptr; }
|
||||
|
||||
int open(THD *thd);
|
||||
|
||||
int close(THD *thd);
|
||||
|
||||
my_bool is_open()
|
||||
{ return MY_TEST(server_side_cursor); }
|
||||
|
||||
int fetch(THD *, List<sp_fetch_target> *vars, bool error_on_no_data);
|
||||
|
||||
bool export_structure(THD *thd, Row_definition_list *list);
|
||||
|
||||
void reset(THD *thd_arg)
|
||||
{
|
||||
sp_cursor_statistics::reset();
|
||||
result.reinit(thd_arg);
|
||||
server_side_cursor= NULL;
|
||||
}
|
||||
|
||||
virtual sp_instr_cpush *get_push_instr() { return nullptr; }
|
||||
private:
|
||||
Select_fetch_into_spvars result;
|
||||
Server_side_cursor *server_side_cursor;
|
||||
void destroy();
|
||||
};
|
||||
|
||||
|
||||
class select_send :public select_result {
|
||||
/**
|
||||
True if we have sent result set metadata to the client.
|
||||
|
Reference in New Issue
Block a user