dbsql/src/cg_delete.c

501 lines
14 KiB
C
Raw Normal View History

2007-03-10 19:04:07 +00:00
/*-
* DBSQL - A SQL database engine.
*
* Copyright (C) 2007 The DBSQL Group, Inc. - All rights reserved.
*
* This library 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; either
* version 2.1 of the License, or (at your option) any later version.
*
* There are special exceptions to the terms and conditions of the GPL as it
* is applied to this software. View the full text of the exception in file
* LICENSE_EXCEPTIONS in the directory of this software distribution.
*
* This library 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.
*
* http://creativecommons.org/licenses/GPL/2.0/
*
* $Id: cg_delete.c 7 2007-02-03 13:34:17Z gburd $
*/
/*
* This file contains C code routines that are called by the parser
* to handle DELETE FROM statements.
*/
#include "dbsql_config.h"
#include "dbsql_int.h"
/*
* __src_list_lookup --
* Look up every table that is named in 'src'. If any table is not found,
* add an error message to parse->zErrMsg and return NULL. If all tables
* are found, return a pointer to the last table.
*
* PUBLIC: table_t *__src_list_lookup __P((parser_t *, src_list_t *));
*/
table_t *
__src_list_lookup(parser, src)
parser_t *parser;
src_list_t *src;
{
table_t *table = 0;
int i;
for (i = 0; i < src->nSrc; i++) {
const char *tb_name = src->a[i].zName;
const char *db_name = src->a[i].zDatabase;
table = __locate_table(parser, tb_name, db_name);
src->a[i].pTab = table;
}
return table;
}
/*
* __is_table_read_only --
* Check to make sure the given table is writable. If it is not
* writable, generate an error message and return 1. If it is
* writable return 0;
*
* PUBLIC: int __is_table_read_only __P((parser_t *, table_t *, int));
*/
int
__is_table_read_only(parser, table, views_ok)
parser_t *parser;
table_t *table;
int views_ok;
{
if (table->readOnly) {
__error_msg(parser, "table %s may not be modified",
table->zName);
return 1;
}
if (!views_ok && table->pSelect) {
__error_msg(parser, "cannot modify %s because it is a view",
table->zName);
return 1;
}
return 0;
}
/*
* __delete_from --
* Process a DELETE FROM statement.
*
* PUBLIC: void __delete_from __P((parser_t *, src_list_t *, expr_t *));
*
* parser The parser context
* list The table from which we should delete things
* where The WHERE clause. May be null.
*/
void __delete_from(parser, list, where)
parser_t *parser;
src_list_t *list;
expr_t *where;
{
vdbe_t *v; /* The virtual database engine */
table_t *table; /* The table from which records will be deleted */
const char *name; /* Name of database holding 'table' */
int end, addr; /* A couple addresses of generated code */
int i; /* Loop counter */
where_info_t *info; /* Information about the WHERE clause */
index_t *idx; /* For looping over indices of the table */
int cur; /* VDBE Cursor number for 'table' */
DBSQL *dbp; /* Main database structure */
int view; /* True if attempting to delete from a view */
auth_context_t context;/* Authorization context */
int row_triggers_exist = 0;/* True if any triggers exist */
int before_triggers;/* True if there are BEFORE triggers */
int after_triggers; /* True if there are AFTER triggers */
int old_idx = -1; /* Cursor for the OLD table of AFTER triggers */
context.pParse = 0;
if (parser->nErr || parser->rc == ENOMEM) {
list = 0;
goto delete_from_cleanup;
}
dbp = parser->db;
DBSQL_ASSERT(list->nSrc == 1);
/*
* Locate the table which we want to delete. This table has to be
* put in an src_list_t structure because some of the subroutines we
* will be calling are designed to work with multiple tables and expect
* an src_list_t* parameter instead of just a table_t* parameter.
*/
table = __src_list_lookup(parser, list);
if (table == 0)
goto delete_from_cleanup;
before_triggers = __triggers_exist(parser, table->pTrigger,
TK_DELETE, TK_BEFORE, TK_ROW, 0);
after_triggers = __triggers_exist(parser, table->pTrigger,
TK_DELETE, TK_AFTER, TK_ROW, 0);
row_triggers_exist = (before_triggers || after_triggers);
view = (table->pSelect != 0);
if (__is_table_read_only(parser, table, before_triggers)) {
goto delete_from_cleanup;
}
DBSQL_ASSERT(table->iDb < dbp->nDb);
name = dbp->aDb[table->iDb].zName;
if (__auth_check(parser, DBSQL_DELETE, table->zName, 0, name)) {
goto delete_from_cleanup;
}
/*
* If pTab is really a view, make sure it has been initialized.
*/
if (view && __view_get_column_names(parser, table)) {
goto delete_from_cleanup;
}
/*
* Allocate a cursor used to store the old.* data for a trigger.
*/
if (row_triggers_exist) {
old_idx = parser->nTab++;
}
/*
* Resolve the column names in all the expressions.
*/
DBSQL_ASSERT(list->nSrc == 1);
cur = list->a[0].iCursor = parser->nTab++;
if (where) {
if (__expr_resolve_ids(parser, list, 0, where)) {
goto delete_from_cleanup;
}
if (__expr_check(parser, where, 0, 0)) {
goto delete_from_cleanup;
}
}
/*
* Start the view context.
*/
if (view) {
__auth_context_push(parser, &context, table->zName);
}
/*
* Begin generating code.
*/
v = __parser_get_vdbe(parser);
if (v == 0) {
goto delete_from_cleanup;
}
__vdbe_prepare_write(parser, row_triggers_exist, table->iDb);
/*
* If we are trying to delete from a view, construct that view into
* a temporary table.
*/
if (view) {
select_t *vsel = __select_dup(table->pSelect);
__select(parser, vsel, SRT_TempTable, cur, 0, 0, 0);
__select_delete(vsel);
}
/*
* Initialize the counter of the number of rows deleted, if
* we are counting rows.
*/
if (dbp->flags & DBSQL_CountRows) {
__vdbe_add_op(v, OP_Integer, 0, 0);
}
/*
* Special case: A DELETE without a WHERE clause deletes everything.
* It is easier just to erase the whole table. Note, however, that
* this means that the row change count will be incorrect.
*/
if (where == 0 && !row_triggers_exist) {
if (dbp->flags & DBSQL_CountRows) {
/*
* If counting rows deleted, just count the total
* number of entries in the table.
*/
int end_of_loop = __vdbe_make_label(v);
int addr;
if (!view) {
__vdbe_add_op(v, OP_Integer, table->iDb, 0);
__vdbe_add_op(v, OP_OpenRead, cur,
table->tnum);
}
__vdbe_add_op(v, OP_Rewind, cur,
(__vdbe_current_addr(v) + 2));
addr = __vdbe_add_op(v, OP_AddImm, 1, 0);
__vdbe_add_op(v, OP_Next, cur, addr);
__vdbe_resolve_label(v, end_of_loop);
__vdbe_add_op(v, OP_Close, cur, 0);
}
if (!view) {
__vdbe_add_op(v, OP_Clear, table->tnum, table->iDb);
for (idx = table->pIndex; idx; idx = idx->pNext) {
__vdbe_add_op(v, OP_Clear, idx->tnum,
idx->iDb);
}
}
} else {
/*
* The usual case: There is a WHERE clause so we have to
* scan through the table and pick which records to delete.
*/
/*
* Begin the database scan.
*/
info = __where_begin(parser, list, where, 1, 0);
if (info == 0)
goto delete_from_cleanup;
/*
* Remember the key of every item to be deleted.
*/
__vdbe_add_op(v, OP_ListWrite, 0, 0);
if (dbp->flags & DBSQL_CountRows) {
__vdbe_add_op(v, OP_AddImm, 1, 0);
}
/*
* End the database scan loop.
*/
__where_end(info);
/*
* Open the pseudo-table used to store OLD if there are
* triggers.
*/
if (row_triggers_exist) {
__vdbe_add_op(v, OP_OpenPseudo, old_idx, 0);
}
/*
* Delete every item whose key was written to the list
* during the database scan. We have to delete items
* after the scan is complete because deleting an item
* can change the scan order.
*/
__vdbe_add_op(v, OP_ListRewind, 0, 0);
end = __vdbe_make_label(v);
/*
* This is the beginning of the delete loop when there are
* row triggers.
*/
if (row_triggers_exist) {
addr = __vdbe_add_op(v, OP_ListRead, 0, end);
__vdbe_add_op(v, OP_Dup, 0, 0);
if (!view) {
__vdbe_add_op(v, OP_Integer, table->iDb, 0);
__vdbe_add_op(v, OP_OpenRead, cur,
table->tnum);
}
__vdbe_add_op(v, OP_MoveTo, cur, 0);
__vdbe_add_op(v, OP_Recno, cur, 0);
__vdbe_add_op(v, OP_RowData, cur, 0);
__vdbe_add_op(v, OP_PutIntKey, old_idx, 0);
if (!view) {
__vdbe_add_op(v, OP_Close, cur, 0);
}
__code_row_trigger(parser, TK_DELETE, 0, TK_BEFORE,
table, -1, old_idx,
((parser->trigStack) ?
parser->trigStack->orconf :
OE_Default), addr);
}
if (!view) {
/*
* Open cursors for the table we are deleting from
* and all its indices. If there are row triggers,
* this happens inside the OP_ListRead loop because
* the cursor have to all be closed before the trigger
* fires. If there are no row triggers, the cursors
* are opened only once on the outside the loop.
*/
parser->nTab = cur + 1;
__vdbe_add_op(v, OP_Integer, table->iDb, 0);
__vdbe_add_op(v, OP_OpenWrite, cur, table->tnum);
for (i = 1, idx = table->pIndex; idx;
i++, idx = idx->pNext) {
__vdbe_add_op(v, OP_Integer, idx->iDb, 0);
__vdbe_add_op(v, OP_OpenWrite, parser->nTab++,
idx->tnum);
}
/*
* This is the beginning of the delete loop when
* there are no row triggers.
*/
if (!row_triggers_exist) {
addr = __vdbe_add_op(v, OP_ListRead, 0, end);
}
/*
* Delete the row.
*/
__generate_row_delete(dbp, v, table, cur,
(parser->trigStack == 0));
}
/*
* If there are row triggers, close all cursors then invoke
* the AFTER triggers.
*/
if (row_triggers_exist) {
if (!view) {
for(i = 1, idx = table->pIndex; idx;
i++, idx = idx->pNext) {
__vdbe_add_op(v, OP_Close, cur + i,
idx->tnum);
}
__vdbe_add_op(v, OP_Close, cur, 0);
}
__code_row_trigger(parser, TK_DELETE, 0, TK_AFTER,
table, -1, old_idx,
((parser->trigStack) ?
parser->trigStack->orconf :
OE_Default), addr);
}
/*
* End of the delete loop.
*/
__vdbe_add_op(v, OP_Goto, 0, addr);
__vdbe_resolve_label(v, end);
__vdbe_add_op(v, OP_ListReset, 0, 0);
/*
* Close the cursors after the loop if there are no row
* triggers.
*/
if (!row_triggers_exist) {
for (i = 1, idx = table->pIndex; idx;
i++, idx = idx->pNext) {
__vdbe_add_op(v, OP_Close, cur + i, idx->tnum);
}
__vdbe_add_op(v, OP_Close, cur, 0);
parser->nTab = cur;
}
}
__vdbe_conclude_write(parser);
/*
* Return the number of rows that were deleted.
*/
if (dbp->flags & DBSQL_CountRows) {
__vdbe_add_op(v, OP_ColumnName, 0, 0);
__vdbe_change_p3(v, -1, "rows deleted", P3_STATIC);
__vdbe_add_op(v, OP_Callback, 1, 0);
}
delete_from_cleanup:
__auth_context_pop(&context);
__src_list_delete(list);
__expr_delete(where);
return;
}
/*
* __generate_row_delete --
* This routine generates VDBE code that causes a single row of a
* single table to be deleted.
*
* The VDBE must be in a particular state when this routine is called.
* These are the requirements:
*
* 1. A read/write cursor pointing to 'table', the table containing
* the row to be deleted, must be opened as cursor number "base".
*
* 2. Read/write cursors for all indices of pTab must be open as
* cursor number base+i for the i-th index.
*
* 3. The record number of the row to be deleted must be on the top
* of the stack.
*
* This routine pops the top of the stack to remove the record number
* and then generates code to remove both the table record and all index
* entries that point to that record.
*
* PUBLIC: void __generate_row_delete __P((DBSQL *, vdbe_t *, table_t *,
* PUBLIC: int, int));
*
* dbp The database containing the index
* v Generate code into this VDBE
* table Table containing the row to be deleted
* cur Cursor number for the table
* count Increment the row change counter
*/
void __generate_row_delete(dbp, v, table, cur, count)
DBSQL *dbp;
vdbe_t *v;
table_t *table;
int cur;
int count;
{
int addr;
addr = __vdbe_add_op(v, OP_NotExists, cur, 0);
__generate_row_index_delete(dbp, v, table, cur, 0);
__vdbe_add_op(v, OP_Delete, cur, count);
__vdbe_change_p2(v, addr, __vdbe_current_addr(v));
}
/*
* __generate_row_index_delete --
* This routine generates VDBE code that causes the deletion of all
* index entries associated with a single row of a single table.
*
* The VDBE must be in a particular state when this routine is called.
* These are the requirements:
*
* 1. A read/write cursor pointing to 'table', the table containing
* the row to be deleted, must be opened as cursor number 'cur'.
*
* 2. Read/write cursors for all indices of 'table' must be open as
* cursor number cur+i for the i-th index.
*
* 3. The 'cur' cursor must be pointing to the row that is to be
* deleted.
*
* PUBLIC: void __generate_row_index_delete __P((DBSQL *, vdbe_t *,
* PUBLIC: table_t *, int, char *));
*
* dbp The database containing the index
* v Generate code into this VDBE
* table Table containing the row to be deleted
* cur Cursor number for the table
* idx_used Only delete if aIdxUsed!=0 && aIdxUsed[i]!=0
*/
void __generate_row_index_delete(dbp, v, table, cur, idx_used)
DBSQL *dbp;
vdbe_t *v;
table_t *table;
int cur;
char *idx_used;
{
int i;
index_t *idx;
for (i = 1, idx = table->pIndex; idx; i++, idx = idx->pNext) {
int j;
if (idx_used != 0 && idx_used[i-1] == 0)
continue;
__vdbe_add_op(v, OP_Recno, cur, 0);
for (j = 0; j < idx->nColumn; j++) {
int n = idx->aiColumn[j];
if (n == table->iPKey) {
__vdbe_add_op(v, OP_Dup, j, 0);
} else {
__vdbe_add_op(v, OP_Column, cur, n);
}
}
__vdbe_add_op(v, OP_MakeIdxKey, idx->nColumn, 0);
__add_idx_key_type(v, idx);
__vdbe_add_op(v, OP_IdxDelete, cur + i, 0);
}
}