mirror of
https://github.com/MariaDB/server.git
synced 2025-10-18 05:32:11 +02:00

This bug is consequence of a serious architectural flaw of the query processing at the prepare stage. Context analysis of processed queries has to be done at this stage. The important part of this analysis is resolution of column references also known as name resolution. The items for column references used in a query are created by the parser. They are allocated in the statement memory because their life span is the same as the life span of the executed query. During name resolution some of the items are wrapped into objects belonging to classes derived from the Item_ref class. In many cases we can't do without such wrappers. For example, the parser does not differentiate between references to columns of base tables and references to columns of views. However for a reference to a view column we need a pointer to the translation item for this column. This pointer is stored in a Item_direct_view_ref object that wraps the item for the column reference. Before this patch the wrappers were allocated in execution memory good only for one execution. That wasn't a problem when the query was executed only once. Yet for the queries executed as prepared statements or within a stored procedure it could lead to crashes or wrong result sets at the second execution of the query. It could happen when the wrapped item was substituted for something else by permanent transformations during the optimization phase. This patch allocates all the wrappers around items created for column references in the statement memory at the first execution of PS or at the first call of SP/SF. Author: Igor Babaev Commiter: Rex Johnston (rex.johnston@mariadb.com)
310 lines
6.7 KiB
C++
310 lines
6.7 KiB
C++
#ifndef SQL_ARRAY_INCLUDED
|
|
#define SQL_ARRAY_INCLUDED
|
|
|
|
/* Copyright (c) 2003, 2005-2007 MySQL AB, 2009 Sun Microsystems, Inc.
|
|
Use is subject to license terms.
|
|
Copyright (c) 2020, 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
|
|
the Free Software Foundation; version 2 of the License.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */
|
|
|
|
#include <my_sys.h>
|
|
|
|
/**
|
|
A wrapper class which provides array bounds checking.
|
|
We do *not* own the array, we simply have a pointer to the first element,
|
|
and a length.
|
|
|
|
@remark
|
|
We want the compiler-generated versions of:
|
|
- the copy CTOR (memberwise initialization)
|
|
- the assignment operator (memberwise assignment)
|
|
|
|
@param Element_type The type of the elements of the container.
|
|
*/
|
|
template <typename Element_type> class Bounds_checked_array
|
|
{
|
|
public:
|
|
Bounds_checked_array()= default;
|
|
|
|
Bounds_checked_array(Element_type *el, size_t size_arg)
|
|
: m_array(el), m_size(size_arg)
|
|
{
|
|
if (!m_size)
|
|
m_array= NULL;
|
|
}
|
|
|
|
void reset() { m_array= NULL; m_size= 0; }
|
|
|
|
void reset(Element_type *array_arg, size_t size_arg)
|
|
{
|
|
m_array= array_arg;
|
|
m_size= size_arg;
|
|
}
|
|
|
|
/**
|
|
Set a new bound on the array. Does not resize the underlying
|
|
array, so the new size must be smaller than or equal to the
|
|
current size.
|
|
*/
|
|
void resize(size_t new_size)
|
|
{
|
|
DBUG_ASSERT(new_size <= m_size);
|
|
m_size= new_size;
|
|
}
|
|
|
|
Element_type &operator[](size_t n)
|
|
{
|
|
DBUG_ASSERT(n < m_size);
|
|
return m_array[n];
|
|
}
|
|
|
|
const Element_type &operator[](size_t n) const
|
|
{
|
|
DBUG_ASSERT(n < m_size);
|
|
return m_array[n];
|
|
}
|
|
|
|
size_t element_size() const { return sizeof(Element_type); }
|
|
size_t size() const { return m_size; }
|
|
|
|
bool is_null() const { return m_array == NULL; }
|
|
|
|
void pop_front()
|
|
{
|
|
DBUG_ASSERT(m_size > 0);
|
|
m_array+= 1;
|
|
m_size-= 1;
|
|
}
|
|
|
|
Element_type *array() const { return m_array; }
|
|
|
|
Element_type *begin() const { return array(); }
|
|
Element_type *end() const { return array() + m_size; }
|
|
|
|
|
|
bool operator==(const Bounds_checked_array<Element_type>&rhs) const
|
|
{
|
|
return m_array == rhs.m_array && m_size == rhs.m_size;
|
|
}
|
|
bool operator!=(const Bounds_checked_array<Element_type>&rhs) const
|
|
{
|
|
return m_array != rhs.m_array || m_size != rhs.m_size;
|
|
}
|
|
|
|
private:
|
|
Element_type *m_array= nullptr;
|
|
size_t m_size= 0;
|
|
};
|
|
|
|
/*
|
|
A typesafe wrapper around DYNAMIC_ARRAY
|
|
|
|
TODO: Change creator to take a THREAD_SPECIFIC option.
|
|
*/
|
|
|
|
template <class Elem> class Dynamic_array
|
|
{
|
|
DYNAMIC_ARRAY array;
|
|
public:
|
|
Dynamic_array(PSI_memory_key psi_key, size_t prealloc=16, size_t increment=16)
|
|
{
|
|
init(psi_key, prealloc, increment);
|
|
}
|
|
|
|
Dynamic_array(MEM_ROOT *root, size_t prealloc=16, size_t increment=16)
|
|
{
|
|
void *init_buffer= alloc_root(root, sizeof(Elem) * prealloc);
|
|
init_dynamic_array2(root->psi_key, &array, sizeof(Elem), init_buffer,
|
|
prealloc, increment, MYF(0));
|
|
}
|
|
|
|
void init(PSI_memory_key psi_key, size_t prealloc=16, size_t increment=16)
|
|
{
|
|
init_dynamic_array2(psi_key, &array, sizeof(Elem), 0, prealloc, increment, MYF(0));
|
|
}
|
|
|
|
/**
|
|
@note Though formally this could be declared "const" it would be
|
|
misleading at it returns a non-const pointer to array's data.
|
|
*/
|
|
Elem& at(size_t idx)
|
|
{
|
|
DBUG_ASSERT(idx < array.elements);
|
|
return *(((Elem*)array.buffer) + idx);
|
|
}
|
|
|
|
/// Const variant of at(), which cannot change data
|
|
const Elem& at(size_t idx) const
|
|
{
|
|
return *(((Elem*)array.buffer) + idx);
|
|
}
|
|
|
|
Elem& operator[](size_t idx)
|
|
{
|
|
return at(idx);
|
|
}
|
|
|
|
/// Const variant of operator[]
|
|
const Elem& operator[](size_t idx) const
|
|
{
|
|
return at(idx);
|
|
}
|
|
|
|
/// @returns pointer to first element
|
|
Elem *front()
|
|
{
|
|
return (Elem*)array.buffer;
|
|
}
|
|
|
|
/// @returns pointer to first element
|
|
const Elem *front() const
|
|
{
|
|
return (const Elem*)array.buffer;
|
|
}
|
|
|
|
/// @returns pointer to last element
|
|
Elem *back()
|
|
{
|
|
return ((Elem*)array.buffer) + array.elements - 1;
|
|
}
|
|
|
|
/// @returns pointer to last element
|
|
const Elem *back() const
|
|
{
|
|
return ((const Elem*)array.buffer) + array.elements - 1;
|
|
}
|
|
|
|
size_t size() const { return array.elements; }
|
|
|
|
const Elem *end() const
|
|
{
|
|
return back() + 1;
|
|
}
|
|
|
|
/// @returns pointer to n-th element
|
|
Elem *get_pos(size_t idx)
|
|
{
|
|
return ((Elem*)array.buffer) + idx;
|
|
}
|
|
|
|
/// @returns pointer to n-th element
|
|
const Elem *get_pos(size_t idx) const
|
|
{
|
|
return ((const Elem*)array.buffer) + idx;
|
|
}
|
|
|
|
/**
|
|
@retval false ok
|
|
@retval true OOM, @c my_error() has been called.
|
|
*/
|
|
bool append(const Elem &el)
|
|
{
|
|
return insert_dynamic(&array, &el);
|
|
}
|
|
|
|
bool append_val(Elem el)
|
|
{
|
|
return (insert_dynamic(&array, (uchar*)&el));
|
|
}
|
|
|
|
bool push(Elem &el)
|
|
{
|
|
return append(el);
|
|
}
|
|
|
|
/// Pops the last element. Does nothing if array is empty.
|
|
Elem& pop()
|
|
{
|
|
return *((Elem*)pop_dynamic(&array));
|
|
}
|
|
|
|
void del(size_t idx)
|
|
{
|
|
DBUG_ASSERT(idx <= array.max_element);
|
|
delete_dynamic_element(&array, idx);
|
|
}
|
|
|
|
size_t elements() const
|
|
{
|
|
return array.elements;
|
|
}
|
|
|
|
void elements(size_t num_elements)
|
|
{
|
|
DBUG_ASSERT(num_elements <= array.max_element);
|
|
array.elements= num_elements;
|
|
}
|
|
|
|
void clear()
|
|
{
|
|
elements(0);
|
|
}
|
|
|
|
void set(size_t idx, const Elem &el)
|
|
{
|
|
set_dynamic(&array, &el, idx);
|
|
}
|
|
|
|
void freeze()
|
|
{
|
|
freeze_size(&array);
|
|
}
|
|
|
|
bool reserve(size_t new_size)
|
|
{
|
|
return allocate_dynamic(&array, new_size);
|
|
}
|
|
|
|
|
|
bool resize(size_t new_size, Elem default_val)
|
|
{
|
|
size_t old_size= elements();
|
|
if (reserve(new_size))
|
|
return true;
|
|
|
|
if (new_size > old_size)
|
|
{
|
|
set_dynamic(&array, (uchar*)&default_val, new_size - 1);
|
|
/*for (size_t i= old_size; i != new_size; i++)
|
|
{
|
|
at(i)= default_val;
|
|
}*/
|
|
}
|
|
return false;
|
|
}
|
|
|
|
~Dynamic_array()
|
|
{
|
|
delete_dynamic(&array);
|
|
}
|
|
|
|
void free_memory()
|
|
{
|
|
delete_dynamic(&array);
|
|
}
|
|
|
|
void sort(int (*cmp_func)(const void *, const void *))
|
|
{
|
|
my_qsort(array.buffer, array.elements, sizeof(Elem), cmp_func);
|
|
}
|
|
|
|
void sort(qsort_cmp2 cmp_func, void *data)
|
|
{
|
|
my_qsort2(array.buffer, array.elements, sizeof(Elem), cmp_func, data);
|
|
}
|
|
};
|
|
|
|
typedef Bounds_checked_array<Item*> Ref_ptr_array;
|
|
|
|
#endif /* SQL_ARRAY_INCLUDED */
|