mirror of
https://github.com/MariaDB/server.git
synced 2026-10-23 18:49:29 +03:00
SET sql_mode=ORACLE;
DELIMITER $$
CREATE OR REPLACE PACKAGE pkg AS
-- Declare a package public data type
TYPE varchar_array IS TABLE OF VARCHAR(2000) INDEX BY INTEGER;
END;
$$
DELIMITER ;
DELIMITER $$
CREATE OR REPLACE PROCEDURE p1 AS
v pkg.varchar_array; -- Use the package public data type
BEGIN
v(0):='test';
SELECT v(0);
END;
$$
DELIMITER ;
Note, the change is done only for sql_mode=ORACLE, because the TYPE
declaration is not available for the default mode.
Where package-wide types are available
--------------------------------------
- Variabe list type:
DECLARE var pkg1.type1;
- RETURN type for a package routine:
CREATE FUNCTION .. RETURN pkg1.type1 ...
- Parameter type for a package routine:
PROCEDURE p1(param1 pkg1.type1);
- Assoc array element type:
TYPE assoc1_t IS TABLE OF pkg1.type1 ...
- REF CURSOR RETURN type:
TYPE cur1_t IS REF CURSOR RETURN pkg1.type1;
Change details
--------------
- Adding a member Lex_length_and_dec_st::m_foreign_module_type
It's set to true when the data type was initialized from a TYPE
in foreign routine (e.g. in PACKAGE spec).
It's needed to prevent use of qualified identifiers in public contexts,
i.e. in schema public routine parameter types and schema publuc function
RETURN types.
Adding a helper method sp_head::check_applicability() which prevents
use of qualified types in public context.
- Adding a helper method sp_head::raise_unknown_data_type().
- Adding methods LEX::set_field_type_typedef_package_spec() for
2-step and 3-step qualified indentifiers.
It's used in field_type_all_with_typedefs which covers cases:
- Variabe list type : DECLARE var pkg1.type1;
- RETURN type : CREATE FUNCTION .. RETURN pkg1.type1 ...
- Parameter type : PROCEDURE p1(param1 pkg1.type1);
- Assoc array element type : TYPE assoc1_t IS TABLE OF pkg1.type1 ...
- Adding a method LEX::declare_type_ref_cursor_return_typedef().
It handles cases when a new TYPE REF CURSOR RETURN is declared,
for both for qualified RETURN types and non-qualified RETURN types:
- TYPE cur0_t IS REF CURSOR RETURN rec1_t;
- TYPE cur0_t IS REF CURSOR RETURN pkg1.rec1_t;
- TYPE cur0_t IS REF CURSOR RETURN db1.pkg1.rec1_t;
The code was moved from LEX::declare_type_ref_cursor() into
LEX::declare_type_ref_cursor_return_typedef() and extended
to cover qualified RETURN types.
- Adding a method Sql_path::find_package_spec_type().
It iterates through all schemas specified in @@path and searches
for the given type in the given package.
- Adding a helper method sp_pcontext::type_defs_add_ref_cursor()
to reuse the code.
- Adding a new method sp_package::get_typedef() to search
for TYPE definitions in PACKAGE specifications.
- Adding a new method sp_head::get_typedef_package_spec()
to search for TYPE definitions used by a PROCEDURE or FUNCTION.
- Adding a helper method
Sp_handler::sp_cache_routine_reentrant_suppress_errors
Adding a method Sp_handler::find_package_spec().
548 lines
13 KiB
C++
548 lines
13 KiB
C++
#include "sql_plugin.h"
|
|
#include "sql_class.h" // class THD
|
|
#include "sp_head.h" // class sp_name
|
|
#include "lex_ident.h"
|
|
#include "sql_db.h"
|
|
#include "sql_path.h"
|
|
|
|
static constexpr Lex_ident_ci cur_schema= {STRING_WITH_LEN("CURRENT_SCHEMA")};
|
|
|
|
Sql_path::Sql_path() : m_count(0)
|
|
{
|
|
}
|
|
|
|
|
|
Sql_path& Sql_path::operator=(const Sql_path &rhs)
|
|
{
|
|
set(rhs);
|
|
return *this;
|
|
}
|
|
|
|
|
|
Lex_ident_db Sql_path::resolve_current_schema(THD *thd, sp_head *caller,
|
|
size_t i) const
|
|
{
|
|
/* Resolve CURRENT_SCHEMA to actual database name. */
|
|
if (caller && caller->m_name.str)
|
|
return thd->to_ident_db_normalized_with_error(caller->m_db);
|
|
if (thd->db.str || thd->lex->sphead)
|
|
return thd->copy_db_normalized();
|
|
return {nullptr, 0};
|
|
}
|
|
|
|
|
|
bool Sql_path::try_resolve_in_schema(THD *thd, const Lex_ident_db_normalized &schema,
|
|
sp_name *name, const Sp_handler **sph,
|
|
Database_qualified_name *pkgname,
|
|
bool *resolved) const
|
|
{
|
|
DBUG_ASSERT(resolved);
|
|
int ret;
|
|
|
|
if (check_db_dir_existence(schema.str))
|
|
return false; // Schema doesn't exist
|
|
|
|
Database_qualified_name tmp_spname;
|
|
tmp_spname.m_name= name->m_name;
|
|
|
|
if (!name->m_explicit_name)
|
|
{
|
|
// Try to find routine directly in schema
|
|
tmp_spname.m_db= schema;
|
|
if (!(ret= (*sph)->sp_routine_exists(thd, &tmp_spname)))
|
|
{
|
|
/*
|
|
[schema] '.' routine_name
|
|
*/
|
|
name->m_db= Lex_ident_db_normalized(thd->strmake(schema.str, schema.length),
|
|
schema.length);
|
|
*resolved= true;
|
|
return false;
|
|
}
|
|
else if (ret != SP_KEY_NOT_FOUND)
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
// Try package routine resolution
|
|
if (is_package_public_routine(thd, schema, name->m_db, name->m_name, (*sph)->type()))
|
|
{
|
|
/*
|
|
[schema] '.' pkg_name '.' routine_name routine
|
|
*/
|
|
pkgname->m_db= schema;
|
|
pkgname->m_name= Lex_ident_routine(name->m_db);
|
|
*sph= (*sph)->package_routine_handler();
|
|
*resolved= true;
|
|
return name->make_package_routine_name(thd->mem_root, schema,
|
|
name->m_db, name->m_name);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
bool Sql_path::resolve(THD *thd, sp_head *caller, sp_name *name,
|
|
const Sp_handler **sph,
|
|
Database_qualified_name *pkgname) const
|
|
{
|
|
DBUG_ASSERT(name);
|
|
DBUG_ASSERT(name->m_name.str[name->m_name.length] == '\0');
|
|
|
|
// Check for fully qualified name schema.pkg.routine and exit early
|
|
if (name->m_explicit_name && strchr(name->m_name.str, '.'))
|
|
return false;
|
|
|
|
DBUG_ASSERT(!name->m_explicit_name || name->m_db.str);
|
|
if (caller) // first, search in the current package
|
|
{
|
|
if (!name->m_explicit_name) // name()
|
|
{
|
|
sp_name tmp_name(*name);
|
|
tmp_name.m_db= caller->m_db;
|
|
const Sp_handler *pkg_routine_hndlr= nullptr;
|
|
if ((*sph)->sp_resolve_package_routine_implicit(thd, caller, &tmp_name,
|
|
&pkg_routine_hndlr,
|
|
pkgname))
|
|
return true;
|
|
|
|
if (pkg_routine_hndlr)
|
|
{
|
|
*sph= pkg_routine_hndlr;
|
|
*name= tmp_name;
|
|
return false;
|
|
}
|
|
}
|
|
else // name1.name2()
|
|
{
|
|
const Sp_handler *pkg_routine_hndlr= nullptr;
|
|
if ((*sph)->sp_resolve_package_routine_explicit(thd, caller, name,
|
|
&pkg_routine_hndlr,
|
|
pkgname))
|
|
return true;
|
|
|
|
if (pkg_routine_hndlr)
|
|
{
|
|
*sph= pkg_routine_hndlr;
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool resolved= false;
|
|
for (size_t i= 0; i < m_count; i++)
|
|
{
|
|
Lex_ident_db schema;
|
|
if (is_cur_schema(i))
|
|
{
|
|
// If PATH contains only CURRENT_SCHEMA (default), skip PATH resolution
|
|
// to avoid extra table operations that affect performance
|
|
if (m_count== 1 && !name->m_explicit_name)
|
|
{
|
|
if (!name->m_db.str && thd->db.str)
|
|
name->m_db= thd->copy_db_normalized();
|
|
return false;
|
|
}
|
|
|
|
schema= resolve_current_schema(thd, caller, i);
|
|
if (!schema.str)
|
|
continue; // CURRENT_SCHEMA resolution failed, skip this entry
|
|
/* recursive calls */
|
|
if (caller && name->eq_routine_name(caller))
|
|
return false;
|
|
}
|
|
else
|
|
schema= m_schemas[i];
|
|
|
|
/*
|
|
Schemas are already normalized when added to the path, except for
|
|
CURRENT_SCHEMA which was resolved above. We can use them directly.
|
|
*/
|
|
const Lex_ident_db_normalized dbn(schema.str, schema.length);
|
|
|
|
if (try_resolve_in_schema(thd, dbn, name, sph, pkgname, &resolved))
|
|
return true;
|
|
|
|
if (resolved)
|
|
return false;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/*
|
|
Iterate through all schemas in @@path and find a package "package" with the
|
|
type "type"
|
|
|
|
@param thd - The THD
|
|
@param OUT db - The found database which contains a type package.type
|
|
If db->str is nullptr the nothing was found.
|
|
@param package - The package to find.
|
|
@param type - The type to find.
|
|
@retval true - If error happened during resolution.
|
|
@retval false - If no errors happened during resolution.
|
|
Note, if nothing was found and no errors happened,
|
|
the return value is false.
|
|
*/
|
|
bool Sql_path::find_package_spec_type(THD *thd,
|
|
Lex_ident_db_normalized *db,
|
|
const Lex_ident_sys_st &package,
|
|
const Lex_ident_sys_st &type)
|
|
{
|
|
DBUG_ASSERT(!thd->is_fatal_error);
|
|
*db= Lex_ident_db_normalized();
|
|
for (size_t i= 0; i < m_count; i++)
|
|
{
|
|
sp_package *pkg;
|
|
Lex_ident_db_normalized ncdb;
|
|
if (is_cur_schema(i))
|
|
{
|
|
if (!thd->db.str)
|
|
continue;
|
|
if (!(ncdb= thd->copy_db_normalized()).str)
|
|
return true; // EOM
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
Conversion to Lex_ident_db_normalized is safe because
|
|
database names get into Sql_path in normalized form.
|
|
*/
|
|
ncdb= Lex_ident_db_normalized(m_schemas[i].str, m_schemas[i].length);
|
|
}
|
|
if (!(pkg= Sp_handler::find_package_spec(thd, ncdb, package)))
|
|
{
|
|
if (thd->is_fatal_error)
|
|
return true; // E.g. ER_STACK_OVERRUN_NEED_MORE raised
|
|
continue;
|
|
}
|
|
sp_type_def *tdef= pkg->find_type_def(type);
|
|
if (tdef)
|
|
{
|
|
*db= ncdb;
|
|
return false; // Found a TYPE declaration in a CREATE PACKAGE
|
|
}
|
|
}
|
|
return false; // Nothing found
|
|
}
|
|
|
|
|
|
void Sql_path::free()
|
|
{
|
|
if (m_count)
|
|
my_free(const_cast<char*>(m_schemas[0].str));
|
|
m_count= 0;
|
|
}
|
|
|
|
|
|
bool Sql_path::init()
|
|
{
|
|
free();
|
|
char *buf= my_strndup(key_memory_Sys_var_charptr_value, "", 1, MYF(MY_WME));
|
|
m_schemas[m_count++]= {buf, 0};
|
|
return false;
|
|
}
|
|
|
|
|
|
bool Sql_path::is_cur_schema(size_t i) const
|
|
{
|
|
return !m_schemas[i].length;
|
|
}
|
|
|
|
|
|
bool Sql_path::add_schema(char **to, bool is_quoted)
|
|
{
|
|
const Lex_ident_db &dbn= m_schemas[m_count];
|
|
m_schemas[m_count].length= *to - m_schemas[m_count].str;
|
|
*(*to)++= 0;
|
|
|
|
// Validate
|
|
if (Lex_ident_db::check_name_with_error(dbn))
|
|
goto err;
|
|
|
|
if (!is_quoted && cur_schema.streq(dbn))
|
|
{
|
|
for (size_t i = 0; i < m_count; i++) // Check for duplicates
|
|
if (is_cur_schema(i))
|
|
goto err;
|
|
m_schemas[m_count].length= 0;
|
|
}
|
|
else
|
|
{
|
|
for (size_t i = 0; i < m_count; i++)
|
|
if (!is_cur_schema(i) && dbn.streq(m_schemas[i]))
|
|
goto err;
|
|
}
|
|
|
|
m_count++;
|
|
return false;
|
|
err:
|
|
return true;
|
|
}
|
|
|
|
|
|
void Sql_path::set(const Sql_path &rhs)
|
|
{
|
|
free();
|
|
|
|
if ((m_count= rhs.m_count))
|
|
{
|
|
auto rbuf= rhs.m_schemas[0].str;
|
|
auto rend= rhs.m_schemas[m_count-1].str + rhs.m_schemas[m_count-1].length;
|
|
auto buf= (const char*)my_memdup(key_memory_Sys_var_charptr_value, rbuf,
|
|
rend - rbuf + 1, MYF(MY_WME));
|
|
for (size_t i= 0; i < rhs.m_count; i++)
|
|
m_schemas[i]= { rhs.m_schemas[i].str - rbuf + buf, rhs.m_schemas[i].length };
|
|
}
|
|
}
|
|
|
|
|
|
void Sql_path::set(Sql_path &&rhs)
|
|
{
|
|
size_t count= std::max(m_count, rhs.m_count);
|
|
for (size_t i= 0; i < count; i++)
|
|
std::swap(m_schemas[i], rhs.m_schemas[i]);
|
|
std::swap(m_count, rhs.m_count);
|
|
rhs.free();
|
|
}
|
|
|
|
|
|
bool Sql_path::from_text(const system_variables &sv, String *str)
|
|
{
|
|
enum tokenize_state
|
|
{
|
|
START, QUOTED_TOKEN_DOUBLE='"', QUOTED_TOKEN_BACKTICK='`', UNQUOTED_TOKEN, END
|
|
} state= START;
|
|
|
|
const bool ansi_quotes= sv.sql_mode & MODE_ANSI_QUOTES;
|
|
|
|
CHARSET_INFO *cs= &my_charset_utf8mb3_general_ci; // as in make_ident_casedn()
|
|
DBUG_ASSERT(cs->cset->casedn_multiply(cs) == 1);
|
|
DBUG_ASSERT(str->ptr());
|
|
char *buf= (char*)my_malloc(key_memory_Sys_var_charptr_value,
|
|
str->length() + 1, MYF(MY_WME));
|
|
if (!buf)
|
|
return true;
|
|
|
|
char *curr= buf, *to= buf, *end;
|
|
|
|
if (lower_case_table_names > 0)
|
|
end= buf + cs->cset->casedn(cs, str->ptr(), str->length(), buf, str->length());
|
|
else
|
|
{
|
|
memcpy(buf, str->ptr(), str->length());
|
|
end= buf + str->length();
|
|
}
|
|
|
|
free();
|
|
while (curr < end)
|
|
{
|
|
auto len = cs->charlen(curr, end);
|
|
|
|
switch (state)
|
|
{
|
|
case START:
|
|
if (*curr == ',' || my_isspace(cs, (uchar) *curr))
|
|
curr++;
|
|
else
|
|
{
|
|
if (m_count >= array_elements(m_schemas))
|
|
{
|
|
my_error(ER_VALUE_TOO_LONG, MYF(0), "PATH");
|
|
goto err;
|
|
}
|
|
m_schemas[m_count].str= to;
|
|
if (*curr == '`' || (*curr == '"' && ansi_quotes))
|
|
state= (tokenize_state) *curr++;
|
|
else if (*curr == '"')
|
|
goto err_bad_val;
|
|
else
|
|
{
|
|
state= UNQUOTED_TOKEN;
|
|
while (len--)
|
|
*to++= *curr++;
|
|
}
|
|
}
|
|
break;
|
|
case QUOTED_TOKEN_BACKTICK:
|
|
case QUOTED_TOKEN_DOUBLE:
|
|
if (*curr == state)
|
|
{
|
|
curr++;
|
|
if (curr >= end || *curr != state)
|
|
{
|
|
state= END;
|
|
if (add_schema(&to, true))
|
|
goto err_bad_val;
|
|
break;
|
|
}
|
|
}
|
|
while (len--)
|
|
*to++= *curr++;
|
|
break;
|
|
case UNQUOTED_TOKEN:
|
|
if (*curr == ',' || my_isspace(cs, (uchar) *curr))
|
|
{
|
|
state= *curr++ == ',' ? START : END;
|
|
if (add_schema(&to, false))
|
|
goto err_bad_val;
|
|
break;
|
|
}
|
|
else if (*curr == '`' || *curr == '"')
|
|
goto err_bad_val;
|
|
else
|
|
while (len--)
|
|
*to++= *curr++;
|
|
break;
|
|
case END:
|
|
if (*curr == ',')
|
|
state= START;
|
|
else if (!my_isspace(cs, (uchar) *curr))
|
|
goto err_bad_val;
|
|
curr++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
switch (state)
|
|
{
|
|
case START:
|
|
case END:
|
|
break;
|
|
case QUOTED_TOKEN_BACKTICK:
|
|
case QUOTED_TOKEN_DOUBLE:
|
|
goto err_bad_val;
|
|
case UNQUOTED_TOKEN:
|
|
if (add_schema(&to, false))
|
|
goto err_bad_val;
|
|
break;
|
|
}
|
|
if (!m_count)
|
|
my_free(buf);
|
|
return false;
|
|
|
|
err_bad_val:
|
|
my_error(ER_WRONG_VALUE_FOR_VAR, MYF(0), "PATH", str->c_ptr());
|
|
err:
|
|
m_count= 0;
|
|
my_free(buf);
|
|
return true;
|
|
}
|
|
|
|
|
|
LEX_CSTRING Sql_path::get_schema_for_print(size_t num) const
|
|
{
|
|
if (is_cur_schema(num))
|
|
return cur_schema;
|
|
return m_schemas[num];
|
|
}
|
|
|
|
|
|
size_t Sql_path::text_format_nbytes_needed() const
|
|
{
|
|
size_t nbytes= 0;
|
|
|
|
for (size_t i= 0; i < m_count; i++)
|
|
{
|
|
LEX_CSTRING schema= get_schema_for_print(i);
|
|
size_t len= schema.length;
|
|
for (size_t j= 0; j < schema.length; j++)
|
|
{
|
|
if (schema.str[j] == '`')
|
|
len++;
|
|
}
|
|
|
|
nbytes+= len + 2 + 1;
|
|
}
|
|
|
|
if (nbytes)
|
|
nbytes--;
|
|
|
|
return nbytes + 1;
|
|
}
|
|
|
|
|
|
size_t Sql_path::print(char *dst, size_t nbytes_available) const
|
|
{
|
|
char *start= dst;
|
|
|
|
for (size_t i= 0; i < m_count; i++)
|
|
{
|
|
LEX_CSTRING schema= get_schema_for_print(i);
|
|
|
|
if (dst - start + schema.length + 3 > nbytes_available)
|
|
break;
|
|
|
|
if (schema.str == cur_schema.str)
|
|
{
|
|
memcpy(dst, schema.str, schema.length);
|
|
dst+= schema.length;
|
|
*dst++= ',';
|
|
continue;
|
|
}
|
|
|
|
*dst++= '`';
|
|
for (size_t j= 0; j < schema.length; j++)
|
|
{
|
|
*dst++= schema.str[j];
|
|
if (schema.str[j] == '`')
|
|
*dst++= '`';
|
|
}
|
|
*dst++= '`';
|
|
*dst++= ',';
|
|
}
|
|
|
|
if (dst > start)
|
|
dst--;
|
|
|
|
if (dst < start + nbytes_available)
|
|
*dst= '\0';
|
|
|
|
return dst - start;
|
|
}
|
|
|
|
|
|
LEX_CSTRING Sql_path::lex_cstring(MEM_ROOT *mem_root) const
|
|
{
|
|
LEX_CSTRING res;
|
|
size_t nbytes_needed= text_format_nbytes_needed();
|
|
char *ptr= (char *) alloc_root(mem_root, nbytes_needed);
|
|
if (ptr)
|
|
{
|
|
res.length= print(ptr, nbytes_needed);
|
|
res.str= ptr;
|
|
DBUG_ASSERT(res.length < nbytes_needed);
|
|
}
|
|
else
|
|
{
|
|
res.str= nullptr;
|
|
res.length= 0;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
Sql_path_instant_set::Sql_path_instant_set(THD *thd, const LEX_CSTRING &str)
|
|
: m_thd(thd), m_path(std::move(thd->variables.path))
|
|
{
|
|
String path(str.str, str.length, system_charset_info);
|
|
if (thd->variables.path.from_text(thd->variables, &path))
|
|
{
|
|
thd->variables.path.set(std::move(m_path));
|
|
m_thd= NULL;
|
|
}
|
|
}
|
|
|
|
Sql_path_instant_set::Sql_path_instant_set(THD *thd, const Sql_path &new_path)
|
|
: m_thd(thd), m_path(std::move(thd->variables.path))
|
|
{
|
|
thd->variables.path.set(new_path);
|
|
}
|
|
|
|
Sql_path_instant_set::~Sql_path_instant_set()
|
|
{
|
|
m_thd->variables.path.set(std::move(m_path));
|
|
}
|