mariadb/sql/sql_path.cc
Sergei Golubchik be67aff19b Don't implicitly search in CURRENT_SCHEMA
the intention was not to, but some code paths weren't fixed yet
2026-02-06 17:46:52 +01:00

490 lines
12 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;
}
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));
}