summaryrefslogtreecommitdiffstats
path: root/storage/innobase/que/que0que.cc
diff options
context:
space:
mode:
Diffstat (limited to 'storage/innobase/que/que0que.cc')
-rw-r--r--storage/innobase/que/que0que.cc708
1 files changed, 708 insertions, 0 deletions
diff --git a/storage/innobase/que/que0que.cc b/storage/innobase/que/que0que.cc
new file mode 100644
index 00000000..d910ee2a
--- /dev/null
+++ b/storage/innobase/que/que0que.cc
@@ -0,0 +1,708 @@
+/*****************************************************************************
+
+Copyright (c) 1996, 2016, Oracle and/or its affiliates. All Rights Reserved.
+Copyright (c) 2017, 2022, 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 Street, Fifth Floor, Boston, MA 02110-1335 USA
+
+*****************************************************************************/
+
+/**************************************************//**
+@file que/que0que.cc
+Query graph
+
+Created 5/27/1996 Heikki Tuuri
+*******************************************************/
+
+#include "que0que.h"
+#include "trx0trx.h"
+#include "trx0roll.h"
+#include "row0undo.h"
+#include "row0ins.h"
+#include "row0upd.h"
+#include "row0sel.h"
+#include "row0purge.h"
+#include "dict0crea.h"
+#include "log0log.h"
+#include "eval0proc.h"
+
+#define QUE_MAX_LOOPS_WITHOUT_CHECK 16
+
+/* Short introduction to query graphs
+ ==================================
+
+A query graph consists of nodes linked to each other in various ways. The
+execution starts at que_run_threads() which takes a que_thr_t parameter.
+que_thr_t contains two fields that control query graph execution: run_node
+and prev_node. run_node is the next node to execute and prev_node is the
+last node executed.
+
+Each node has a pointer to a 'next' statement, i.e., its brother, and a
+pointer to its parent node. The next pointer is NULL in the last statement
+of a block.
+
+Loop nodes contain a link to the first statement of the enclosed statement
+list. While the loop runs, que_thr_step() checks if execution to the loop
+node came from its parent or from one of the statement nodes in the loop. If
+it came from the parent of the loop node it starts executing the first
+statement node in the loop. If it came from one of the statement nodes in
+the loop, then it checks if the statement node has another statement node
+following it, and runs it if so.
+
+To signify loop ending, the loop statements (see e.g. while_step()) set
+que_thr_t->run_node to the loop node's parent node. This is noticed on the
+next call of que_thr_step() and execution proceeds to the node pointed to by
+the loop node's 'next' pointer.
+
+For example, the code:
+
+X := 1;
+WHILE X < 5 LOOP
+ X := X + 1;
+ X := X + 1;
+X := 5
+
+will result in the following node hierarchy, with the X-axis indicating
+'next' links and the Y-axis indicating parent/child links:
+
+A - W - A
+ |
+ |
+ A - A
+
+A = assign_node_t, W = while_node_t. */
+
+/* How a stored procedure containing COMMIT or ROLLBACK commands
+is executed?
+
+The commit or rollback can be seen as a subprocedure call.
+
+When the transaction starts to handle a rollback or commit.
+It builds a query graph which, when executed, will roll back
+or commit the incomplete transaction. The transaction
+may be moved to the TRX_QUE_ROLLING_BACK state.
+If specified, the SQL cursors opened by the transaction are closed.
+When the execution of the graph completes, it is like returning
+from a subprocedure: the query thread which requested the operation
+starts running again. */
+
+/***********************************************************************//**
+Creates a query graph fork node.
+@return own: fork node */
+que_fork_t*
+que_fork_create(mem_heap_t *heap)
+{
+ que_fork_t* fork;
+
+ ut_ad(heap);
+
+ fork = static_cast<que_fork_t*>(mem_heap_zalloc(heap, sizeof(*fork)));
+
+ fork->heap = heap;
+
+ fork->common.type = QUE_NODE_FORK;
+
+ fork->state = QUE_FORK_COMMAND_WAIT;
+
+ fork->graph = fork;
+
+ UT_LIST_INIT(fork->thrs, &que_thr_t::thrs);
+
+ return(fork);
+}
+
+
+/** Creates a query graph thread node.
+@param[in] parent parent node, i.e., a fork node
+@param[in] heap memory heap where created
+@param[in] prebuilt row prebuilt structure
+@return own: query thread node */
+que_thr_t*
+que_thr_create(
+ que_fork_t* parent,
+ mem_heap_t* heap,
+ row_prebuilt_t* prebuilt)
+{
+ que_thr_t* thr;
+
+ ut_ad(parent != NULL);
+ ut_ad(heap != NULL);
+
+ thr = static_cast<que_thr_t*>(mem_heap_zalloc(heap, sizeof(*thr)));
+
+ thr->graph = parent->graph;
+
+ thr->common.parent = parent;
+
+ thr->common.type = QUE_NODE_THR;
+
+ thr->prebuilt = prebuilt;
+
+ UT_LIST_ADD_LAST(parent->thrs, thr);
+
+ return(thr);
+}
+
+/**********************************************************************//**
+Inits a query thread for a command. */
+UNIV_INLINE
+void
+que_thr_init_command(
+/*=================*/
+ que_thr_t* thr) /*!< in: query thread */
+{
+ thr->run_node = thr;
+ thr->prev_node = thr->common.parent;
+ thr->state = QUE_THR_RUNNING;
+}
+
+/**********************************************************************//**
+Round robin scheduler.
+@return a query thread of the graph moved to QUE_THR_RUNNING state, or
+NULL; the query thread should be executed by que_run_threads by the
+caller */
+que_thr_t*
+que_fork_scheduler_round_robin(
+/*===========================*/
+ que_fork_t* fork, /*!< in: a query fork */
+ que_thr_t* thr) /*!< in: current pos */
+{
+ fork->trx->mutex_lock();
+
+ /* If no current, start first available. */
+ if (thr == NULL) {
+ thr = UT_LIST_GET_FIRST(fork->thrs);
+ } else {
+ thr = UT_LIST_GET_NEXT(thrs, thr);
+ }
+
+ if (thr) {
+
+ fork->state = QUE_FORK_ACTIVE;
+
+ fork->last_sel_node = NULL;
+ ut_ad(thr->state == QUE_THR_COMPLETED);
+ que_thr_init_command(thr);
+ }
+
+ fork->trx->mutex_unlock();
+
+ return(thr);
+}
+
+/**********************************************************************//**
+Starts execution of a command in a query fork. Picks a query thread which
+is not in the QUE_THR_RUNNING state and moves it to that state. If none
+can be chosen, a situation which may arise in parallelized fetches, NULL
+is returned.
+@return a query thread of the graph moved to QUE_THR_RUNNING state, or
+NULL; the query thread should be executed by que_run_threads by the
+caller */
+que_thr_t*
+que_fork_start_command(
+/*===================*/
+ que_fork_t* fork) /*!< in: a query fork */
+{
+ fork->state = QUE_FORK_ACTIVE;
+
+ fork->last_sel_node = NULL;
+
+ que_thr_t* thr = UT_LIST_GET_FIRST(fork->thrs);
+
+ if (thr) {
+ ut_ad(thr->state == QUE_THR_COMPLETED);
+ que_thr_init_command(thr);
+ }
+
+ return(thr);
+}
+
+/**********************************************************************//**
+Calls que_graph_free_recursive for statements in a statement list. */
+static
+void
+que_graph_free_stat_list(
+/*=====================*/
+ que_node_t* node) /*!< in: first query graph node in the list */
+{
+ while (node) {
+ que_node_t* next = que_node_get_next(node);
+ que_graph_free_recursive(node);
+ node = next;
+ }
+}
+
+/**********************************************************************//**
+Frees a query graph, but not the heap where it was created. Does not free
+explicit cursor declarations, they are freed in que_graph_free. */
+void
+que_graph_free_recursive(
+/*=====================*/
+ que_node_t* node) /*!< in: query graph node */
+{
+ que_fork_t* fork;
+ que_thr_t* thr;
+ undo_node_t* undo;
+ sel_node_t* sel;
+ ins_node_t* ins;
+ upd_node_t* upd;
+ tab_node_t* cre_tab;
+ ind_node_t* cre_ind;
+ purge_node_t* purge;
+
+ if (node == NULL) {
+ return;
+ }
+
+ switch (que_node_get_type(node)) {
+
+ case QUE_NODE_FORK:
+ fork = static_cast<que_fork_t*>(node);
+
+ thr = UT_LIST_GET_FIRST(fork->thrs);
+
+ while (thr) {
+ que_graph_free_recursive(thr);
+
+ thr = UT_LIST_GET_NEXT(thrs, thr);
+ }
+
+ break;
+ case QUE_NODE_THR:
+ thr = static_cast<que_thr_t*>(node);
+ que_graph_free_recursive(thr->child);
+ break;
+ case QUE_NODE_UNDO:
+
+ undo = static_cast<undo_node_t*>(node);
+
+ mem_heap_free(undo->heap);
+
+ break;
+ case QUE_NODE_SELECT:
+
+ sel = static_cast<sel_node_t*>(node);
+
+ sel_node_free_private(sel);
+
+ break;
+ case QUE_NODE_INSERT:
+ ins = static_cast<ins_node_t*>(node);
+
+ que_graph_free_recursive(ins->select);
+ ins->~ins_node_t();
+ break;
+ case QUE_NODE_PURGE:
+ purge = static_cast<purge_node_t*>(node);
+
+ mem_heap_free(purge->heap);
+
+ purge->~purge_node_t();
+ break;
+
+ case QUE_NODE_UPDATE:
+ upd = static_cast<upd_node_t*>(node);
+
+ que_graph_free_recursive(upd->cascade_node);
+ ut_free(upd->pcur->old_rec_buf);
+ upd->pcur->old_rec_buf = NULL;
+
+ if (upd->cascade_heap) {
+ mem_heap_free(upd->cascade_heap);
+ upd->cascade_heap = NULL;
+ }
+
+ que_graph_free_recursive(upd->select);
+ upd->select = NULL;
+
+ if (upd->heap != NULL) {
+ mem_heap_free(upd->heap);
+ upd->heap = NULL;
+ }
+
+ break;
+ case QUE_NODE_CREATE_TABLE:
+ cre_tab = static_cast<tab_node_t*>(node);
+
+ que_graph_free_recursive(cre_tab->tab_def);
+ que_graph_free_recursive(cre_tab->col_def);
+ que_graph_free_recursive(cre_tab->v_col_def);
+
+ mem_heap_free(cre_tab->heap);
+
+ break;
+ case QUE_NODE_CREATE_INDEX:
+ cre_ind = static_cast<ind_node_t*>(node);
+
+ que_graph_free_recursive(cre_ind->ind_def);
+ que_graph_free_recursive(cre_ind->field_def);
+
+ mem_heap_free(cre_ind->heap);
+
+ break;
+ case QUE_NODE_PROC:
+ que_graph_free_stat_list(((proc_node_t*) node)->stat_list);
+
+ break;
+ case QUE_NODE_IF:
+ que_graph_free_stat_list(((if_node_t*) node)->stat_list);
+ que_graph_free_stat_list(((if_node_t*) node)->else_part);
+ que_graph_free_stat_list(((if_node_t*) node)->elsif_list);
+
+ break;
+ case QUE_NODE_ELSIF:
+ que_graph_free_stat_list(((elsif_node_t*) node)->stat_list);
+
+ break;
+ case QUE_NODE_WHILE:
+ que_graph_free_stat_list(((while_node_t*) node)->stat_list);
+
+ break;
+ case QUE_NODE_FOR:
+ que_graph_free_stat_list(((for_node_t*) node)->stat_list);
+
+ break;
+
+ case QUE_NODE_ASSIGNMENT:
+ case QUE_NODE_EXIT:
+ case QUE_NODE_RETURN:
+ case QUE_NODE_COMMIT:
+ case QUE_NODE_ROLLBACK:
+ case QUE_NODE_LOCK:
+ case QUE_NODE_FUNC:
+ case QUE_NODE_ORDER:
+ case QUE_NODE_ROW_PRINTF:
+ case QUE_NODE_OPEN:
+ case QUE_NODE_FETCH:
+ /* No need to do anything */
+
+ break;
+ default:
+ ut_error;
+ }
+}
+
+/**********************************************************************//**
+Frees a query graph. */
+void
+que_graph_free(
+/*===========*/
+ que_t* graph) /*!< in: query graph; we assume that the memory
+ heap where this graph was created is private
+ to this graph: if not, then use
+ que_graph_free_recursive and free the heap
+ afterwards! */
+{
+ ut_ad(graph);
+
+ if (graph->sym_tab) {
+ /* The following call frees dynamic memory allocated
+ for variables etc. during execution. Frees also explicit
+ cursor definitions. */
+
+ sym_tab_free_private(graph->sym_tab);
+ }
+
+ if (graph->info) {
+ pars_info_free(graph->info);
+ }
+
+ que_graph_free_recursive(graph);
+
+ mem_heap_free(graph->heap);
+}
+
+/****************************************************************//**
+Performs an execution step on a thr node.
+@return query thread to run next, or NULL if none */
+static
+que_thr_t*
+que_thr_node_step(
+/*==============*/
+ que_thr_t* thr) /*!< in: query thread where run_node must
+ be the thread node itself */
+{
+ ut_ad(thr->run_node == thr);
+
+ if (thr->prev_node == thr->common.parent) {
+ /* If control to the node came from above, it is just passed
+ on */
+
+ thr->run_node = thr->child;
+
+ return(thr);
+ }
+
+ trx_t *trx= thr->graph->trx;
+ trx->mutex_lock();
+
+ if (!trx->lock.wait_thr && thr->graph->state == QUE_FORK_ACTIVE) {
+ thr->state = QUE_THR_COMPLETED;
+ thr = NULL;
+ }
+
+ trx->mutex_unlock();
+ return(thr);
+}
+
+/****************************************************************//**
+Get the first containing loop node (e.g. while_node_t or for_node_t) for the
+given node, or NULL if the node is not within a loop.
+@return containing loop node, or NULL. */
+que_node_t*
+que_node_get_containing_loop_node(
+/*==============================*/
+ que_node_t* node) /*!< in: node */
+{
+ ut_ad(node);
+
+ for (;;) {
+ ulint type;
+
+ node = que_node_get_parent(node);
+
+ if (!node) {
+ break;
+ }
+
+ type = que_node_get_type(node);
+
+ if ((type == QUE_NODE_FOR) || (type == QUE_NODE_WHILE)) {
+ break;
+ }
+ }
+
+ return(node);
+}
+
+/**********************************************************************//**
+Performs an execution step of an open or close cursor statement node.
+@param thr query thread */
+static void open_step(que_thr_t *thr)
+{
+ open_node_t *node= static_cast<open_node_t*>(thr->run_node);
+ ut_ad(que_node_get_type(node) == QUE_NODE_OPEN);
+ sel_node_t *sel_node= node->cursor_def;
+
+ if (node->op_type == ROW_SEL_OPEN_CURSOR)
+ sel_node->state= SEL_NODE_OPEN;
+ else
+ {
+ ut_ad(sel_node->state != SEL_NODE_CLOSED);
+ sel_node->state= SEL_NODE_CLOSED;
+ }
+
+ thr->run_node= que_node_get_parent(node);
+}
+
+/**********************************************************************//**
+Performs an execution step on a query thread.
+@return query thread to run next: it may differ from the input
+parameter if, e.g., a subprocedure call is made */
+UNIV_INLINE
+que_thr_t*
+que_thr_step(
+/*=========*/
+ que_thr_t* thr) /*!< in: query thread */
+{
+ que_node_t* node;
+ que_thr_t* old_thr;
+ trx_t* trx;
+ ulint type;
+
+ trx = thr_get_trx(thr);
+
+ ut_ad(thr->state == QUE_THR_RUNNING);
+ ut_a(trx->error_state == DB_SUCCESS);
+
+ thr->resource++;
+
+ node = thr->run_node;
+ type = que_node_get_type(node);
+
+ old_thr = thr;
+
+ if (type & QUE_NODE_CONTROL_STAT) {
+ if ((thr->prev_node != que_node_get_parent(node))
+ && que_node_get_next(thr->prev_node)) {
+
+ /* The control statements, like WHILE, always pass the
+ control to the next child statement if there is any
+ child left */
+
+ thr->run_node = que_node_get_next(thr->prev_node);
+
+ } else if (type == QUE_NODE_IF) {
+ if_step(thr);
+ } else if (type == QUE_NODE_FOR) {
+ for_step(thr);
+ } else if (type == QUE_NODE_PROC) {
+ if (thr->prev_node == que_node_get_parent(node)) {
+ trx->last_sql_stat_start.least_undo_no
+ = trx->undo_no;
+ }
+
+ proc_step(thr);
+ } else if (type == QUE_NODE_WHILE) {
+ while_step(thr);
+ } else {
+ ut_error;
+ }
+ } else if (type == QUE_NODE_ASSIGNMENT) {
+ assign_step(thr);
+ } else if (type == QUE_NODE_SELECT) {
+ thr = row_sel_step(thr);
+ } else if (type == QUE_NODE_INSERT) {
+ trx_start_if_not_started_xa(thr_get_trx(thr), true);
+ thr = row_ins_step(thr);
+ } else if (type == QUE_NODE_UPDATE) {
+ trx_start_if_not_started_xa(thr_get_trx(thr), true);
+ thr = row_upd_step(thr);
+ } else if (type == QUE_NODE_FETCH) {
+ thr = fetch_step(thr);
+ } else if (type == QUE_NODE_OPEN) {
+ open_step(thr);
+ } else if (type == QUE_NODE_FUNC) {
+ proc_eval_step(thr);
+
+ } else if (type == QUE_NODE_LOCK) {
+
+ ut_error;
+ } else if (type == QUE_NODE_THR) {
+ thr = que_thr_node_step(thr);
+ } else if (type == QUE_NODE_COMMIT) {
+ thr = trx_commit_step(thr);
+ } else if (type == QUE_NODE_UNDO) {
+ thr = row_undo_step(thr);
+ } else if (type == QUE_NODE_PURGE) {
+ thr = row_purge_step(thr);
+ } else if (type == QUE_NODE_RETURN) {
+ thr = return_step(thr);
+ } else if (type == QUE_NODE_EXIT) {
+ thr = exit_step(thr);
+ } else if (type == QUE_NODE_ROLLBACK) {
+ thr = trx_rollback_step(thr);
+ } else if (type == QUE_NODE_CREATE_TABLE) {
+ thr = dict_create_table_step(thr);
+ } else if (type == QUE_NODE_CREATE_INDEX) {
+ thr = dict_create_index_step(thr);
+ } else if (type == QUE_NODE_ROW_PRINTF) {
+ thr = row_printf_step(thr);
+ } else {
+ ut_error;
+ }
+
+ if (type == QUE_NODE_EXIT) {
+ old_thr->prev_node = que_node_get_containing_loop_node(node);
+ } else {
+ old_thr->prev_node = node;
+ }
+
+ if (thr) {
+ ut_a(thr_get_trx(thr)->error_state == DB_SUCCESS);
+ }
+
+ return(thr);
+}
+
+/**********************************************************************//**
+Run a query thread until it finishes or encounters e.g. a lock wait. */
+static
+void
+que_run_threads_low(
+/*================*/
+ que_thr_t* thr) /*!< in: query thread */
+{
+ ut_ad(thr->state == QUE_THR_RUNNING);
+
+ /* cumul_resource counts how much resources the OS thread (NOT the
+ query thread) has spent in this function */
+
+ for (trx_t* trx = thr_get_trx(thr);;) {
+ ut_ad(!trx->mutex_is_owner());
+ ut_a(trx->error_state == DB_SUCCESS);
+ /* Check that there is enough space in the log to accommodate
+ possible log entries by this query step; if the operation can
+ touch more than about 4 pages, checks must be made also within
+ the query step! */
+
+ log_free_check();
+
+ /* Perform the actual query step: note that the query thread
+ may change if, e.g., a subprocedure call is made */
+
+ que_thr_t* next_thr = que_thr_step(thr);
+ ut_ad(trx == thr_get_trx(thr));
+ if (!next_thr) {
+ return;
+ }
+
+ ut_a(next_thr == thr);
+ }
+}
+
+/**********************************************************************//**
+Run a query thread. Handles lock waits. */
+void
+que_run_threads(
+/*============*/
+ que_thr_t* thr) /*!< in: query thread */
+{
+ trx_t* trx = thr->graph->trx;
+loop:
+ ut_a(trx->error_state == DB_SUCCESS);
+ que_run_threads_low(thr);
+
+ if (thr->state != QUE_THR_COMPLETED) {
+ if (trx->lock.wait_thr) {
+ ut_ad(trx->id);
+ if (lock_wait(thr) == DB_SUCCESS) {
+ goto loop;
+ }
+ } else if (trx->error_state == DB_SUCCESS) {
+ goto loop;
+ }
+ }
+}
+
+/*********************************************************************//**
+Evaluate the given SQL.
+@return error code or DB_SUCCESS */
+dberr_t
+que_eval_sql(
+/*=========*/
+ pars_info_t* info, /*!< in: info struct, or NULL */
+ const char* sql, /*!< in: SQL string */
+ trx_t* trx) /*!< in: trx */
+{
+ que_thr_t* thr;
+ que_t* graph;
+
+ DBUG_ENTER("que_eval_sql");
+ DBUG_PRINT("que_eval_sql", ("query: %s", sql));
+
+ ut_a(trx->error_state == DB_SUCCESS);
+
+ graph = pars_sql(info, sql);
+
+ graph->trx = trx;
+ trx->graph = NULL;
+
+ ut_a(thr = que_fork_start_command(graph));
+
+ que_run_threads(thr);
+
+ que_graph_free(graph);
+
+ DBUG_RETURN(trx->error_state);
+}