1068 lines
33 KiB
C
1068 lines
33 KiB
C
/*-
|
|
* DBSQL - A SQL database engine.
|
|
*
|
|
* Copyright (C) 2007-2008 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 3 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.
|
|
*/
|
|
|
|
/*
|
|
* This file contains routines that handle INSERT statements.
|
|
*/
|
|
|
|
#include "dbsql_config.h"
|
|
#include "dbsql_int.h"
|
|
|
|
/*
|
|
* __insert --
|
|
* This routine is call to handle SQL of the following forms:
|
|
*
|
|
* insert into TABLE (IDLIST) values(EXPRLIST)
|
|
* insert into TABLE (IDLIST) select
|
|
*
|
|
* The IDLIST following the table name is always optional. If omitted,
|
|
* then a list of all columns for the table is substituted. The IDLIST
|
|
* appears in the 'column' parameter. 'column' is NULL if IDLIST is
|
|
* omitted.
|
|
*
|
|
* The 'list' parameter holds EXPRLIST in the first form of the INSERT
|
|
* statement above, and 'select' is NULL. For the second form, 'list' is
|
|
* NULL and 'select' is a pointer to the select statement used to generate
|
|
* data for the insert.
|
|
*
|
|
* The code generated follows one of three templates. For a simple
|
|
* select with data coming from a VALUES clause, the code executes
|
|
* once straight down through. The template looks like this:
|
|
*
|
|
* open write cursor to <table> and its indices
|
|
* puts VALUES clause expressions onto the stack
|
|
* write the resulting record into <table>
|
|
* cleanup
|
|
*
|
|
* If the statement is of the form
|
|
*
|
|
* INSERT INTO <table> SELECT ...
|
|
*
|
|
* And the SELECT clause does not read from <table> at any time, then
|
|
* the generated code follows this template:
|
|
*
|
|
* goto B
|
|
* A: setup for the SELECT
|
|
* loop over the tables in the SELECT
|
|
* gosub C
|
|
* end loop
|
|
* cleanup after the SELECT
|
|
* goto D
|
|
* B: open write cursor to <table> and its indices
|
|
* goto A
|
|
* C: insert the select result into <table>
|
|
* return
|
|
* D: cleanup
|
|
*
|
|
* The third template is used if the insert statement takes its
|
|
* values from a SELECT but the data is being inserted into a table
|
|
* that is also read as part of the SELECT. In the third form,
|
|
* we have to use a intermediate table to store the results of
|
|
* the select. The template is like this:
|
|
*
|
|
* goto B
|
|
* A: setup for the SELECT
|
|
* loop over the tables in the SELECT
|
|
* gosub C
|
|
* end loop
|
|
* cleanup after the SELECT
|
|
* goto D
|
|
* C: insert the select result into the intermediate table
|
|
* return
|
|
* B: open a cursor to an intermediate table
|
|
* goto A
|
|
* D: open write cursor to <table> and its indices
|
|
* loop over the intermediate table
|
|
* transfer values form intermediate table into <table>
|
|
* end the loop
|
|
* cleanup
|
|
*
|
|
* PUBLIC: void __insert __P((parser_t *, src_list_t *, expr_list_t *,
|
|
* PUBLIC: select_t *, id_list_t *, int));
|
|
*
|
|
* parser Parser context
|
|
* tlist Name of table into which we are inserting
|
|
* vlist List of values to be inserted
|
|
* select A SELECT statement to use as the data source
|
|
* column Column names corresponding to IDLIST
|
|
* on_error How to handle constraint errors
|
|
*/
|
|
void __insert(parser, tlist, vlist, select, column, on_error)
|
|
parser_t *parser;
|
|
src_list_t *tlist;
|
|
expr_list_t *vlist;
|
|
select_t *select;
|
|
id_list_t *column;
|
|
int on_error;
|
|
{
|
|
int rc, init_code;
|
|
int i, j, dx; /* Loop counters */
|
|
table_t *table; /* The table to insert into */
|
|
char *tname; /* Name of the table into which we are inserting*/
|
|
const char *dname; /* Name of the database holding this table */
|
|
vdbe_t *v; /* Generate code into this virtual machine */
|
|
index_t *idx; /* For looping over indices of the table */
|
|
int ncol; /* Number of columns in the data */
|
|
int base; /* VDBE Cursor number for pTab */
|
|
int cont, brk; /* Beginning and end of the loop over srcTab */
|
|
DBSQL *dbp; /* The main database structure */
|
|
int key_col = -1; /* Column that is the INTEGER PRIMARY KEY */
|
|
int end_of_loop; /* Label for the end of the insertion loop */
|
|
int use_temp_table; /* Store SELECT results in intermediate table */
|
|
int src_tab; /* Data comes from this temporary cursor if >=0 */
|
|
int select_loop; /* Address of code that implements the SELECT */
|
|
int cleanup; /* Address of the cleanup code */
|
|
int insert_block; /* Address of the subroutine used to insert data*/
|
|
int cnt_mem; /* Memory cell used for the row counter */
|
|
int view_p; /* True if attempting to insert into a view */
|
|
|
|
int row_triggers_p = 0; /* True if there are FOR EACH ROW triggers */
|
|
int before_triggers_p; /* True if there are BEFORE triggers */
|
|
int after_triggers_p; /* True if there are AFTER triggers */
|
|
int nidx = -1; /* Cursor for the NEW table */
|
|
vdbe_op_t *op;
|
|
src_list_t dummy;
|
|
|
|
if (parser->nErr)
|
|
goto insert_cleanup;
|
|
|
|
dbp = parser->db;
|
|
|
|
/*
|
|
* Locate the table into which we will be inserting new information.
|
|
*/
|
|
DBSQL_ASSERT(tlist->nSrc == 1);
|
|
tname = tlist->a[0].zName;
|
|
if (tname == 0)
|
|
goto insert_cleanup;
|
|
table = __src_list_lookup(parser, tlist);
|
|
if (table == 0) {
|
|
goto insert_cleanup;
|
|
}
|
|
DBSQL_ASSERT(table->iDb < dbp->nDb);
|
|
dname = dbp->aDb[table->iDb].zName;
|
|
if (__auth_check(parser, DBSQL_INSERT, table->zName, 0, dname)) {
|
|
goto insert_cleanup;
|
|
}
|
|
|
|
/*
|
|
* Ensure that:
|
|
* (a) the table is not read-only,
|
|
* (b) that if it is a view then ON INSERT triggers exist
|
|
*/
|
|
before_triggers_p = __triggers_exist(parser, table->pTrigger,
|
|
TK_INSERT, TK_BEFORE, TK_ROW, 0);
|
|
after_triggers_p = __triggers_exist(parser, table->pTrigger,
|
|
TK_INSERT, TK_AFTER, TK_ROW, 0);
|
|
row_triggers_p = before_triggers_p || after_triggers_p;
|
|
view_p = (table->pSelect != 0);
|
|
if (__is_table_read_only(parser, table, before_triggers_p)) {
|
|
goto insert_cleanup;
|
|
}
|
|
if (table == 0)
|
|
goto insert_cleanup;
|
|
|
|
/*
|
|
* If 'table' is really a view, make sure it has been initialized.
|
|
*/
|
|
if (view_p && __view_get_column_names(parser, table)) {
|
|
goto insert_cleanup;
|
|
}
|
|
|
|
/*
|
|
* Allocate a VDBE
|
|
*/
|
|
v = __parser_get_vdbe(parser);
|
|
if (v == 0)
|
|
goto insert_cleanup;
|
|
__vdbe_prepare_write(parser, select || row_triggers_p, table->iDb);
|
|
|
|
/*
|
|
* If there are row triggers, allocate a temp table for new.*
|
|
* references.
|
|
*/
|
|
if (row_triggers_p) {
|
|
nidx = parser->nTab++;
|
|
}
|
|
|
|
/*
|
|
* Figure out how many columns of data are supplied. If the data
|
|
* is coming from a SELECT statement, then this step also generates
|
|
* all the code to implement the SELECT statement and invoke a
|
|
* subroutine to process each row of the result. (Template 2.) If
|
|
* the SELECT statement uses the the table that is being inserted
|
|
* into, then the subroutine is also coded here. That subroutine
|
|
* stores the SELECT results in a temporary table. (Template 3.)
|
|
*/
|
|
if (select) {
|
|
/*
|
|
* Data is coming from a SELECT. Generate code to implement
|
|
* that SELECT.
|
|
*/
|
|
init_code = __vdbe_add_op(v, OP_Goto, 0, 0);
|
|
select_loop = __vdbe_current_addr(v);
|
|
insert_block = __vdbe_make_label(v);
|
|
rc = __select(parser, select, SRT_Subroutine, insert_block,
|
|
0,0,0);
|
|
if (rc || parser->nErr)
|
|
goto insert_cleanup;
|
|
cleanup = __vdbe_make_label(v);
|
|
__vdbe_add_op(v, OP_Goto, 0, cleanup);
|
|
DBSQL_ASSERT(select->pEList);
|
|
ncol = select->pEList->nExpr;
|
|
|
|
/*
|
|
* Set use_temp_table to TRUE if the result of the SELECT
|
|
* statement should be written into a temporary table. Set
|
|
* to FALSE if each row of the SELECT can be written directly
|
|
* into the result table.
|
|
*
|
|
* A temp table must be used if the table being updated is
|
|
* also one of the tables being read by the SELECT statement.
|
|
* Also use a temp table in the case of row triggers.
|
|
*/
|
|
if (row_triggers_p) {
|
|
use_temp_table = 1;
|
|
} else {
|
|
int addr = __vdbe_find_op(v, OP_OpenRead, table->tnum);
|
|
use_temp_table = 0;
|
|
if (addr > 0) {
|
|
op = __vdbe_get_op(v, addr-2);
|
|
if (op->opcode == OP_Integer &&
|
|
op->p1 == table->iDb) {
|
|
use_temp_table = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (use_temp_table) {
|
|
/*
|
|
* Generate the subroutine that SELECT calls to
|
|
* process each row of the result. Store the result
|
|
* in a temporary table.
|
|
*/
|
|
src_tab = parser->nTab++;
|
|
__vdbe_resolve_label(v, insert_block);
|
|
__vdbe_add_op(v, OP_MakeRecord, ncol, 0);
|
|
__vdbe_add_op(v, OP_NewRecno, src_tab, 0);
|
|
__vdbe_add_op(v, OP_Pull, 1, 0);
|
|
__vdbe_add_op(v, OP_PutIntKey, src_tab, 0);
|
|
__vdbe_add_op(v, OP_Return, 0, 0);
|
|
|
|
/*
|
|
* The following code runs first because the GOTO at
|
|
* the very top of the program jumps to it. Create
|
|
* the temporary table, then jump back up and execute
|
|
* the SELECT code above.
|
|
*/
|
|
__vdbe_change_p2(v, init_code, __vdbe_current_addr(v));
|
|
__vdbe_add_op(v, OP_OpenTemp, src_tab, 0);
|
|
__vdbe_add_op(v, OP_Goto, 0, select_loop);
|
|
__vdbe_resolve_label(v, cleanup);
|
|
} else {
|
|
__vdbe_change_p2(v, init_code, __vdbe_current_addr(v));
|
|
}
|
|
} else {
|
|
/*
|
|
* This is the case if the data for the INSERT is coming from
|
|
* a VALUES clause.
|
|
*/
|
|
DBSQL_ASSERT(vlist != 0);
|
|
src_tab = -1;
|
|
use_temp_table = 0;
|
|
DBSQL_ASSERT(vlist);
|
|
ncol = vlist->nExpr;
|
|
dummy.nSrc = 0;
|
|
for(i = 0; i < ncol; i++) {
|
|
if (__expr_resolve_ids(parser, &dummy, 0,
|
|
vlist->a[i].pExpr)) {
|
|
goto insert_cleanup;
|
|
}
|
|
if (__expr_check(parser, vlist->a[i].pExpr, 0, 0)) {
|
|
goto insert_cleanup;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Make sure the number of columns in the source data matches the
|
|
* number of columns to be inserted into the table.
|
|
*/
|
|
if (column == 0 && ncol != table->nCol) {
|
|
__error_msg(parser,
|
|
"table %S has %d columns but %d values "
|
|
"were supplied", tlist, 0, table->nCol, ncol);
|
|
goto insert_cleanup;
|
|
}
|
|
if (column != 0 && ncol != column->nId) {
|
|
__error_msg(parser, "%d values for %d columns", ncol,
|
|
column->nId);
|
|
goto insert_cleanup;
|
|
}
|
|
|
|
/*
|
|
* If the INSERT statement included an IDLIST term, then make sure
|
|
* all elements of the IDLIST really are columns of the table and
|
|
* remember the column indices.
|
|
*
|
|
* If the table has an INTEGER PRIMARY KEY column and that column
|
|
* is named in the IDLIST, then record in the key_col variable
|
|
* the index into IDLIST of the primary key column. key_col is
|
|
* the index of the primary key as it appears in IDLIST, not as
|
|
* is appears in the original table. (The index of the primary
|
|
* key in the original table is table->iPKey.)
|
|
*/
|
|
if (column) {
|
|
for(i = 0; i < column->nId; i++) {
|
|
column->a[i].idx = -1;
|
|
}
|
|
for(i = 0; i < column->nId; i++) {
|
|
for(j = 0; j < table->nCol; j++) {
|
|
if (strcasecmp(column->a[i].zName,
|
|
table->aCol[j].zName) == 0) {
|
|
column->a[i].idx = j;
|
|
if (j == table->iPKey) {
|
|
key_col = i;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if(j >= table->nCol) {
|
|
if (__is_row_id(column->a[i].zName)) {
|
|
key_col = i;
|
|
} else {
|
|
__error_msg(parser,
|
|
"table %S has no column named %s",
|
|
tlist, 0, column->a[i].zName);
|
|
parser->nErr++;
|
|
goto insert_cleanup;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If there is no IDLIST term but the table has an integer primary
|
|
* key, then set the key_col variable to the primary key column
|
|
* index in the original table definition.
|
|
*/
|
|
if (column == 0) {
|
|
key_col = table->iPKey;
|
|
}
|
|
|
|
/*
|
|
* Open the temp table for FOR EACH ROW triggers.
|
|
*/
|
|
if (row_triggers_p) {
|
|
__vdbe_add_op(v, OP_OpenPseudo, nidx, 0);
|
|
}
|
|
|
|
/*
|
|
* Initialize the count of rows to be inserted.
|
|
*/
|
|
if (dbp->flags & DBSQL_CountRows) {
|
|
cnt_mem = parser->nMem++;
|
|
__vdbe_add_op(v, OP_Integer, 0, 0);
|
|
__vdbe_add_op(v, OP_MemStore, cnt_mem, 1);
|
|
}
|
|
|
|
/*
|
|
* Open tables and indices if there are no row triggers.
|
|
*/
|
|
if (!row_triggers_p) {
|
|
base = parser->nTab;
|
|
__vdbe_add_op(v, OP_Integer, table->iDb, 0);
|
|
__vdbe_add_op(v, OP_OpenWrite, base, table->tnum);
|
|
__vdbe_change_p3(v, -1, table->zName, P3_STATIC);
|
|
for (dx = 1, idx = table->pIndex; idx; idx = idx->pNext, dx++){
|
|
__vdbe_add_op(v, OP_Integer, idx->iDb, 0);
|
|
__vdbe_add_op(v, OP_OpenWrite, (dx + base), idx->tnum);
|
|
__vdbe_change_p3(v, -1, idx->zName, P3_STATIC);
|
|
}
|
|
parser->nTab += dx;
|
|
}
|
|
|
|
/*
|
|
* If the data source is a temporary table, then we have to create
|
|
* a loop because there might be multiple rows of data. If the data
|
|
* source is a subroutine call from the SELECT statement, then we need
|
|
* to launch the SELECT statement processing.
|
|
*/
|
|
if (use_temp_table) {
|
|
brk = __vdbe_make_label(v);
|
|
__vdbe_add_op(v, OP_Rewind, src_tab, brk);
|
|
cont = __vdbe_current_addr(v);
|
|
} else if (select) {
|
|
__vdbe_add_op(v, OP_Goto, 0, select_loop);
|
|
__vdbe_resolve_label(v, insert_block);
|
|
}
|
|
|
|
/*
|
|
* Run the BEFORE and INSTEAD OF triggers, if there are any.
|
|
*/
|
|
end_of_loop = __vdbe_make_label(v);
|
|
if (before_triggers_p) {
|
|
|
|
/*
|
|
* build the new.* reference row. Note that if there is
|
|
* an INTEGER PRIMARY KEY into which a NULL is being
|
|
* inserted, that NULL will be translated into a unique ID
|
|
* for the row. But on a BEFORE trigger, we do not know what
|
|
* the unique ID will be (because the insert has not happened
|
|
* yet) so we substitute a rowid of -1.
|
|
*/
|
|
if (key_col < 0) {
|
|
__vdbe_add_op(v, OP_Integer, -1, 0);
|
|
} else if (use_temp_table) {
|
|
__vdbe_add_op(v, OP_Column, src_tab, key_col);
|
|
} else if (select) {
|
|
__vdbe_add_op(v, OP_Dup, ncol - key_col - 1, 1);
|
|
} else {
|
|
__expr_code(parser, vlist->a[key_col].pExpr);
|
|
__vdbe_add_op(v, OP_NotNull, -1,
|
|
(__vdbe_current_addr(v) + 3));
|
|
__vdbe_add_op(v, OP_Pop, 1, 0);
|
|
__vdbe_add_op(v, OP_Integer, -1, 0);
|
|
__vdbe_add_op(v, OP_MustBeInt, 0, 0);
|
|
}
|
|
|
|
/*
|
|
* Create the new column data.
|
|
*/
|
|
for (i = 0; i < table->nCol; i++) {
|
|
if (column == 0) {
|
|
j = i;
|
|
} else {
|
|
for (j = 0; j < column->nId; j++) {
|
|
if (column->a[j].idx == i)
|
|
break;
|
|
}
|
|
}
|
|
if (column && j >= column->nId) {
|
|
__vdbe_add_op(v, OP_String, 0, 0);
|
|
__vdbe_change_p3(v, -1, table->aCol[i].zDflt,
|
|
P3_STATIC);
|
|
} else if (use_temp_table) {
|
|
__vdbe_add_op(v, OP_Column, src_tab, j);
|
|
} else if (select) {
|
|
__vdbe_add_op(v, OP_Dup, (ncol - j - 1), 1);
|
|
} else {
|
|
__expr_code(parser, vlist->a[j].pExpr);
|
|
}
|
|
}
|
|
__vdbe_add_op(v, OP_MakeRecord, table->nCol, 0);
|
|
__vdbe_add_op(v, OP_PutIntKey, nidx, 0);
|
|
|
|
/*
|
|
* Fire BEFORE or INSTEAD OF triggers.
|
|
*/
|
|
if (__code_row_trigger(parser, TK_INSERT, 0, TK_BEFORE, table,
|
|
nidx, -1, on_error, end_of_loop)) {
|
|
goto insert_cleanup;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If any triggers exists, the opening of tables and indices is
|
|
* deferred until now.
|
|
*/
|
|
if (row_triggers_p && !view_p) {
|
|
base = parser->nTab;
|
|
__vdbe_add_op(v, OP_Integer, table->iDb, 0);
|
|
__vdbe_add_op(v, OP_OpenWrite, base, table->tnum);
|
|
__vdbe_change_p3(v, -1, table->zName, P3_STATIC);
|
|
for (dx = 1, idx = table->pIndex; idx; idx = idx->pNext, dx++){
|
|
__vdbe_add_op(v, OP_Integer, idx->iDb, 0);
|
|
__vdbe_add_op(v, OP_OpenWrite, (dx + base), idx->tnum);
|
|
__vdbe_change_p3(v, -1, idx->zName, P3_STATIC);
|
|
}
|
|
parser->nTab += dx;
|
|
}
|
|
|
|
/*
|
|
* Push the record number for the new entry onto the stack. The
|
|
* record number is a randomly generate integer created by NewRecno
|
|
* except when the table has an INTEGER PRIMARY KEY column, in which
|
|
* case the record number is the same as that column.
|
|
*/
|
|
if (!view_p) {
|
|
if (key_col >= 0) {
|
|
if (use_temp_table) {
|
|
__vdbe_add_op(v, OP_Column, src_tab,
|
|
key_col);
|
|
} else if (select) {
|
|
__vdbe_add_op(v, OP_Dup,
|
|
(ncol - key_col - 1), 1);
|
|
} else {
|
|
__expr_code(parser, vlist->a[key_col].pExpr);
|
|
}
|
|
/*
|
|
* If the PRIMARY KEY expression is NULL, then use
|
|
* OP_NewRecno to generate a unique primary key value.
|
|
*/
|
|
__vdbe_add_op(v, OP_NotNull, -1,
|
|
(__vdbe_current_addr(v) + 3));
|
|
__vdbe_add_op(v, OP_Pop, 1, 0);
|
|
__vdbe_add_op(v, OP_NewRecno, base, 0);
|
|
__vdbe_add_op(v, OP_MustBeInt, 0, 0);
|
|
} else {
|
|
__vdbe_add_op(v, OP_NewRecno, base, 0);
|
|
}
|
|
|
|
/*
|
|
* Push onto the stack, data for all columns of the new
|
|
* entry, beginning with the first column.
|
|
*/
|
|
for (i = 0; i < table->nCol; i++) {
|
|
if (i == table->iPKey) {
|
|
/*
|
|
* The value of the INTEGER PRIMARY KEY
|
|
* column is always a NULL. Whenever this
|
|
* column is read, the record number will be
|
|
* substituted in its place. So will fill
|
|
* this column with a NULL to avoid taking up
|
|
* data space with information that will never
|
|
* be used.
|
|
*/
|
|
__vdbe_add_op(v, OP_String, 0, 0);
|
|
continue;
|
|
}
|
|
if (column == 0) {
|
|
j = i;
|
|
} else {
|
|
for (j = 0; j < column->nId; j++) {
|
|
if (column->a[j].idx == i)
|
|
break;
|
|
}
|
|
}
|
|
if (column && j >= column->nId) {
|
|
__vdbe_add_op(v, OP_String, 0, 0);
|
|
__vdbe_change_p3(v, -1, table->aCol[i].zDflt,
|
|
P3_STATIC);
|
|
} else if (use_temp_table) {
|
|
__vdbe_add_op(v, OP_Column, src_tab, j);
|
|
} else if (select) {
|
|
__vdbe_add_op(v, OP_Dup, (i + ncol - j), 1);
|
|
} else {
|
|
__expr_code(parser, vlist->a[j].pExpr);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Generate code to check constraints and generate index
|
|
* keys and do the insertion.
|
|
*/
|
|
__generate_constraint_checks(parser, table, base, 0,
|
|
key_col >= 0, 0, on_error,
|
|
end_of_loop);
|
|
__complete_insertion(parser, table, base, 0, 0, 0,
|
|
after_triggers_p ? nidx : -1);
|
|
}
|
|
|
|
/*
|
|
* Update the count of rows that are inserted.
|
|
*/
|
|
if ((dbp->flags & DBSQL_CountRows) != 0) {
|
|
__vdbe_add_op(v, OP_MemIncr, cnt_mem, 0);
|
|
}
|
|
|
|
if (row_triggers_p) {
|
|
/* Close all tables opened */
|
|
if (!view_p) {
|
|
__vdbe_add_op(v, OP_Close, base, 0);
|
|
for(dx = 1, idx = table->pIndex; idx;
|
|
idx = idx->pNext, dx++) {
|
|
__vdbe_add_op(v, OP_Close, (dx + base), 0);
|
|
}
|
|
}
|
|
|
|
/* Code AFTER triggers */
|
|
if (__code_row_trigger(parser, TK_INSERT, 0, TK_AFTER, table,
|
|
nidx, -1, on_error, end_of_loop)) {
|
|
goto insert_cleanup;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* The bottom of the loop, if the data source is a SELECT statement.
|
|
*/
|
|
__vdbe_resolve_label(v, end_of_loop);
|
|
if (use_temp_table) {
|
|
__vdbe_add_op(v, OP_Next, src_tab, cont);
|
|
__vdbe_resolve_label(v, brk);
|
|
__vdbe_add_op(v, OP_Close, src_tab, 0);
|
|
} else if (select) {
|
|
__vdbe_add_op(v, OP_Pop, ncol, 0);
|
|
__vdbe_add_op(v, OP_Return, 0, 0);
|
|
__vdbe_resolve_label(v, cleanup);
|
|
}
|
|
|
|
if (!row_triggers_p) {
|
|
/* Close all tables opened */
|
|
__vdbe_add_op(v, OP_Close, base, 0);
|
|
for(dx = 1, idx = table->pIndex; idx; idx = idx->pNext, dx++){
|
|
__vdbe_add_op(v, OP_Close, (dx + base), 0);
|
|
}
|
|
}
|
|
|
|
__vdbe_conclude_write(parser);
|
|
|
|
/*
|
|
* Return the number of rows inserted.
|
|
*/
|
|
if (dbp->flags & DBSQL_CountRows) {
|
|
__vdbe_add_op(v, OP_ColumnName, 0, 0);
|
|
__vdbe_change_p3(v, -1, "rows inserted", P3_STATIC);
|
|
__vdbe_add_op(v, OP_MemLoad, cnt_mem, 0);
|
|
__vdbe_add_op(v, OP_Callback, 1, 0);
|
|
}
|
|
|
|
insert_cleanup:
|
|
__src_list_delete(tlist);
|
|
if (vlist )
|
|
__expr_list_delete(vlist);
|
|
if (select)
|
|
__select_delete(select);
|
|
__id_list_delete(column);
|
|
}
|
|
|
|
/*
|
|
* __generate_constraint_checks --
|
|
* Generate code to do a constraint check prior to an INSERT or an UPDATE.
|
|
*
|
|
* When this routine is called, the stack contains (from bottom to top)
|
|
* the following values:
|
|
*
|
|
* 1. The recno of the row to be updated before the update. This
|
|
* value is omitted unless we are doing an UPDATE that involves a
|
|
* change to the record number.
|
|
*
|
|
* 2. The recno of the row after the update.
|
|
*
|
|
* 3. The data in the first column of the entry after the update.
|
|
*
|
|
* i. Data from middle columns...
|
|
*
|
|
* N. The data in the last column of the entry after the update.
|
|
*
|
|
* The old recno shown as entry (1) above is omitted unless both
|
|
* 'update_p' and 'will_recno_chng_p' are 1. 'update_p' is true for
|
|
* UPDATEs and false for INSERTs and 'will_recno_chng_p' is true if
|
|
* the record number is being changed.
|
|
*
|
|
* The code generated by this routine pushes additional entries onto
|
|
* the stack which are the keys for new index entries for the new record.
|
|
* The order of index keys is the same as the order of the indices on
|
|
* the table->pIndex list. A key is only created for index i if
|
|
* used_indicies!=0 and used_indicies[i]!=0.
|
|
*
|
|
* This routine also generates code to check constraints. NOT NULL,
|
|
* CHECK, and UNIQUE constraints are all checked. If a constraint fails,
|
|
* then the appropriate action is performed. There are five possible
|
|
* actions: ROLLBACK, ABORT, FAIL, REPLACE, and IGNORE.
|
|
*
|
|
* Constraint type Action What Happens
|
|
* --------------- ---------- ----------------------------------------
|
|
* any ROLLBACK The current transaction is rolled back and
|
|
* dbsql_exec() returns immediately with a
|
|
* return code of DBSQL_CONSTRAINT.
|
|
*
|
|
* any ABORT Back out changes from the current command
|
|
* only (do not do a complete rollback) then
|
|
* cause dbsql_exec() to return immediately
|
|
* with DBSQL_CONSTRAINT.
|
|
*
|
|
* any FAIL dbsql_exec() returns immediately with a
|
|
* return code of DBSQL_CONSTRAINT. The
|
|
* transaction is not rolled back and any
|
|
* prior changes are retained.
|
|
*
|
|
* any IGNORE The record number and data is popped from
|
|
* the stack and there is an immediate jump
|
|
* to label 'ignore_dest'.
|
|
*
|
|
* NOT NULL REPLACE The NULL value is replace by the default
|
|
* value for that column. If the default value
|
|
* is NULL, the action is the same as ABORT.
|
|
*
|
|
* UNIQUE REPLACE The other row that conflicts with the row
|
|
* being inserted is removed.
|
|
*
|
|
* CHECK REPLACE Illegal. The results in an exception.
|
|
*
|
|
* Which action to take is determined by the override_error parameter.
|
|
* Or if override_error==OE_Default, then the parser->onError parameter
|
|
* is used. Or if parser->onError==OE_Default then the onError value
|
|
* for the constraint is used.
|
|
*
|
|
* The calling routine must open a read/write cursor for table with
|
|
* cursor number 'base'. All indices of table must also have open
|
|
* read/write cursors with cursor number base+i for the i-th cursor.
|
|
* Except, if there is no possibility of a REPLACE action then
|
|
* cursors do not need to be open for indices where used_indicies[i]==0.
|
|
*
|
|
* If the 'update_p' flag is true, it means that the 'base' cursor is
|
|
* initially pointing to an entry that is being updated. The 'update_p'
|
|
* flag causes extra code to be generated so that the 'base' cursor
|
|
* is still pointing at the same entry after the routine returns.
|
|
* Without the 'update_p' flag, the 'base' cursor might be moved.
|
|
*
|
|
* PUBLIC: void __generate_constraint_checks __P((parser_t *, table_t *, int,
|
|
* PUBLIC: char *, int, int, int, int));
|
|
*
|
|
* parser The parser context
|
|
* table The table into which we are inserting
|
|
* base Index of a read/write cursor pointing at table
|
|
* used_indicies Which indices are used. If NULL, all are used
|
|
* will_recno_chng_p True if the record number will change
|
|
* update_p True for UPDATE, False for INSERT
|
|
* override_error Override onError to this if not OE_Default
|
|
* ignore_dest Jump to this label on an OE_Ignore resolution
|
|
*/
|
|
void
|
|
__generate_constraint_checks(parser, table, base, used_indicies,
|
|
will_recno_chng_p, update_p, override_error,
|
|
ignore_dest)
|
|
parser_t *parser;
|
|
table_t *table;
|
|
int base;
|
|
char *used_indicies;
|
|
int will_recno_chng_p;
|
|
int update_p;
|
|
int override_error;
|
|
int ignore_dest;
|
|
{
|
|
int i;
|
|
vdbe_t *v;
|
|
int num_col;
|
|
int on_error;
|
|
int addr;
|
|
int extra;
|
|
int cur;
|
|
index_t *index;
|
|
int seen_replace_p = 0;
|
|
int jmp_inst_1, jmp_inst_2;
|
|
int cont_addr;
|
|
int two_recnos_p = (update_p && will_recno_chng_p);
|
|
char *msg = 0;
|
|
char *dup_msg;
|
|
int j, n1, n2;
|
|
char err_msg[200];
|
|
char *col;
|
|
|
|
v = __parser_get_vdbe(parser);
|
|
DBSQL_ASSERT(v != 0);
|
|
DBSQL_ASSERT(table->pSelect == 0); /* This table is not a VIEW */
|
|
num_col = table->nCol;
|
|
|
|
/*
|
|
* Test all NOT NULL constraints.
|
|
*/
|
|
for (i = 0; i < num_col; i++) {
|
|
if (i == table->iPKey) {
|
|
continue;
|
|
}
|
|
on_error = table->aCol[i].notNull;
|
|
if (on_error == OE_None)
|
|
continue;
|
|
if (override_error != OE_Default) {
|
|
on_error = override_error;
|
|
} else if (parser->db->onError != OE_Default) {
|
|
on_error = parser->db->onError;
|
|
} else if (on_error == OE_Default) {
|
|
on_error = OE_Abort;
|
|
}
|
|
if (on_error == OE_Replace && table->aCol[i].zDflt == 0) {
|
|
on_error = OE_Abort;
|
|
}
|
|
__vdbe_add_op(v, OP_Dup, (num_col - 1 - i), 1);
|
|
addr = __vdbe_add_op(v, OP_NotNull, 1, 0);
|
|
switch(on_error) {
|
|
case OE_Rollback: /* FALLTHROUGH */
|
|
case OE_Abort: /* FALLTHROUGH */
|
|
case OE_Fail:
|
|
msg = 0; /* Reset msg or set_string does bad things. */
|
|
__vdbe_add_op(v, OP_Halt, DBSQL_CONSTRAINT, on_error);
|
|
__str_append(&msg, table->zName, ".",
|
|
table->aCol[i].zName,
|
|
" may not be NULL", (char*)0);
|
|
__vdbe_change_p3(v, -1, msg, P3_DYNAMIC);
|
|
break;
|
|
case OE_Ignore:
|
|
__vdbe_add_op(v, OP_Pop, (num_col + 1 + two_recnos_p),
|
|
0);
|
|
__vdbe_add_op(v, OP_Goto, 0, ignore_dest);
|
|
break;
|
|
case OE_Replace:
|
|
__vdbe_add_op(v, OP_String, 0, 0);
|
|
__vdbe_change_p3(v, -1, table->aCol[i].zDflt,
|
|
P3_STATIC);
|
|
__vdbe_add_op(v, OP_Push, (num_col - i), 0);
|
|
break;
|
|
default:
|
|
DBSQL_ASSERT(0);
|
|
break;
|
|
}
|
|
__vdbe_change_p2(v, addr, __vdbe_current_addr(v));
|
|
}
|
|
|
|
/*
|
|
* Test all CHECK constraints
|
|
*/
|
|
|
|
/*
|
|
* If we have an INTEGER PRIMARY KEY, make sure the primary key
|
|
* of the new record does not previously exist. Except, if this
|
|
* is an UPDATE and the primary key is not changing, that is OK.
|
|
*/
|
|
if (will_recno_chng_p) {
|
|
on_error = table->keyConf;
|
|
if (override_error != OE_Default) {
|
|
on_error = override_error;
|
|
} else if (parser->db->onError != OE_Default) {
|
|
on_error = parser->db->onError;
|
|
} else if (on_error == OE_Default) {
|
|
on_error = OE_Abort;
|
|
}
|
|
|
|
if (update_p) {
|
|
__vdbe_add_op(v, OP_Dup, (num_col + 1), 1);
|
|
__vdbe_add_op(v, OP_Dup, (num_col + 1), 1);
|
|
jmp_inst_1 = __vdbe_add_op(v, OP_Eq, 0, 0);
|
|
}
|
|
__vdbe_add_op(v, OP_Dup, num_col, 1);
|
|
jmp_inst_2 = __vdbe_add_op(v, OP_NotExists, base, 0);
|
|
switch(on_error) {
|
|
default:
|
|
on_error = OE_Abort; /* FALLTHROUGH */
|
|
case OE_Rollback: /* FALLTHROUGH */
|
|
case OE_Abort: /* FALLTHROUGH */
|
|
case OE_Fail:
|
|
__vdbe_add_op(v, OP_Halt, DBSQL_CONSTRAINT, on_error);
|
|
__vdbe_change_p3(v, -1, "PRIMARY KEY must be unique",
|
|
P3_STATIC);
|
|
break;
|
|
case OE_Replace:
|
|
__generate_row_index_delete(parser->db, v, table,
|
|
base, 0);
|
|
if (update_p) {
|
|
__vdbe_add_op(v, OP_Dup, (num_col +
|
|
two_recnos_p), 1);
|
|
__vdbe_add_op(v, OP_MoveTo, base, 0);
|
|
}
|
|
seen_replace_p = 1;
|
|
break;
|
|
case OE_Ignore:
|
|
DBSQL_ASSERT(seen_replace_p == 0);
|
|
__vdbe_add_op(v, OP_Pop, (num_col + 1 + two_recnos_p),
|
|
0);
|
|
__vdbe_add_op(v, OP_Goto, 0, ignore_dest);
|
|
break;
|
|
}
|
|
cont_addr = __vdbe_current_addr(v);
|
|
__vdbe_change_p2(v, jmp_inst_2, cont_addr);
|
|
if (update_p) {
|
|
__vdbe_change_p2(v, jmp_inst_1, cont_addr);
|
|
__vdbe_add_op(v, OP_Dup, (num_col + 1), 1);
|
|
__vdbe_add_op(v, OP_MoveTo, base, 0);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Test all UNIQUE constraints by creating entries for each UNIQUE
|
|
* index and making sure that duplicate entries do not already exist.
|
|
* Add the new records to the indices as we go.
|
|
*/
|
|
extra = -1;
|
|
for (cur = 0, index = table->pIndex; index;
|
|
index = index->pNext, cur++) {
|
|
if (used_indicies && used_indicies[cur] == 0)
|
|
continue; /* Skip unused indices */
|
|
extra++;
|
|
|
|
/*
|
|
* Create a key for accessing the index entry.
|
|
*/
|
|
__vdbe_add_op(v, OP_Dup, (num_col + extra), 1);
|
|
for (i = 0; i < index->nColumn; i++) {
|
|
int idx = index->aiColumn[i];
|
|
if (idx == table->iPKey) {
|
|
__vdbe_add_op(v, OP_Dup, (i + extra +
|
|
num_col + 1), 1);
|
|
} else {
|
|
__vdbe_add_op(v, OP_Dup, (i + extra +
|
|
num_col - idx), 1);
|
|
}
|
|
}
|
|
jmp_inst_1 = __vdbe_add_op(v, OP_MakeIdxKey, index->nColumn,0);
|
|
__add_idx_key_type(v, index);
|
|
|
|
/*
|
|
* Find out what action to take in case there is an
|
|
* indexing conflict.
|
|
*/
|
|
on_error = index->onError;
|
|
if (on_error == OE_None)
|
|
continue; /* index is not a UNIQUE index */
|
|
if (override_error != OE_Default) {
|
|
on_error = override_error;
|
|
} else if (parser->db->onError != OE_Default) {
|
|
on_error = parser->db->onError;
|
|
} else if (on_error == OE_Default) {
|
|
on_error = OE_Abort;
|
|
}
|
|
if (seen_replace_p) {
|
|
if (on_error == OE_Ignore)
|
|
on_error = OE_Replace;
|
|
else if (on_error == OE_Fail)
|
|
on_error = OE_Abort;
|
|
}
|
|
|
|
/*
|
|
* Check to see if the new index entry will be unique.
|
|
*/
|
|
__vdbe_add_op(v, OP_Dup, (extra + num_col + 1 +
|
|
two_recnos_p), 1);
|
|
jmp_inst_2 = __vdbe_add_op(v, OP_IsUnique, (base + cur + 1),
|
|
0);
|
|
|
|
/*
|
|
* Generate code that executes if the new index entry
|
|
* is not unique.
|
|
*/
|
|
switch(on_error) {
|
|
case OE_Rollback: /* FALLTHROUGH */
|
|
case OE_Abort: /* FALLTHROUGH */
|
|
case OE_Fail:
|
|
strcpy(err_msg, (index->nColumn>1 ?
|
|
"columns " : "column "));
|
|
n1 = strlen(err_msg);
|
|
for (j = 0; j < index->nColumn &&
|
|
n1 < (sizeof(err_msg) - 30); j++) {
|
|
col = table->aCol[index->aiColumn[j]].zName;
|
|
n2 = strlen(col);
|
|
if (j > 0) {
|
|
strcpy(&err_msg[n1], ", ");
|
|
n1 += 2;
|
|
}
|
|
if ((n1 + n2) > (sizeof(err_msg) - 30)) {
|
|
strcpy(&err_msg[n1], "...");
|
|
n1 += 3;
|
|
break;
|
|
} else {
|
|
strcpy(&err_msg[n1], col);
|
|
n1 += n2;
|
|
}
|
|
}
|
|
strcpy(&err_msg[n1], (index->nColumn > 1 ?
|
|
" are not unique" : " is not unique"));
|
|
__vdbe_add_op(v, OP_Halt, DBSQL_CONSTRAINT, on_error);
|
|
__dbsql_strdup(parser->db, err_msg, &dup_msg);
|
|
__vdbe_change_p3(v, -1, dup_msg, P3_DYNAMIC);
|
|
break;
|
|
case OE_Ignore:
|
|
DBSQL_ASSERT(seen_replace_p == 0);
|
|
__vdbe_add_op(v, OP_Pop, (num_col + extra + 3 +
|
|
two_recnos_p), 0);
|
|
__vdbe_add_op(v, OP_Goto, 0, ignore_dest);
|
|
break;
|
|
case OE_Replace:
|
|
__generate_row_delete(parser->db, v, table, base, 0);
|
|
if (update_p) {
|
|
__vdbe_add_op(v, OP_Dup, (num_col + extra + 1 +
|
|
two_recnos_p), 1);
|
|
__vdbe_add_op(v, OP_MoveTo, base, 0);
|
|
}
|
|
seen_replace_p = 1;
|
|
break;
|
|
default:
|
|
DBSQL_ASSERT(0);
|
|
break;
|
|
}
|
|
cont_addr = __vdbe_current_addr(v);
|
|
#if NULL_DISTINCT_FOR_UNIQUE
|
|
__vdbe_change_p2(v, jmp_inst_1, cont_addr);
|
|
#endif
|
|
__vdbe_change_p2(v, jmp_inst_2, cont_addr);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* __complete_insertion --
|
|
* This routine generates code to finish the INSERT or UPDATE operation
|
|
* that was started by a prior call to __generate_constraint_checks.
|
|
* The stack must contain keys for all active indices followed by data
|
|
* and the recno for the new entry. This routine creates the new
|
|
* entries in all indices and in the main table.
|
|
*
|
|
* The arguments to this routine should be the same as the first six
|
|
* arguments to __generate_constraint_checks.
|
|
*
|
|
* PUBLIC: void __complete_insertion __P((parser_t *, table_t *, int, char *,
|
|
* PUBLIC: int, int, int));
|
|
*
|
|
* parser The parser context
|
|
* table The table into which we are inserting
|
|
* base Index of a read/write cursor pointing at table
|
|
* used_indicies Which indices are used. If NULL, all are used
|
|
* will_recno_chng_p True if the record number will change
|
|
* update_p True for UPDATE, False for INSERT
|
|
* new_idx Index of NEW table for triggers, -1 if none
|
|
*/
|
|
void __complete_insertion(parser, table, base, used_indicies,
|
|
will_recno_chng_p, update_p, new_idx)
|
|
parser_t *parser;
|
|
table_t *table;
|
|
int base;
|
|
char *used_indicies;
|
|
int will_recno_chng_p;
|
|
int update_p;
|
|
int new_idx;
|
|
{
|
|
int i;
|
|
vdbe_t *v;
|
|
int idx;
|
|
index_t *index;
|
|
|
|
v = __parser_get_vdbe(parser);
|
|
DBSQL_ASSERT(v != 0);
|
|
DBSQL_ASSERT(table->pSelect == 0); /* This table is not a VIEW */
|
|
idx = 0;
|
|
index = table->pIndex;
|
|
while (index) {
|
|
index = index->pNext;
|
|
idx++;
|
|
}
|
|
for (i = (idx - 1); i >= 0; i--) {
|
|
if (used_indicies && used_indicies[i] == 0)
|
|
continue;
|
|
__vdbe_add_op(v, OP_IdxPut, base + i + 1, 0);
|
|
}
|
|
__vdbe_add_op(v, OP_MakeRecord, table->nCol, 0);
|
|
if (new_idx >= 0) {
|
|
__vdbe_add_op(v, OP_Dup, 1, 0);
|
|
__vdbe_add_op(v, OP_Dup, 1, 0);
|
|
__vdbe_add_op(v, OP_PutIntKey, new_idx, 0);
|
|
}
|
|
__vdbe_add_op(v, OP_PutIntKey, base, (parser->trigStack ? 0 : 1));
|
|
if (update_p && will_recno_chng_p) {
|
|
__vdbe_add_op(v, OP_Pop, 1, 0);
|
|
}
|
|
}
|