summaryrefslogtreecommitdiffstats
path: root/sql/sql_list.h
diff options
context:
space:
mode:
Diffstat (limited to 'sql/sql_list.h')
-rw-r--r--sql/sql_list.h874
1 files changed, 874 insertions, 0 deletions
diff --git a/sql/sql_list.h b/sql/sql_list.h
new file mode 100644
index 00000000..5a57c86e
--- /dev/null
+++ b/sql/sql_list.h
@@ -0,0 +1,874 @@
+#ifndef INCLUDES_MYSQL_SQL_LIST_H
+#define INCLUDES_MYSQL_SQL_LIST_H
+/* Copyright (c) 2000, 2012, Oracle and/or its affiliates.
+ Copyright (c) 2019, 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 */
+
+#ifdef USE_PRAGMA_INTERFACE
+#pragma interface /* gcc class implementation */
+#endif
+
+#include "sql_alloc.h"
+#include <iterator>
+
+/**
+ Simple intrusive linked list.
+
+ @remark Similar in nature to base_list, but intrusive. It keeps a
+ a pointer to the first element in the list and a indirect
+ reference to the last element.
+*/
+
+template <typename T>
+class SQL_I_List :public Sql_alloc
+{
+public:
+ uint elements;
+ /** The first element in the list. */
+ T *first;
+ /** A reference to the next element in the list. */
+ T **next;
+
+ SQL_I_List() { empty(); }
+
+ SQL_I_List(const SQL_I_List &tmp) : Sql_alloc()
+ {
+ elements= tmp.elements;
+ first= tmp.first;
+ next= elements ? tmp.next : &first;
+ }
+
+ SQL_I_List& operator=(const SQL_I_List &tmp)
+ {
+ elements= tmp.elements;
+ first= tmp.first;
+ next= elements ? tmp.next : &first;;
+ return *this;
+ }
+
+ inline void empty()
+ {
+ elements= 0;
+ first= NULL;
+ next= &first;
+ }
+
+ inline void link_in_list(T *element, T **next_ptr)
+ {
+ elements++;
+ (*next)= element;
+ next= next_ptr;
+ *next= NULL;
+ }
+
+ inline void save_and_clear(SQL_I_List<T> *save)
+ {
+ *save= *this;
+ empty();
+ }
+
+ inline void push_front(SQL_I_List<T> *save)
+ {
+ /* link current list last */
+ *save->next= first;
+ first= save->first;
+ elements+= save->elements;
+ }
+
+ inline void push_back(SQL_I_List<T> *save)
+ {
+ if (save->first)
+ {
+ *next= save->first;
+ next= save->next;
+ elements+= save->elements;
+ }
+ }
+};
+
+
+/*
+ Basic single linked list
+ Used for item and item_buffs.
+ All list ends with a pointer to the 'end_of_list' element, which
+ data pointer is a null pointer and the next pointer points to itself.
+ This makes it very fast to traverse lists as we don't have to
+ test for a specialend condition for list that can't contain a null
+ pointer.
+*/
+
+
+/**
+ list_node - a node of a single-linked list.
+ @note We never call a destructor for instances of this class.
+*/
+
+struct list_node :public Sql_alloc
+{
+ list_node *next;
+ void *info;
+ list_node(const void *info_par, list_node *next_par)
+ :next(next_par), info(const_cast<void *>(info_par))
+ {}
+ list_node() /* For end_of_list */
+ {
+ info= 0;
+ next= this;
+ }
+};
+
+typedef bool List_eq(void *a, void *b);
+
+extern MYSQL_PLUGIN_IMPORT list_node end_of_list;
+
+class base_list :public Sql_alloc
+{
+protected:
+ list_node *first,**last;
+
+public:
+ uint elements;
+
+ bool operator==(const base_list &rhs) const
+ {
+ return
+ elements == rhs.elements &&
+ first == rhs.first &&
+ last == rhs.last;
+ }
+ base_list& operator=(const base_list &rhs)
+ {
+ elements= rhs.elements;
+ first= rhs.first;
+ last= elements ? rhs.last : &first;
+ return *this;
+ }
+
+ inline void empty() { elements=0; first= &end_of_list; last=&first;}
+ inline base_list() { empty(); }
+ /**
+ This is a shallow copy constructor that implicitly passes the ownership
+ from the source list to the new instance. The old instance is not
+ updated, so both objects end up sharing the same nodes. If one of
+ the instances then adds or removes a node, the other becomes out of
+ sync ('last' pointer), while still operational. Some old code uses and
+ relies on this behaviour. This logic is quite tricky: please do not use
+ it in any new code.
+ */
+ inline base_list(const base_list &tmp) :Sql_alloc()
+ {
+ *this= tmp;
+ }
+ /**
+ Construct a deep copy of the argument in memory root mem_root.
+ The elements themselves are copied by pointer. If you also
+ need to copy elements by value, you should employ
+ list_copy_and_replace_each_value after creating a copy.
+ */
+ bool copy(const base_list *rhs, MEM_ROOT *mem_root);
+ base_list(const base_list &rhs, MEM_ROOT *mem_root) { copy(&rhs, mem_root); }
+ inline base_list(bool) {}
+ inline bool push_back(void *info)
+ {
+ if (((*last)=new list_node(info, &end_of_list)))
+ {
+ last= &(*last)->next;
+ elements++;
+ return 0;
+ }
+ return 1;
+ }
+ inline bool push_back(void *info, MEM_ROOT *mem_root)
+ {
+ if (((*last)=new (mem_root) list_node(info, &end_of_list)))
+ {
+ last= &(*last)->next;
+ elements++;
+ return 0;
+ }
+ return 1;
+ }
+ bool push_front_impl(list_node *node)
+ {
+ if (node)
+ {
+ if (last == &first)
+ last= &node->next;
+ first=node;
+ elements++;
+ return 0;
+ }
+ return 1;
+ }
+ inline bool push_front(void *info)
+ { return push_front_impl(new list_node(info, first)); }
+ inline bool push_front(void *info, MEM_ROOT *mem_root)
+ { return push_front_impl(new (mem_root) list_node(info,first)); }
+ void remove(list_node **prev)
+ {
+ list_node *node=(*prev)->next;
+ if (!--elements)
+ last= &first;
+ else if (last == &(*prev)->next)
+ last= prev;
+ delete *prev;
+ *prev=node;
+ }
+ inline void append(base_list *list)
+ {
+ if (!list->is_empty())
+ {
+ if (is_empty())
+ {
+ *this= *list;
+ return;
+ }
+ *last= list->first;
+ last= list->last;
+ elements+= list->elements;
+ }
+ }
+ inline void *pop(void)
+ {
+ if (first == &end_of_list) return 0;
+ list_node *tmp=first;
+ first=first->next;
+ if (!--elements)
+ last= &first;
+ return tmp->info;
+ }
+
+ /*
+ Remove from this list elements that are contained in the passed list.
+ We assume that the passed list is a tail of this list (that is, the whole
+ list_node* elements are shared).
+ */
+ inline void disjoin(const base_list *list)
+ {
+ list_node **prev= &first;
+ list_node *node= first;
+ list_node *list_first= list->first;
+ elements=0;
+ while (node != &end_of_list && node != list_first)
+ {
+ prev= &node->next;
+ node= node->next;
+ elements++;
+ if (node == &end_of_list)
+ return;
+ }
+ *prev= &end_of_list;
+ last= prev;
+ }
+ inline void prepend(base_list *list)
+ {
+ if (!list->is_empty())
+ {
+ if (is_empty())
+ last= list->last;
+ *list->last= first;
+ first= list->first;
+ elements+= list->elements;
+ }
+ }
+ /**
+ Swap two lists.
+ */
+ inline void swap(base_list &rhs)
+ {
+ list_node **rhs_last=rhs.last;
+ swap_variables(list_node *, first, rhs.first);
+ swap_variables(uint, elements, rhs.elements);
+ rhs.last= last == &first ? &rhs.first : last;
+ last = rhs_last == &rhs.first ? &first : rhs_last;
+ }
+
+ inline list_node* last_node() { return *last; }
+ inline list_node* first_node() { return first;}
+ inline void *head() { return first->info; }
+ inline void **head_ref() { return first != &end_of_list ? &first->info : 0; }
+ inline bool is_empty() { return first == &end_of_list ; }
+ inline list_node *last_ref() { return &end_of_list; }
+ inline bool add_unique(void *info, List_eq *eq)
+ {
+ list_node *node= first;
+ for (;
+ node != &end_of_list && (!(*eq)(node->info, info));
+ node= node->next) ;
+ if (node == &end_of_list)
+ return push_back(info);
+ return 1;
+ }
+ friend class base_list_iterator;
+ friend class error_list;
+ friend class error_list_iterator;
+
+ /*
+ Return N-th element in the list, or NULL if the list has
+ less than N elements.
+ */
+ void *elem(uint n)
+ {
+ list_node *node= first;
+ void *data= NULL;
+ for (uint i= 0; i <= n; i++)
+ {
+ if (node == &end_of_list)
+ {
+ data= NULL;
+ break;
+ }
+ data= node->info;
+ node= node->next;
+ }
+ return data;
+ }
+
+#ifdef LIST_EXTRA_DEBUG
+ /*
+ Check list invariants and print results into trace. Invariants are:
+ - (*last) points to end_of_list
+ - There are no NULLs in the list.
+ - base_list::elements is the number of elements in the list.
+
+ SYNOPSIS
+ check_list()
+ name Name to print to trace file
+
+ RETURN
+ 1 The list is Ok.
+ 0 List invariants are not met.
+ */
+
+ bool check_list(const char *name)
+ {
+ base_list *list= this;
+ list_node *node= first;
+ uint cnt= 0;
+
+ while (node->next != &end_of_list)
+ {
+ if (!node->info)
+ {
+ DBUG_PRINT("list_invariants",("%s: error: NULL element in the list",
+ name));
+ return FALSE;
+ }
+ node= node->next;
+ cnt++;
+ }
+ if (last != &(node->next))
+ {
+ DBUG_PRINT("list_invariants", ("%s: error: wrong last pointer", name));
+ return FALSE;
+ }
+ if (cnt+1 != elements)
+ {
+ DBUG_PRINT("list_invariants", ("%s: error: wrong element count", name));
+ return FALSE;
+ }
+ DBUG_PRINT("list_invariants", ("%s: list is ok", name));
+ return TRUE;
+ }
+#endif // LIST_EXTRA_DEBUG
+
+protected:
+ void after(const void *info, list_node *node)
+ {
+ list_node *new_node=new list_node(info,node->next);
+ node->next=new_node;
+ elements++;
+ if (last == &(node->next))
+ last= &new_node->next;
+ }
+};
+
+
+class base_list_iterator
+{
+protected:
+ base_list *list;
+ list_node **el,**prev,*current;
+ void sublist(base_list &ls, uint elm)
+ {
+ ls.first= *el;
+ ls.last= list->last;
+ ls.elements= elm;
+ }
+public:
+ base_list_iterator()
+ :list(0), el(0), prev(0), current(0)
+ {}
+
+ base_list_iterator(base_list &list_par)
+ { init(list_par); }
+
+ inline void init(base_list &list_par)
+ {
+ list= &list_par;
+ el= &list_par.first;
+ prev= 0;
+ current= 0;
+ }
+
+ inline void *next(void)
+ {
+ prev=el;
+ current= *el;
+ el= &current->next;
+ return current->info;
+ }
+ /* Get what calling next() would return, without moving the iterator */
+ inline void *peek()
+ {
+ return (*el)->info;
+ }
+ inline void *next_fast(void)
+ {
+ list_node *tmp;
+ tmp= *el;
+ el= &tmp->next;
+ return tmp->info;
+ }
+ inline void rewind(void)
+ {
+ el= &list->first;
+ }
+ inline void *replace(const void *element)
+ { // Return old element
+ void *tmp=current->info;
+ DBUG_ASSERT(current->info != 0);
+ current->info= const_cast<void *>(element);
+ return tmp;
+ }
+ void *replace(base_list &new_list)
+ {
+ void *ret_value=current->info;
+ if (!new_list.is_empty())
+ {
+ *new_list.last=current->next;
+ current->info=new_list.first->info;
+ current->next=new_list.first->next;
+ if ((list->last == &current->next) && (new_list.elements > 1))
+ list->last= new_list.last;
+ list->elements+=new_list.elements-1;
+ }
+ return ret_value; // return old element
+ }
+ inline void remove(void) // Remove current
+ {
+ list->remove(prev);
+ el=prev;
+ current=0; // Safeguard
+ }
+ void after(const void *element) // Insert element after current
+ {
+ list->after(element,current);
+ current=current->next;
+ el= &current->next;
+ }
+ inline void **ref(void) // Get reference pointer
+ {
+ return &current->info;
+ }
+ inline bool is_last(void)
+ {
+ return el == &list->last_ref()->next;
+ }
+ inline bool at_end()
+ {
+ return current == &end_of_list;
+ }
+ friend class error_list_iterator;
+};
+
+template <class T> class List :public base_list
+{
+public:
+ inline List() :base_list() {}
+ inline List(const List<T> &tmp, MEM_ROOT *mem_root) :
+ base_list(tmp, mem_root) {}
+ inline bool push_back(T *a) { return base_list::push_back(a); }
+ inline bool push_back(T *a, MEM_ROOT *mem_root)
+ { return base_list::push_back((void*) a, mem_root); }
+ inline bool push_front(T *a) { return base_list::push_front(a); }
+ inline bool push_front(T *a, MEM_ROOT *mem_root)
+ { return base_list::push_front((void*) a, mem_root); }
+ inline T* head() {return (T*) base_list::head(); }
+ inline T** head_ref() {return (T**) base_list::head_ref(); }
+ inline T* pop() {return (T*) base_list::pop(); }
+ inline void append(List<T> *list) { base_list::append(list); }
+ inline void prepend(List<T> *list) { base_list::prepend(list); }
+ inline void disjoin(List<T> *list) { base_list::disjoin(list); }
+ inline bool add_unique(T *a, bool (*eq)(T *a, T *b))
+ { return base_list::add_unique(a, (List_eq *)eq); }
+ inline bool copy(const List<T> *list, MEM_ROOT *root)
+ { return base_list::copy(list, root); }
+ void delete_elements(void)
+ {
+ list_node *element,*next;
+ for (element=first; element != &end_of_list; element=next)
+ {
+ next=element->next;
+ delete (T*) element->info;
+ }
+ empty();
+ }
+ T *elem(uint n) { return (T*) base_list::elem(n); }
+ // Create a new list with one element
+ static List<T> *make(MEM_ROOT *mem_root, T *first)
+ {
+ List<T> *res= new (mem_root) List<T>;
+ return res == NULL || res->push_back(first, mem_root) ? NULL : res;
+ }
+
+ class Iterator;
+ using value_type= T;
+ using iterator= Iterator;
+ using const_iterator= const Iterator;
+
+ Iterator begin() const { return Iterator(first); }
+ Iterator end() const { return Iterator(); }
+
+ class Iterator
+ {
+ public:
+ using iterator_category= std::forward_iterator_tag;
+ using value_type= T;
+ using difference_type= std::ptrdiff_t;
+ using pointer= T *;
+ using reference= T &;
+
+ Iterator(list_node *p= &end_of_list) : node{p} {}
+
+ Iterator &operator++()
+ {
+ DBUG_ASSERT(node != &end_of_list);
+
+ node= node->next;
+ return *this;
+ }
+
+ T operator++(int)
+ {
+ Iterator tmp(*this);
+ operator++();
+ return tmp;
+ }
+
+ T &operator*() { return *static_cast<T *>(node->info); }
+ T *operator->() { return static_cast<T *>(node->info); }
+
+ bool operator==(const typename List<T>::iterator &rhs)
+ {
+ return node == rhs.node;
+ }
+
+ bool operator!=(const typename List<T>::iterator &rhs)
+ {
+ return node != rhs.node;
+ }
+
+ private:
+ list_node *node{&end_of_list};
+ };
+};
+
+
+template <class T> class List_iterator :public base_list_iterator
+{
+public:
+ List_iterator(List<T> &a) : base_list_iterator(a) {}
+ List_iterator() : base_list_iterator() {}
+ inline void init(List<T> &a) { base_list_iterator::init(a); }
+ inline T* operator++(int) { return (T*) base_list_iterator::next(); }
+ inline T* peek() { return (T*) base_list_iterator::peek(); }
+ inline T *replace(T *a) { return (T*) base_list_iterator::replace(a); }
+ inline T *replace(List<T> &a) { return (T*) base_list_iterator::replace(a); }
+ inline void rewind(void) { base_list_iterator::rewind(); }
+ inline void remove() { base_list_iterator::remove(); }
+ inline void after(T *a) { base_list_iterator::after(a); }
+ inline T** ref(void) { return (T**) base_list_iterator::ref(); }
+};
+
+
+template <class T> class List_iterator_fast :public base_list_iterator
+{
+protected:
+ inline T *replace(T *) { return (T*) 0; }
+ inline T *replace(List<T> &) { return (T*) 0; }
+ inline void remove(void) {}
+ inline void after(T *) {}
+ inline T** ref(void) { return (T**) 0; }
+
+public:
+ inline List_iterator_fast(List<T> &a) : base_list_iterator(a) {}
+ inline List_iterator_fast() : base_list_iterator() {}
+ inline void init(List<T> &a) { base_list_iterator::init(a); }
+ inline T* operator++(int) { return (T*) base_list_iterator::next_fast(); }
+ inline void rewind(void) { base_list_iterator::rewind(); }
+ void sublist(List<T> &list_arg, uint el_arg)
+ {
+ base_list_iterator::sublist(list_arg, el_arg);
+ }
+};
+
+
+/*
+ Bubble sort algorithm for List<T>.
+ This sort function is supposed to be used only for very short list.
+ Currently it is used for the lists of Item_equal objects and
+ for some lists in the table elimination algorithms. In both
+ cases the sorted lists are very short.
+*/
+
+template <class T>
+inline void bubble_sort(List<T> *list_to_sort,
+ int (*sort_func)(T *a, T *b, void *arg), void *arg)
+{
+ bool swap;
+ T **ref1= 0;
+ T **ref2= 0;
+ List_iterator<T> it(*list_to_sort);
+ do
+ {
+ T **last_ref= ref1;
+ T *item1= it++;
+ ref1= it.ref();
+ T *item2;
+
+ swap= FALSE;
+ while ((item2= it++) && (ref2= it.ref()) != last_ref)
+ {
+ if (sort_func(item1, item2, arg) > 0)
+ {
+ *ref1= item2;
+ *ref2= item1;
+ swap= TRUE;
+ }
+ else
+ item1= item2;
+ ref1= ref2;
+ }
+ it.rewind();
+ } while (swap);
+}
+
+
+/*
+ A simple intrusive list which automaticly removes element from list
+ on delete (for THD element)
+*/
+
+struct ilink
+{
+ struct ilink **prev,*next;
+ static void *operator new(size_t size) throw ()
+ {
+ return (void*)my_malloc(PSI_INSTRUMENT_ME,
+ (uint)size, MYF(MY_WME | MY_FAE | ME_FATAL));
+ }
+ static void operator delete(void* ptr_arg, size_t)
+ {
+ my_free(ptr_arg);
+ }
+
+ inline ilink()
+ {
+ prev=0; next=0;
+ }
+ inline void unlink()
+ {
+ /* Extra tests because element doesn't have to be linked */
+ if (prev) *prev= next;
+ if (next) next->prev=prev;
+ prev=0 ; next=0;
+ }
+ inline void assert_linked()
+ {
+ DBUG_ASSERT(prev != 0 && next != 0);
+ }
+ inline void assert_not_linked()
+ {
+ DBUG_ASSERT(prev == 0 && next == 0);
+ }
+ virtual ~ilink() { unlink(); } /*lint -e1740 */
+};
+
+
+/* Needed to be able to have an I_List of char* strings in mysqld.cc. */
+
+class i_string: public ilink
+{
+public:
+ const char* ptr;
+ i_string():ptr(0) { }
+ i_string(const char* s) : ptr(s) {}
+};
+
+/* needed for linked list of two strings for replicate-rewrite-db */
+class i_string_pair: public ilink
+{
+public:
+ const char* key;
+ const char* val;
+ i_string_pair():key(0),val(0) { }
+ i_string_pair(const char* key_arg, const char* val_arg) :
+ key(key_arg),val(val_arg) {}
+};
+
+
+template <class T> class I_List_iterator;
+
+
+class base_ilist
+{
+ struct ilink *first;
+ struct ilink last;
+public:
+ inline void empty() { first= &last; last.prev= &first; }
+ base_ilist() { empty(); }
+ inline bool is_empty() { return first == &last; }
+ // Returns true if p is the last "real" object in the list,
+ // i.e. p->next points to the sentinel.
+ inline bool is_last(ilink *p) { return p->next == NULL || p->next == &last; }
+ inline void append(ilink *a)
+ {
+ first->prev= &a->next;
+ a->next=first; a->prev= &first; first=a;
+ }
+ inline void push_back(ilink *a)
+ {
+ *last.prev= a;
+ a->next= &last;
+ a->prev= last.prev;
+ last.prev= &a->next;
+ }
+ inline struct ilink *get()
+ {
+ struct ilink *first_link=first;
+ if (first_link == &last)
+ return 0;
+ first_link->unlink(); // Unlink from list
+ return first_link;
+ }
+ inline struct ilink *head()
+ {
+ return (first != &last) ? first : 0;
+ }
+
+ /**
+ Moves list elements to new owner, and empties current owner (i.e. this).
+
+ @param[in,out] new_owner The new owner of the list elements.
+ Should be empty in input.
+ */
+
+ void move_elements_to(base_ilist *new_owner)
+ {
+ DBUG_ASSERT(new_owner->is_empty());
+ new_owner->first= first;
+ new_owner->last= last;
+ empty();
+ }
+
+ friend class base_ilist_iterator;
+ private:
+ /*
+ We don't want to allow copying of this class, as that would give us
+ two list heads containing the same elements.
+ So we declare, but don't define copy CTOR and assignment operator.
+ */
+ base_ilist(const base_ilist&);
+ void operator=(const base_ilist&);
+};
+
+
+class base_ilist_iterator
+{
+ base_ilist *list;
+ struct ilink **el,*current;
+public:
+ base_ilist_iterator(base_ilist &list_par) :list(&list_par),
+ el(&list_par.first),current(0) {}
+ void *next(void)
+ {
+ /* This is coded to allow push_back() while iterating */
+ current= *el;
+ if (current == &list->last) return 0;
+ el= &current->next;
+ return current;
+ }
+};
+
+
+template <class T>
+class I_List :private base_ilist
+{
+public:
+ I_List() :base_ilist() {}
+ inline bool is_last(T *p) { return base_ilist::is_last(p); }
+ inline void empty() { base_ilist::empty(); }
+ inline bool is_empty() { return base_ilist::is_empty(); }
+ inline void append(T* a) { base_ilist::append(a); }
+ inline void push_back(T* a) { base_ilist::push_back(a); }
+ inline T* get() { return (T*) base_ilist::get(); }
+ inline T* head() { return (T*) base_ilist::head(); }
+ inline void move_elements_to(I_List<T>* new_owner) {
+ base_ilist::move_elements_to(new_owner);
+ }
+#ifndef _lint
+ friend class I_List_iterator<T>;
+#endif
+};
+
+
+template <class T> class I_List_iterator :public base_ilist_iterator
+{
+public:
+ I_List_iterator(I_List<T> &a) : base_ilist_iterator(a) {}
+ inline T* operator++(int) { return (T*) base_ilist_iterator::next(); }
+};
+
+/**
+ Make a deep copy of each list element.
+
+ @note A template function and not a template method of class List
+ is employed because of explicit template instantiation:
+ in server code there are explicit instantiations of List<T> and
+ an explicit instantiation of a template requires that any method
+ of the instantiated class used in the template can be resolved.
+ Evidently not all template arguments have clone() method with
+ the right signature.
+
+ @return You must query the error state in THD for out-of-memory
+ situation after calling this function.
+*/
+
+template <typename T>
+inline
+void
+list_copy_and_replace_each_value(List<T> &list, MEM_ROOT *mem_root)
+{
+ /* Make a deep copy of each element */
+ List_iterator<T> it(list);
+ T *el;
+ while ((el= it++))
+ it.replace(el->clone(mem_root));
+}
+
+void free_list(I_List <i_string> *list);
+
+#endif // INCLUDES_MYSQL_SQL_LIST_H