1
0
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:
Alexander Barkov
2023-09-14 07:01:32 +04:00
parent 1e00b9ec2a
commit f11504af51
183 changed files with 16896 additions and 536 deletions

View File

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