2007-03-10 19:04:07 +00:00
|
|
|
/*-
|
|
|
|
* DBSQL - A SQL database engine.
|
|
|
|
*
|
2008-05-15 23:08:18 +00:00
|
|
|
* Copyright (C) 2007-2008 The DBSQL Group, Inc. - All rights reserved.
|
2007-03-10 19:04:07 +00:00
|
|
|
*
|
2007-10-21 01:57:28 +00:00
|
|
|
* 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.
|
2007-03-10 19:04:07 +00:00
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*
|
|
|
|
* $Id: vdbe.c 7 2007-02-03 13:34:17Z gburd $
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* The code in this file implements execution method of the
|
|
|
|
* Virtual Database Engine (VDBE). A separate file ("vdbe_aux.c")
|
|
|
|
* handles housekeeping details such as creating and deleting
|
|
|
|
* VDBE instances. This file is solely interested in executing
|
|
|
|
* the VDBE program.
|
|
|
|
*
|
|
|
|
* In the external interface, an "dbsql_stmt_t*" is an opaque pointer
|
|
|
|
* to a VDBE.
|
|
|
|
*
|
|
|
|
* The SQL parser generates a program which is then executed by
|
|
|
|
* the VDBE to do the work of the SQL statement. VDBE programs are
|
|
|
|
* similar in form to assembly language. The program consists of
|
|
|
|
* a linear sequence of operations. Each operation has an opcode
|
|
|
|
* and 3 operands. Operands P1 and P2 are integers. Operand P3
|
|
|
|
* is a null-terminated string. The P2 operand must be non-negative.
|
|
|
|
* Opcodes will typically ignore one or more operands. Many opcodes
|
|
|
|
* ignore all three operands.
|
|
|
|
*
|
|
|
|
* Computation results are stored on a stack. Each entry on the
|
|
|
|
* stack is either an integer, a null-terminated string, a floating point
|
|
|
|
* number, or the SQL "NULL" value. An inplicit conversion from one
|
|
|
|
* type to the other occurs as necessary.
|
|
|
|
*
|
|
|
|
* Most of the code in this file is taken up by the __vdbe_exec()
|
|
|
|
* function which does the work of interpreting a VDBE program.
|
|
|
|
* But other routines are also provided to help in building up
|
|
|
|
* a program instruction by instruction.
|
|
|
|
*
|
|
|
|
* Various scripts scan this source file in order to generate HTML
|
|
|
|
* documentation, headers files, or other derived files. The formatting
|
|
|
|
* of the code in this file is, therefore, important. See other comments
|
|
|
|
* in this file for details. If in doubt, do not deviate from existing
|
|
|
|
* commenting and indentation practices when changing or adding code.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "dbsql_config.h"
|
|
|
|
|
|
|
|
#ifndef NO_SYSTEM_INCLUDES
|
|
|
|
#include <ctype.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include "dbsql_int.h"
|
|
|
|
#include "inc/vdbe_int.h"
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The following global variable is incremented every time a cursor
|
|
|
|
* moves, either by the OP_MoveTo or the OP_Next opcode. The test
|
|
|
|
* procedures use this information to make sure that indices are
|
|
|
|
* working correctly.
|
|
|
|
* This variable has no function other than to help verify the correct
|
|
|
|
* operation of the library.
|
|
|
|
*/
|
|
|
|
#ifdef CONFIG_TEST
|
|
|
|
int dbsql_search_count = 0;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/*
|
|
|
|
* When this global variable is positive, it gets decremented once before
|
|
|
|
* each instruction in the VDBE. When reaches zero, the DBSQL_Interrupt
|
|
|
|
* of the db.flags field is set in order to simulate and interrupt.
|
|
|
|
* This variable is used for testing purposes only. It does not function
|
|
|
|
* in an ordinary build.
|
|
|
|
*/
|
|
|
|
#ifdef CONFIG_TEST
|
|
|
|
int dbsql_interrupt_count = 0;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/*
|
|
|
|
* __api_step --
|
|
|
|
* Advance the virtual machine to the next output row.
|
|
|
|
*
|
|
|
|
* The return vale will be either DBSQL_BUSY, DBSQL_DONE,
|
|
|
|
* DBSQL_ROW, DBSQL_ERROR, or DBSQL_MISUSE.
|
|
|
|
*
|
|
|
|
* DBSQL_BUSY means that the virtual machine attempted to open
|
|
|
|
* a locked database and there is no busy callback registered.
|
|
|
|
* Call DBSQL->step() again to retry the open. '*n' is set to 0
|
|
|
|
* and '*col_names' and '*values' are both set to NULL.
|
|
|
|
*
|
|
|
|
* DBSQL_DONE means that the virtual machine has finished
|
|
|
|
* executing. DBSQL->step() should not be called again on this
|
|
|
|
* virtual machine. '*n' and '*col_names' are set appropriately
|
|
|
|
* but '*values' is set to NULL.
|
|
|
|
*
|
|
|
|
* DBSQL_ROW means that the virtual machine has generated another
|
|
|
|
* row of the result set. '*n' is set to the number of columns in
|
|
|
|
* the row. '*col_name' is set to the names of the columns followed
|
|
|
|
* by the column datatypes. '*values' is set to the values of each
|
|
|
|
* column in the row. The value of the i-th column is (*values)[i].
|
|
|
|
* The name of the i-th column is (*col_names)[i] and the datatype
|
|
|
|
* of the i-th column is (*col_names)[i+*n].
|
|
|
|
*
|
|
|
|
* DBSQL_ERROR means that a run-time error (such as a constraint
|
|
|
|
* violation) has occurred. The details of the error will be returned
|
|
|
|
* by the next call to DBSQL->finalize(). DBSQL->step() should not
|
|
|
|
* be called again on the VM.
|
|
|
|
*
|
|
|
|
* DBSQL_MISUSE means that the this routine was called inappropriately.
|
|
|
|
* Perhaps it was called on a virtual machine that had already been
|
|
|
|
* finalized or on one that had previously returned DBSQL_ERROR or
|
|
|
|
* DBSQL_DONE. Or it could be the case the the same database connection
|
|
|
|
* is being used simulataneously by two or more threads.
|
|
|
|
*
|
|
|
|
* PUBLIC: int __api_step __P((dbsql_stmt_t *, int *, const char ***,
|
|
|
|
* PUBLIC: const char ***));
|
|
|
|
*
|
|
|
|
* p The virtual machine to execute
|
|
|
|
* n OUT: Number of columns in result
|
|
|
|
* values OUT: Column data
|
|
|
|
* col_names OUT: Column names and datatypes
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
__api_step(p, n, values, col_names)
|
|
|
|
dbsql_stmt_t *p;
|
|
|
|
int *n;
|
|
|
|
const char ***values;
|
|
|
|
const char ***col_names;
|
|
|
|
{
|
|
|
|
vdbe_t *vm = (vdbe_t*)p;
|
|
|
|
DBSQL *db;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
if (vm->magic != VDBE_MAGIC_RUN) {
|
|
|
|
return DBSQL_MISUSE;
|
|
|
|
}
|
|
|
|
db = vm->db;
|
|
|
|
if (__safety_on(db)) {
|
|
|
|
return DBSQL_MISUSE;
|
|
|
|
}
|
|
|
|
if (vm->explain) {
|
|
|
|
rc = __vdbe_list(vm);
|
|
|
|
} else {
|
|
|
|
rc = __vdbe_exec(vm);
|
|
|
|
}
|
|
|
|
if (rc == DBSQL_DONE || rc == DBSQL_ROW) {
|
|
|
|
if (col_names)
|
|
|
|
*col_names = (const char**)vm->azColName;
|
|
|
|
if (n)
|
|
|
|
*n = vm->nResColumn;
|
|
|
|
} else {
|
|
|
|
if (col_names)
|
|
|
|
*col_names = 0;
|
|
|
|
if (n)
|
|
|
|
*n = 0;
|
|
|
|
}
|
|
|
|
if (values) {
|
|
|
|
if (rc == DBSQL_ROW) {
|
|
|
|
*values = (const char**)vm->azResColumn;
|
|
|
|
} else {
|
|
|
|
*values = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (__safety_off(db)) {
|
|
|
|
return DBSQL_MISUSE;
|
|
|
|
}
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* __agg_insert --
|
|
|
|
* Insert a new aggregate element and make it the element that
|
|
|
|
* has focus.
|
|
|
|
* Return 0 on success and 1 if memory is exhausted.
|
|
|
|
*
|
|
|
|
* STATIC: static int __agg_insert __P((agg_t *, char *, int));
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
__agg_insert(p, key, len)
|
|
|
|
agg_t *p;
|
|
|
|
char *key;
|
|
|
|
int len;
|
|
|
|
{
|
|
|
|
agg_elem_t *elem, *old;
|
|
|
|
int i;
|
|
|
|
mem_t *mem;
|
|
|
|
|
|
|
|
if (__dbsql_calloc(NULL, 1, sizeof(agg_elem_t) + len +
|
|
|
|
((p->nMem - 1) * sizeof(elem->aMem[0])),
|
|
|
|
&elem) == ENOMEM)
|
|
|
|
return 1;
|
|
|
|
elem->zKey = (char*)&elem->aMem[p->nMem];
|
|
|
|
memcpy(elem->zKey, key, len);
|
|
|
|
elem->nKey = len;
|
|
|
|
old = __hash_insert(&p->hash, elem->zKey, elem->nKey, elem);
|
|
|
|
if (old != 0) {
|
|
|
|
DBSQL_ASSERT(old == elem); /* Malloc failed on insert */
|
|
|
|
__dbsql_free(NULL, old);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
for (i = 0, mem = elem->aMem; i < p->nMem; i++, mem++) {
|
|
|
|
mem->flags = MEM_Null;
|
|
|
|
}
|
|
|
|
p->pCurrent = elem;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* __agg_in_focus --
|
|
|
|
* Get the agg_elem_t currently in focus
|
|
|
|
*
|
|
|
|
* STATIC: static agg_elem_t *__agg_in_focus __P((agg_t *p));
|
|
|
|
*/
|
|
|
|
static agg_elem_t *
|
|
|
|
__agg_in_focus(p)
|
|
|
|
agg_t *p;
|
|
|
|
{
|
|
|
|
if (p->pCurrent) {
|
|
|
|
return p->pCurrent;
|
|
|
|
} else {
|
|
|
|
hash_ele_t *elem;
|
|
|
|
elem = __hash_first(&p->hash);
|
|
|
|
if (elem == 0) {
|
|
|
|
__agg_insert(p,"",1);
|
|
|
|
elem = __hash_first(&p->hash);
|
|
|
|
}
|
|
|
|
return elem ? __hash_data(elem) : 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* __entity_as_string --
|
|
|
|
* Convert the given stack entity into a string if it isn't one
|
|
|
|
* already.
|
|
|
|
*
|
|
|
|
* STATIC: static int __entity_as_string __P((mem_t *));
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
__entity_as_string(stack)
|
|
|
|
mem_t *stack;
|
|
|
|
{
|
|
|
|
if ((stack->flags & MEM_Str) == 0) {
|
|
|
|
int fg = stack->flags;
|
|
|
|
if (fg & MEM_Real) {
|
|
|
|
snprintf(stack->zShort, sizeof(stack->zShort),
|
|
|
|
"%.15g", stack->r);
|
|
|
|
} else if (fg & MEM_Int) {
|
|
|
|
snprintf(stack->zShort, sizeof(stack->zShort),
|
|
|
|
"%d",stack->i);
|
|
|
|
} else {
|
|
|
|
stack->zShort[0] = 0;
|
|
|
|
}
|
|
|
|
stack->z = stack->zShort;
|
|
|
|
stack->n = strlen(stack->zShort) + 1;
|
|
|
|
stack->flags = MEM_Str | MEM_Short;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* __entity_to_string --
|
|
|
|
* Convert the given stack entity into a string that has been obtained
|
|
|
|
* from __dbsql_calloc(). This is different from __entity_as_string()
|
|
|
|
* above in that __entity_as_string() will use the NBFS bytes of static
|
|
|
|
* string space if the string will fit but this routine will always
|
|
|
|
* __dbsql_malloc new memory. Return non-zero if we run out of memory.
|
|
|
|
*
|
|
|
|
* STATIC: int __entity_to_string __P((mem_t *));
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
__entity_to_string(stack)
|
|
|
|
mem_t *stack;
|
|
|
|
{
|
|
|
|
if ((stack->flags & MEM_Dyn) == 0) {
|
|
|
|
int fg = stack->flags;
|
|
|
|
char *z;
|
|
|
|
__entity_as_string(stack);
|
|
|
|
DBSQL_ASSERT((fg & MEM_Dyn) == 0);
|
|
|
|
if (__dbsql_calloc(NULL, 1, stack->n, &z) == ENOMEM)
|
|
|
|
return 1;
|
|
|
|
memcpy(z, stack->z, stack->n);
|
|
|
|
stack->z = z;
|
|
|
|
stack->flags |= MEM_Dyn;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* __entity_ephem_to_dyn --
|
|
|
|
* An ephemeral string value (signified by the MEM_Ephem flag) contains
|
|
|
|
* a pointer to a dynamically allocated string where some other entity
|
|
|
|
* is responsible for deallocating that string. Because the stack entry
|
|
|
|
* does not control the string, it might be deleted without the stack
|
|
|
|
* entry knowing it.
|
|
|
|
*
|
|
|
|
* This routine converts an ephemeral string into a dynamically allocated
|
|
|
|
* string that the stack entry itself controls. In other words, it
|
|
|
|
* converts an MEM_Ephem string into an MEM_Dyn string.
|
|
|
|
*
|
|
|
|
* STATIC: static int __entity_ephem_to_dyn __P((mem_t * stack));
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
__entity_ephem_to_dyn(stack)
|
|
|
|
mem_t *stack;
|
|
|
|
{
|
|
|
|
if ((stack->flags & MEM_Ephem) != 0) {
|
|
|
|
char *z;
|
|
|
|
if (__dbsql_calloc(NULL, 1, stack->n, &z) == ENOMEM)
|
|
|
|
return 1;
|
|
|
|
memcpy(z, stack->z, stack->n);
|
|
|
|
stack->z = z;
|
|
|
|
stack->flags &= ~MEM_Ephem;
|
|
|
|
stack->flags |= MEM_Dyn;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* __entity_release_mem --
|
|
|
|
* Release the memory associated with the given stack level. This
|
|
|
|
* leaves the mem_t.flags field in an inconsistent state.
|
|
|
|
*
|
|
|
|
* STATIC: static void __entity_release_mem __P((mem_t *));
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
__entity_release_mem(stack)
|
|
|
|
mem_t *stack;
|
|
|
|
{
|
|
|
|
if ((stack->flags & MEM_Dyn) != 0) {
|
|
|
|
__dbsql_free(NULL, stack->z);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* __pop_stack --
|
|
|
|
* Pop the stack N times.
|
|
|
|
*
|
|
|
|
* STATIC: void __pop_stack __P((mem_t **, int));
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
__pop_stack(stack, n)
|
|
|
|
mem_t **stack;
|
|
|
|
int n;
|
|
|
|
{
|
|
|
|
mem_t *tos = *stack;
|
|
|
|
while(n > 0) {
|
|
|
|
n--;
|
|
|
|
__entity_release_mem(tos);
|
|
|
|
tos--;
|
|
|
|
}
|
|
|
|
*stack = tos;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* __entity_to_int --
|
|
|
|
* Convert the given stack entity into a integer if it isn't one
|
|
|
|
* already. Any prior string or real representation is invalidated.
|
|
|
|
* NULLs are converted into 0.
|
|
|
|
*
|
|
|
|
* STATIC: static void __entity_to_int __P((mem_t *));
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
__entity_to_int(stack)
|
|
|
|
mem_t *stack;
|
|
|
|
{
|
|
|
|
if ((stack->flags & MEM_Int) == 0) {
|
|
|
|
if (stack->flags & MEM_Real) {
|
|
|
|
stack->i = (int)stack->r;
|
|
|
|
__entity_release_mem(stack);
|
|
|
|
} else if (stack->flags & MEM_Str) {
|
|
|
|
__dbsql_atoi(stack->z, &stack->i);
|
|
|
|
__entity_release_mem(stack);
|
|
|
|
} else {
|
|
|
|
stack->i = 0;
|
|
|
|
}
|
|
|
|
stack->flags = MEM_Int;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* __entity_to_real --
|
|
|
|
* Get a valid Real representation for the given stack element.
|
|
|
|
* Any prior string or integer representation is retained.
|
|
|
|
* NULLs are converted into 0.0.
|
|
|
|
*
|
|
|
|
* STATIC: static void __entity_to_real __P((mem_t *));
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
__entity_to_real(stack)
|
|
|
|
mem_t *stack;
|
|
|
|
{
|
|
|
|
if ((stack->flags & MEM_Real) == 0) {
|
|
|
|
if (stack->flags & MEM_Str) {
|
|
|
|
stack->r = __dbsql_atof(stack->z);
|
|
|
|
} else if (stack->flags & MEM_Int) {
|
|
|
|
stack->r = stack->i;
|
|
|
|
} else {
|
|
|
|
stack->r = 0.0;
|
|
|
|
}
|
|
|
|
stack->flags |= MEM_Real;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* __sorted_merge --
|
|
|
|
* The parameters are pointers to the head of two sorted lists
|
|
|
|
* of sorter_t structures. Merge these two lists together and return
|
|
|
|
* a single sorted list. This routine forms the core of the merge-sort
|
|
|
|
* algorithm. In the case of a tie, left sorts in front of right.
|
|
|
|
*
|
|
|
|
* STATIC: static sorter_t * __sorted_merge __P((sorter_t *, sorter_t *));
|
|
|
|
*/
|
|
|
|
static sorter_t *
|
|
|
|
__sorted_merge(left, right)
|
|
|
|
sorter_t *left;
|
|
|
|
sorter_t *right;
|
|
|
|
{
|
|
|
|
sorter_t head;
|
|
|
|
sorter_t *tail;
|
|
|
|
tail = &head;
|
|
|
|
tail->pNext = 0;
|
|
|
|
while(left && right) {
|
|
|
|
int c = __str_cmp(left->zKey, right->zKey);
|
|
|
|
if (c <= 0) {
|
|
|
|
tail->pNext = left;
|
|
|
|
left = left->pNext;
|
|
|
|
} else {
|
|
|
|
tail->pNext = right;
|
|
|
|
right = right->pNext;
|
|
|
|
}
|
|
|
|
tail = tail->pNext;
|
|
|
|
}
|
|
|
|
if (left) {
|
|
|
|
tail->pNext = left;
|
|
|
|
} else if (right) {
|
|
|
|
tail->pNext = right;
|
|
|
|
}
|
|
|
|
return head.pNext;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* __fgets --
|
|
|
|
* The following routine works like a replacement for the standard
|
|
|
|
* library routine fgets(). The difference is in how end-of-line (EOL)
|
|
|
|
* is handled. Standard fgets() uses LF for EOL under unix, CRLF
|
|
|
|
* under windows, and CR under mac. This routine accepts any of these
|
|
|
|
* character sequences as an EOL mark. The EOL mark is replaced by
|
|
|
|
* a single LF character in zBuf.
|
|
|
|
*
|
|
|
|
* STATIC: static char *__fgets __P((char *, int, FILE *));
|
|
|
|
*/
|
|
|
|
static char *
|
|
|
|
__fgets(buf, len, in)
|
|
|
|
char *buf;
|
|
|
|
int len;
|
|
|
|
FILE *in;
|
|
|
|
{
|
|
|
|
int i, c;
|
|
|
|
for(i = 0; i < (len - 1) && (c = getc(in)) != EOF; i++) {
|
|
|
|
buf[i] = c;
|
|
|
|
if (c == '\r' || c == '\n') {
|
|
|
|
if (c == '\r') {
|
|
|
|
buf[i] = '\n';
|
|
|
|
c = getc(in);
|
|
|
|
if (c != EOF && c != '\n')
|
|
|
|
ungetc(c, in);
|
|
|
|
}
|
|
|
|
i++;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
buf[i] = 0;
|
|
|
|
return (i > 0 ? buf : 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* __expand_cursor_array_size --
|
|
|
|
* Make sure there is space in the vdbe_t structure to hold at least
|
|
|
|
* 'num_cursors' cursors. If there is not currently enough space, then
|
|
|
|
* allocate more. If a memory allocation error occurs, return 1.
|
|
|
|
* Return 0 if everything works.
|
|
|
|
*
|
|
|
|
* STATIC: static int __expand_cursor_array_size __P((vdbe_t *, int));
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
__expand_cursor_array_size(vm, num_cursors)
|
|
|
|
vdbe_t *vm;
|
|
|
|
int num_cursors;
|
|
|
|
{
|
|
|
|
if (num_cursors >= vm->nCursor) {
|
|
|
|
if (__dbsql_realloc(NULL, ((num_cursors + 1) *
|
|
|
|
sizeof(cursor_t)), &vm->aCsr) == ENOMEM)
|
|
|
|
return 1;
|
|
|
|
memset(&vm->aCsr[vm->nCursor], 0,
|
|
|
|
(sizeof(cursor_t) * (num_cursors + 1 - vm->nCursor)));
|
|
|
|
vm->nCursor = num_cursors + 1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The CHECK_FOR_INTERRUPT macro defined here looks to see if the
|
|
|
|
* DBSQL->interrupt() routine has been called. If it has been, then
|
|
|
|
* processing of the VDBE program is interrupted.
|
|
|
|
*
|
|
|
|
* This macro added to every instruction that does a jump in order to
|
|
|
|
* implement a loop. This test used to be on every single instruction,
|
|
|
|
* but that meant we had more testing that we needed. By only testing the
|
|
|
|
* flag on jump instructions, we get a (small) speed improvement.
|
|
|
|
*/
|
|
|
|
#define CHECK_FOR_INTERRUPT \
|
|
|
|
if( db->flags & DBSQL_Interrupt ) goto abort_due_to_interrupt;
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* __vdbe_exec --
|
|
|
|
* Execute as much of a VDBE program as we can then return.
|
|
|
|
*
|
|
|
|
* __vdbe_make_ready() must be called before this routine in order to
|
|
|
|
* close the program with a final OP_Halt and to set up the callbacks
|
|
|
|
* and the error message pointer.
|
|
|
|
*
|
|
|
|
* Whenever a row or result data is available, this routine will either
|
|
|
|
* invoke the result callback (if there is one) or return with
|
|
|
|
* DBSQL_ROW.
|
|
|
|
*
|
|
|
|
* If an attempt is made to open a locked database, then this routine
|
|
|
|
* will either invoke the busy callback (if there is one) or it will
|
|
|
|
* return DBSQL_BUSY.
|
|
|
|
*
|
|
|
|
* If an error occurs, an error message is written to memory obtained
|
|
|
|
* from __dbsql_calloc() and p->zErrMsg is made to point to that memory.
|
|
|
|
* The error code is stored in p->rc and this routine returns DBSQL_ERROR.
|
|
|
|
*
|
|
|
|
* If the callback ever returns non-zero, then the program exits
|
|
|
|
* immediately. There will be no error message but the p->rc field is
|
|
|
|
* set to DBSQL_ABORT and this routine will return DBSQL_ERROR.
|
|
|
|
*
|
|
|
|
* A memory allocation error causes p->rc to be set to DBSQL_NOMEM and
|
|
|
|
* this routine to return DBSQL_ERROR.
|
|
|
|
*
|
|
|
|
* Other fatal errors return DBSQL_ERROR.
|
|
|
|
*
|
|
|
|
* After this routine has finished, __vdbe_finalize() should be
|
|
|
|
* used to clean up the mess that was left behind.
|
|
|
|
*
|
|
|
|
* PUBLIC: int __vdbe_exec __P((vdbe_t *));
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
__vdbe_exec(p)
|
|
|
|
vdbe_t *p;
|
|
|
|
{
|
|
|
|
int pc; /* The program counter */
|
|
|
|
vdbe_op_t *pOp; /* Current operation */
|
|
|
|
int rc = DBSQL_SUCCESS; /* Value to return */
|
|
|
|
DBSQL *db = p->db; /* The database */
|
|
|
|
mem_t *pTos; /* Top entry in the operand stack */
|
|
|
|
#define BUF_SIZE 100
|
|
|
|
char zBuf[BUF_SIZE]; /* Space to sprintf() an integer */
|
|
|
|
#ifdef VDBE_PROFILE
|
|
|
|
unsigned long long start; /* CPU clock count at start of opcode */
|
|
|
|
int origPc; /* Program counter at start of opcode */
|
|
|
|
#endif
|
|
|
|
#ifndef DBSQL_NO_PROGRESS
|
|
|
|
int nProgressOps = 0; /* Opcodes executed since progress
|
|
|
|
callback. */
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (p->magic != VDBE_MAGIC_RUN)
|
|
|
|
return DBSQL_MISUSE;
|
|
|
|
DBSQL_ASSERT(db->magic == DBSQL_STATUS_BUSY);
|
|
|
|
DBSQL_ASSERT(p->rc == DBSQL_SUCCESS || p->rc == DBSQL_BUSY);
|
|
|
|
p->rc = DBSQL_SUCCESS;
|
|
|
|
DBSQL_ASSERT(p->explain == 0);
|
|
|
|
if (p->rc == ENOMEM)
|
|
|
|
goto no_mem;
|
|
|
|
pTos = p->pTos;
|
|
|
|
if (p->popStack) {
|
|
|
|
__pop_stack(&pTos, p->popStack);
|
|
|
|
p->popStack = 0;
|
|
|
|
}
|
|
|
|
for(pc = p->pc; rc == DBSQL_SUCCESS; pc++) {
|
|
|
|
DBSQL_ASSERT(pc >= 0 && pc < p->nOp);
|
|
|
|
DBSQL_ASSERT(pTos <= &p->aStack[pc]);
|
|
|
|
#ifdef VDBE_PROFILE
|
|
|
|
origPc = pc;
|
|
|
|
start = __os_hwtime();
|
|
|
|
#endif
|
|
|
|
pOp = &p->aOp[pc];
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Only allow tracing if NDEBUG is not defined.
|
|
|
|
* TODO, DIAGNOSTIC?
|
|
|
|
*/
|
|
|
|
#ifndef NDEBUG
|
|
|
|
if (p->trace) {
|
|
|
|
__vdbe_print_op(p->trace, pc, pOp);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Check to see if we need to simulate an interrupt. This
|
|
|
|
* only happens if we have a special test build.
|
|
|
|
*/
|
|
|
|
#ifdef CONFIG_TEST
|
|
|
|
if (dbsql_interrupt_count > 0) {
|
|
|
|
dbsql_interrupt_count--;
|
|
|
|
if (dbsql_interrupt_count == 0) {
|
|
|
|
db->interrupt(db);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifndef DBSQL_NO_PROGRESS
|
|
|
|
/*
|
|
|
|
* Call the progress callback if it is configured and the
|
|
|
|
* required number of VDBE ops have been executed (either
|
|
|
|
* since this invocation of __vdbe_exec() or since last time
|
|
|
|
* the progress callback was called). If the progress
|
|
|
|
* callback returns non-zero, exit the virtual machine with
|
|
|
|
* a return code DBSQL_ABORT.
|
|
|
|
*/
|
|
|
|
if (db->xProgress) {
|
|
|
|
if (db->nProgressOps == nProgressOps) {
|
|
|
|
if (db->xProgress(db->pProgressArg) != 0) {
|
|
|
|
rc = DBSQL_ABORT;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
nProgressOps = 0;
|
|
|
|
}
|
|
|
|
nProgressOps++;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
switch(pOp->opcode) {
|
|
|
|
|
|
|
|
/*****************************************************************************
|
|
|
|
* What follows is a massive switch statement where each case implements a
|
|
|
|
* separate instruction in the virtual machine. If we follow the usual
|
|
|
|
* indentation conventions, each case should be indented by 6 spaces. But
|
|
|
|
* that is a lot of wasted space on the left margin. So the code within
|
|
|
|
* the switch statement will break with convention and be flush-left. Another
|
|
|
|
* big comment (similar to this one) will mark the point in the code where
|
|
|
|
* we transition back to normal indentation.
|
|
|
|
*
|
|
|
|
* The formatting of each case is important. The makefile generates
|
|
|
|
* two C files "opcodes.h" and "opcodes.c" by scanning this file looking
|
|
|
|
* for lines that begin with "case OP_". The opcodes.h files
|
|
|
|
* will be filled with #defines that give unique integer values to each
|
|
|
|
* opcode and the opcodes.c file is filled with an array of strings where
|
|
|
|
* each string is the symbolic name for the corresponding opcode.
|
|
|
|
*
|
|
|
|
* Documentation about VDBE opcodes is generated by scanning this file
|
|
|
|
* for lines of that contain "Opcode:". That line and all subsequent
|
|
|
|
* comment lines are used in the generation of the opcode.html documentation
|
|
|
|
* file.
|
|
|
|
*
|
|
|
|
* SUMMARY:
|
|
|
|
*
|
|
|
|
* Formatting is important to scripts that scan this file.
|
|
|
|
* Do not deviate from the formatting style currently in use.
|
|
|
|
*
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
|
|
/* Opcode: Goto * P2 *
|
|
|
|
**
|
|
|
|
** An unconditional jump to address P2.
|
|
|
|
** The next instruction executed will be
|
|
|
|
** the one at index P2 from the beginning of
|
|
|
|
** the program.
|
|
|
|
*/
|
|
|
|
case OP_Goto: {
|
|
|
|
CHECK_FOR_INTERRUPT;
|
|
|
|
pc = pOp->p2 - 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: Gosub * P2 *
|
|
|
|
**
|
|
|
|
** Push the current address plus 1 onto the return address stack
|
|
|
|
** and then jump to address P2.
|
|
|
|
**
|
|
|
|
** The return address stack is of limited depth. If too many
|
|
|
|
** OP_Gosub operations occur without intervening OP_Returns, then
|
|
|
|
** the return address stack will fill up and processing will abort
|
|
|
|
** with a fatal error.
|
|
|
|
*/
|
|
|
|
case OP_Gosub: {
|
|
|
|
if (p->returnDepth >=
|
|
|
|
(sizeof(p->returnStack) / sizeof(p->returnStack[0]))) {
|
|
|
|
__str_append(&p->zErrMsg, "return address stack overflow",
|
|
|
|
(char*)0);
|
|
|
|
p->rc = DBSQL_INTERNAL;
|
|
|
|
return DBSQL_ERROR;
|
|
|
|
}
|
|
|
|
p->returnStack[p->returnDepth++] = pc+1;
|
|
|
|
pc = pOp->p2 - 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: Return * * *
|
|
|
|
**
|
|
|
|
** Jump immediately to the next instruction after the last unreturned
|
|
|
|
** OP_Gosub. If an OP_Return has occurred for all OP_Gosubs, then
|
|
|
|
** processing aborts with a fatal error.
|
|
|
|
*/
|
|
|
|
case OP_Return: {
|
|
|
|
if (p->returnDepth <= 0) {
|
|
|
|
__str_append(&p->zErrMsg, "return address stack underflow",
|
|
|
|
(char*)0);
|
|
|
|
p->rc = DBSQL_INTERNAL;
|
|
|
|
return DBSQL_ERROR;
|
|
|
|
}
|
|
|
|
p->returnDepth--;
|
|
|
|
pc = p->returnStack[p->returnDepth] - 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: Halt P1 P2 *
|
|
|
|
**
|
|
|
|
** Exit immediately. All open cursors, Lists, Sorts, etc are closed
|
|
|
|
** automatically.
|
|
|
|
**
|
|
|
|
** P1 is the result code returned by dbsql_exec(). For a normal
|
|
|
|
** halt, this should be DBSQL_SUCCESS (0). For errors, it can be some
|
|
|
|
** other value. If P1!=0 then P2 will determine whether or not to
|
|
|
|
** rollback the current transaction. Do not rollback if P2==OE_Fail.
|
|
|
|
** Do the rollback if P2==OE_Rollback. If P2==OE_Abort, then back
|
|
|
|
** out all changes that have occurred during this execution of the
|
|
|
|
** VDBE, but do not rollback the transaction.
|
|
|
|
**
|
|
|
|
** There is an implied "Halt 0 0 0" instruction inserted at the very end of
|
|
|
|
** every program. So a jump past the last instruction of the program
|
|
|
|
** is the same as executing Halt.
|
|
|
|
*/
|
|
|
|
case OP_Halt: {
|
|
|
|
p->magic = VDBE_MAGIC_HALT;
|
|
|
|
p->pTos = pTos;
|
|
|
|
if (pOp->p1 != DBSQL_SUCCESS) {
|
|
|
|
p->rc = pOp->p1;
|
|
|
|
p->errorAction = pOp->p2;
|
|
|
|
if (pOp->p3) {
|
|
|
|
__str_append(&p->zErrMsg, pOp->p3, (char*)0);
|
|
|
|
}
|
|
|
|
return DBSQL_ERROR;
|
|
|
|
} else {
|
|
|
|
p->rc = DBSQL_SUCCESS;
|
|
|
|
return DBSQL_DONE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: Integer P1 * P3
|
|
|
|
**
|
|
|
|
** The integer value P1 is pushed onto the stack. If P3 is not zero
|
|
|
|
** then it is assumed to be a string representation of the same integer.
|
|
|
|
*/
|
|
|
|
case OP_Integer: {
|
|
|
|
pTos++;
|
|
|
|
pTos->i = pOp->p1;
|
|
|
|
pTos->flags = MEM_Int;
|
|
|
|
if (pOp->p3) {
|
|
|
|
pTos->z = pOp->p3;
|
|
|
|
pTos->flags |= MEM_Str | MEM_Static;
|
|
|
|
pTos->n = strlen(pOp->p3) + 1;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: String * * P3
|
|
|
|
**
|
|
|
|
** The string value P3 is pushed onto the stack. If P3==0 then a
|
|
|
|
** NULL is pushed onto the stack.
|
|
|
|
*/
|
|
|
|
case OP_String: {
|
|
|
|
char *z = pOp->p3;
|
|
|
|
pTos++;
|
|
|
|
if( z==0 ){
|
|
|
|
pTos->flags = MEM_Null;
|
|
|
|
} else {
|
|
|
|
pTos->z = z;
|
|
|
|
pTos->n = strlen(z) + 1;
|
|
|
|
pTos->flags = MEM_Str | MEM_Static;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: Variable P1 * *
|
|
|
|
**
|
|
|
|
** Push the value of variable P1 onto the stack. A variable is
|
|
|
|
** an unknown in the original SQL string as handed to DBSQL->prepare().
|
|
|
|
** Any occurance of the '?' character in the original SQL is considered
|
|
|
|
** a variable. Variables in the SQL string are number from left to
|
|
|
|
** right beginning with 1. The values of variables are set using the
|
|
|
|
** dbsql_bind() API.
|
|
|
|
*/
|
|
|
|
case OP_Variable: {
|
|
|
|
int j = pOp->p1 - 1;
|
|
|
|
pTos++;
|
|
|
|
if (j >= 0 && j < p->nVar && p->azVar[j] != 0) {
|
|
|
|
pTos->z = p->azVar[j];
|
|
|
|
pTos->n = p->anVar[j];
|
|
|
|
pTos->flags = MEM_Str | MEM_Static;
|
|
|
|
} else {
|
|
|
|
pTos->flags = MEM_Null;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: Pop P1 * *
|
|
|
|
**
|
|
|
|
** P1 elements are popped off of the top of stack and discarded.
|
|
|
|
*/
|
|
|
|
case OP_Pop: {
|
|
|
|
DBSQL_ASSERT(pOp->p1 >= 0);
|
|
|
|
__pop_stack(&pTos, pOp->p1);
|
|
|
|
DBSQL_ASSERT(pTos >= &p->aStack[-1]);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: Dup P1 P2 *
|
|
|
|
**
|
|
|
|
** A copy of the P1-th element of the stack
|
|
|
|
** is made and pushed onto the top of the stack.
|
|
|
|
** The top of the stack is element 0. So the
|
|
|
|
** instruction "Dup 0 0 0" will make a copy of the
|
|
|
|
** top of the stack.
|
|
|
|
**
|
|
|
|
** If the content of the P1-th element is a dynamically
|
|
|
|
** allocated string, then a new copy of that string
|
|
|
|
** is made if P2==0. If P2!=0, then just a pointer
|
|
|
|
** to the string is copied.
|
|
|
|
**
|
|
|
|
** Also see the Pull instruction.
|
|
|
|
*/
|
|
|
|
case OP_Dup: {
|
|
|
|
mem_t *pFrom = &pTos[-pOp->p1];
|
|
|
|
DBSQL_ASSERT(pFrom <= pTos && pFrom >= p->aStack);
|
|
|
|
pTos++;
|
|
|
|
memcpy(pTos, pFrom, (sizeof(*pFrom) - NBFS));
|
|
|
|
if (pTos->flags & MEM_Str) {
|
|
|
|
if (pOp->p2 && (pTos->flags & (MEM_Dyn | MEM_Ephem))) {
|
|
|
|
pTos->flags &= ~MEM_Dyn;
|
|
|
|
pTos->flags |= MEM_Ephem;
|
|
|
|
} else if (pTos->flags & MEM_Short) {
|
|
|
|
memcpy(pTos->zShort, pFrom->zShort, pTos->n);
|
|
|
|
pTos->z = pTos->zShort;
|
|
|
|
} else if ((pTos->flags & MEM_Static) == 0) {
|
|
|
|
if (__dbsql_calloc(NULL, 1, pFrom->n,
|
|
|
|
&pTos->z) == ENOMEM)
|
|
|
|
goto no_mem;
|
|
|
|
memcpy(pTos->z, pFrom->z, pFrom->n);
|
|
|
|
pTos->flags &= ~(MEM_Static | MEM_Ephem | MEM_Short);
|
|
|
|
pTos->flags |= MEM_Dyn;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: Pull P1 * *
|
|
|
|
**
|
|
|
|
** The P1-th element is removed from its current location on
|
|
|
|
** the stack and pushed back on top of the stack. The
|
|
|
|
** top of the stack is element 0, so "Pull 0 0 0" is
|
|
|
|
** a no-op. "Pull 1 0 0" swaps the top two elements of
|
|
|
|
** the stack.
|
|
|
|
**
|
|
|
|
** See also the Dup instruction.
|
|
|
|
*/
|
|
|
|
case OP_Pull: {
|
|
|
|
mem_t *pFrom = &pTos[-pOp->p1];
|
|
|
|
int i;
|
|
|
|
mem_t ts;
|
|
|
|
|
|
|
|
ts = *pFrom;
|
|
|
|
if (__entity_ephem_to_dyn(pTos) == 1) {
|
|
|
|
goto no_mem;
|
|
|
|
}
|
|
|
|
for (i = 0; i < pOp->p1; i++, pFrom++) {
|
|
|
|
if (__entity_ephem_to_dyn(&pFrom[1]) == 1) {
|
|
|
|
goto no_mem;
|
|
|
|
}
|
|
|
|
*pFrom = pFrom[1];
|
|
|
|
DBSQL_ASSERT((pFrom->flags & MEM_Ephem) == 0);
|
|
|
|
if (pFrom->flags & MEM_Short) {
|
|
|
|
DBSQL_ASSERT(pFrom->flags & MEM_Str);
|
|
|
|
DBSQL_ASSERT(pFrom->z == pFrom[1].zShort);
|
|
|
|
pFrom->z = pFrom->zShort;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
*pTos = ts;
|
|
|
|
if (pTos->flags & MEM_Short) {
|
|
|
|
DBSQL_ASSERT(pTos->flags & MEM_Str);
|
|
|
|
DBSQL_ASSERT(pTos->z == pTos[-pOp->p1].zShort);
|
|
|
|
pTos->z = pTos->zShort;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: Push P1 * *
|
|
|
|
**
|
|
|
|
** Overwrite the value of the P1-th element down on the
|
|
|
|
** stack (P1==0 is the top of the stack) with the value
|
|
|
|
** of the top of the stack. Then pop the top of the stack.
|
|
|
|
*/
|
|
|
|
case OP_Push: {
|
|
|
|
mem_t *pTo = &pTos[-pOp->p1];
|
|
|
|
|
|
|
|
DBSQL_ASSERT(pTo >= p->aStack);
|
|
|
|
if (__entity_ephem_to_dyn(pTos) == 1) {
|
|
|
|
goto no_mem;
|
|
|
|
}
|
|
|
|
__entity_release_mem(pTo);
|
|
|
|
*pTo = *pTos;
|
|
|
|
if (pTo->flags & MEM_Short) {
|
|
|
|
DBSQL_ASSERT(pTo->z == pTos->zShort);
|
|
|
|
pTo->z = pTo->zShort;
|
|
|
|
}
|
|
|
|
pTos--;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Opcode: ColumnName P1 * P3
|
|
|
|
**
|
|
|
|
** P3 becomes the P1-th column name (first is 0). An array of pointers
|
|
|
|
** to all column names is passed as the 4th parameter to the callback.
|
|
|
|
*/
|
|
|
|
case OP_ColumnName: {
|
|
|
|
DBSQL_ASSERT(pOp->p1 >= 0 && pOp->p1 < p->nOp);
|
|
|
|
p->azColName[pOp->p1] = pOp->p3;
|
|
|
|
p->nCallback = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: Callback P1 * *
|
|
|
|
**
|
|
|
|
** Pop P1 values off the stack and form them into an array. Then
|
|
|
|
** invoke the callback function using the newly formed array as the
|
|
|
|
** 3rd parameter.
|
|
|
|
*/
|
|
|
|
case OP_Callback: {
|
|
|
|
int i;
|
|
|
|
char **azArgv = p->zArgv;
|
|
|
|
mem_t *pCol;
|
|
|
|
|
|
|
|
pCol = &pTos[1 - pOp->p1];
|
|
|
|
DBSQL_ASSERT(pCol >= p->aStack);
|
|
|
|
for (i = 0; i < pOp->p1; i++, pCol++) {
|
|
|
|
if (pCol->flags & MEM_Null) {
|
|
|
|
azArgv[i] = 0;
|
|
|
|
} else {
|
|
|
|
__entity_as_string(pCol);
|
|
|
|
azArgv[i] = pCol->z;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
azArgv[i] = 0;
|
|
|
|
if (p->xCallback == 0) {
|
|
|
|
p->azResColumn = azArgv;
|
|
|
|
p->nResColumn = pOp->p1;
|
|
|
|
p->popStack = pOp->p1;
|
|
|
|
p->pc = pc + 1;
|
|
|
|
p->pTos = pTos;
|
|
|
|
return DBSQL_ROW;
|
|
|
|
}
|
|
|
|
if (__safety_off(db))
|
|
|
|
goto abort_due_to_misuse;
|
|
|
|
if (p->xCallback(p->pCbArg, pOp->p1, azArgv, p->azColName) != 0) {
|
|
|
|
rc = DBSQL_ABORT;
|
|
|
|
}
|
|
|
|
if (__safety_on(db))
|
|
|
|
goto abort_due_to_misuse;
|
|
|
|
p->nCallback++;
|
|
|
|
__pop_stack(&pTos, pOp->p1);
|
|
|
|
DBSQL_ASSERT(pTos >= &p->aStack[-1]);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: NullCallback P1 * *
|
|
|
|
**
|
|
|
|
** Invoke the callback function once with the 2nd argument (the
|
|
|
|
** number of columns) equal to P1 and with the 4th argument (the
|
|
|
|
** names of the columns) set according to prior OP_ColumnName
|
|
|
|
** instructions. This is all like the regular
|
|
|
|
** OP_Callback or OP_SortCallback opcodes. But the 3rd argument
|
|
|
|
** which normally contains a pointer to an array of pointers to
|
|
|
|
** data is NULL.
|
|
|
|
**
|
|
|
|
** The callback is only invoked if there have been no prior calls
|
|
|
|
** to OP_Callback or OP_SortCallback.
|
|
|
|
**
|
|
|
|
** This opcode is used to report the number and names of columns
|
|
|
|
** in cases where the result set is empty.
|
|
|
|
*/
|
|
|
|
case OP_NullCallback: {
|
|
|
|
if (p->nCallback == 0 && p->xCallback != 0) {
|
|
|
|
if (__safety_off(db))
|
|
|
|
goto abort_due_to_misuse;
|
|
|
|
if (p->xCallback(p->pCbArg, pOp->p1, 0, p->azColName) != 0) {
|
|
|
|
rc = DBSQL_ABORT;
|
|
|
|
}
|
|
|
|
if (__safety_on(db))
|
|
|
|
goto abort_due_to_misuse;
|
|
|
|
p->nCallback++;
|
|
|
|
}
|
|
|
|
p->nResColumn = pOp->p1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: Concat P1 P2 P3
|
|
|
|
**
|
|
|
|
** Look at the first P1 elements of the stack. Append them all
|
|
|
|
** together with the lowest element first. Use P3 as a separator.
|
|
|
|
** Put the result on the top of the stack. The original P1 elements
|
|
|
|
** are popped from the stack if P2==0 and retained if P2==1. If
|
|
|
|
** any element of the stack is NULL, then the result is NULL.
|
|
|
|
**
|
|
|
|
** If P3 is NULL, then use no separator. When P1==1, this routine
|
|
|
|
** makes a copy of the top stack element into memory obtained
|
|
|
|
** from __dbsql_calloc().
|
|
|
|
*/
|
|
|
|
case OP_Concat: {
|
|
|
|
char *zNew;
|
|
|
|
int nByte;
|
|
|
|
int nField;
|
|
|
|
int i, j;
|
|
|
|
char *zSep;
|
|
|
|
int nSep;
|
|
|
|
mem_t *pTerm;
|
|
|
|
|
|
|
|
nField = pOp->p1;
|
|
|
|
zSep = pOp->p3;
|
|
|
|
if (zSep == 0)
|
|
|
|
zSep = "";
|
|
|
|
nSep = strlen(zSep);
|
|
|
|
DBSQL_ASSERT(&pTos[1 - nField] >= p->aStack);
|
|
|
|
nByte = 1 - nSep;
|
|
|
|
pTerm = &pTos[1 - nField];
|
|
|
|
for (i = 0; i < nField; i++, pTerm++) {
|
|
|
|
if (pTerm->flags & MEM_Null) {
|
|
|
|
nByte = -1;
|
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
__entity_as_string(pTerm);
|
|
|
|
nByte += pTerm->n - 1 + nSep;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (nByte < 0) {
|
|
|
|
if (pOp->p2 == 0) {
|
|
|
|
__pop_stack(&pTos, nField);
|
|
|
|
}
|
|
|
|
pTos++;
|
|
|
|
pTos->flags = MEM_Null;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (__dbsql_calloc(NULL, 1, nByte, &zNew) == ENOMEM)
|
|
|
|
goto no_mem;
|
|
|
|
j = 0;
|
|
|
|
pTerm = &pTos[1 - nField];
|
|
|
|
for (i = j = 0; i < nField; i++, pTerm++) {
|
|
|
|
DBSQL_ASSERT(pTerm->flags & MEM_Str);
|
|
|
|
memcpy(&zNew[j], pTerm->z, (pTerm->n - 1));
|
|
|
|
j += pTerm->n - 1;
|
|
|
|
if (nSep > 0 && i < nField - 1) {
|
|
|
|
memcpy(&zNew[j], zSep, nSep);
|
|
|
|
j += nSep;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
zNew[j] = 0;
|
|
|
|
if (pOp->p2 == 0) {
|
|
|
|
__pop_stack(&pTos, nField);
|
|
|
|
}
|
|
|
|
pTos++;
|
|
|
|
pTos->n = nByte;
|
|
|
|
pTos->flags = MEM_Str | MEM_Dyn;
|
|
|
|
pTos->z = zNew;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: Add * * *
|
|
|
|
**
|
|
|
|
** Pop the top two elements from the stack, add them together,
|
|
|
|
** and push the result back onto the stack. If either element
|
|
|
|
** is a string then it is converted to a double using the atof()
|
|
|
|
** function before the addition.
|
|
|
|
** If either operand is NULL, the result is NULL.
|
|
|
|
*/
|
|
|
|
/* Opcode: Multiply * * *
|
|
|
|
**
|
|
|
|
** Pop the top two elements from the stack, multiply them together,
|
|
|
|
** and push the result back onto the stack. If either element
|
|
|
|
** is a string then it is converted to a double using the atof()
|
|
|
|
** function before the multiplication.
|
|
|
|
** If either operand is NULL, the result is NULL.
|
|
|
|
*/
|
|
|
|
/* Opcode: Subtract * * *
|
|
|
|
**
|
|
|
|
** Pop the top two elements from the stack, subtract the
|
|
|
|
** first (what was on top of the stack) from the second (the
|
|
|
|
** next on stack)
|
|
|
|
** and push the result back onto the stack. If either element
|
|
|
|
** is a string then it is converted to a double using the atof()
|
|
|
|
** function before the subtraction.
|
|
|
|
** If either operand is NULL, the result is NULL.
|
|
|
|
*/
|
|
|
|
/* Opcode: Divide * * *
|
|
|
|
**
|
|
|
|
** Pop the top two elements from the stack, divide the
|
|
|
|
** first (what was on top of the stack) from the second (the
|
|
|
|
** next on stack)
|
|
|
|
** and push the result back onto the stack. If either element
|
|
|
|
** is a string then it is converted to a double using the atof()
|
|
|
|
** function before the division. Division by zero returns NULL.
|
|
|
|
** If either operand is NULL, the result is NULL.
|
|
|
|
*/
|
|
|
|
/* Opcode: Remainder * * *
|
|
|
|
**
|
|
|
|
** Pop the top two elements from the stack, divide the
|
|
|
|
** first (what was on top of the stack) from the second (the
|
|
|
|
** next on stack)
|
|
|
|
** and push the remainder after division onto the stack. If either element
|
|
|
|
** is a string then it is converted to a double using the atof()
|
|
|
|
** function before the division. Division by zero returns NULL.
|
|
|
|
** If either operand is NULL, the result is NULL.
|
|
|
|
*/
|
|
|
|
case OP_Add: /* FALLTHROUGH */
|
|
|
|
case OP_Subtract: /* FALLTHROUGH */
|
|
|
|
case OP_Multiply: /* FALLTHROUGH */
|
|
|
|
case OP_Divide: /* FALLTHROUGH */
|
|
|
|
case OP_Remainder: {
|
|
|
|
mem_t *pNos = &pTos[-1];
|
|
|
|
DBSQL_ASSERT(pNos >= p->aStack);
|
|
|
|
if (((pTos->flags | pNos->flags) & MEM_Null) != 0) {
|
|
|
|
__entity_release_mem(pTos);
|
|
|
|
pTos--;
|
|
|
|
__entity_release_mem(pTos);
|
|
|
|
pTos->flags = MEM_Null;
|
|
|
|
} else if ((pTos->flags & pNos->flags & MEM_Int) == MEM_Int) {
|
|
|
|
int a, b;
|
|
|
|
a = pTos->i;
|
|
|
|
b = pNos->i;
|
|
|
|
switch(pOp->opcode) {
|
|
|
|
case OP_Add: b += a; break;
|
|
|
|
case OP_Subtract: b -= a; break;
|
|
|
|
case OP_Multiply: b *= a; break;
|
|
|
|
case OP_Divide: {
|
|
|
|
if (a == 0) goto divide_by_zero;
|
|
|
|
b /= a;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default: {
|
|
|
|
if (a == 0) goto divide_by_zero;
|
|
|
|
b %= a;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
__entity_release_mem(pTos);
|
|
|
|
pTos--;
|
|
|
|
__entity_release_mem(pTos);
|
|
|
|
pTos->i = b;
|
|
|
|
pTos->flags = MEM_Int;
|
|
|
|
} else {
|
|
|
|
double a, b;
|
|
|
|
__entity_to_real(pTos);
|
|
|
|
__entity_to_real(pNos);
|
|
|
|
a = pTos->r;
|
|
|
|
b = pNos->r;
|
|
|
|
switch( pOp->opcode ){
|
|
|
|
case OP_Add: b += a; break;
|
|
|
|
case OP_Subtract: b -= a; break;
|
|
|
|
case OP_Multiply: b *= a; break;
|
|
|
|
case OP_Divide: {
|
|
|
|
if (a == 0.0) goto divide_by_zero;
|
|
|
|
b /= a;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default: {
|
|
|
|
int ia = (int)a;
|
|
|
|
int ib = (int)b;
|
|
|
|
if (ia == 0.0) goto divide_by_zero;
|
|
|
|
b = ib % ia;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
__entity_release_mem(pTos);
|
|
|
|
pTos--;
|
|
|
|
__entity_release_mem(pTos);
|
|
|
|
pTos->r = b;
|
|
|
|
pTos->flags = MEM_Real;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
divide_by_zero:
|
|
|
|
__entity_release_mem(pTos);
|
|
|
|
pTos--;
|
|
|
|
__entity_release_mem(pTos);
|
|
|
|
pTos->flags = MEM_Null;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: Function P1 * P3
|
|
|
|
**
|
|
|
|
** Invoke a user function (P3 is a pointer to a Function structure that
|
|
|
|
** defines the function) with P1 string arguments taken from the stack.
|
|
|
|
** Pop all arguments from the stack and push back the result.
|
|
|
|
**
|
|
|
|
** See also: AggFunc
|
|
|
|
*/
|
|
|
|
case OP_Function: {
|
|
|
|
int n, i;
|
|
|
|
mem_t *pArg;
|
|
|
|
char **azArgv;
|
|
|
|
dbsql_func_t ctx;
|
|
|
|
|
|
|
|
n = pOp->p1;
|
|
|
|
pArg = &pTos[1-n];
|
|
|
|
azArgv = p->zArgv;
|
|
|
|
for (i = 0; i < n; i++, pArg++) {
|
|
|
|
if (pArg->flags & MEM_Null) {
|
|
|
|
azArgv[i] = 0;
|
|
|
|
} else {
|
|
|
|
__entity_as_string(pArg);
|
|
|
|
azArgv[i] = pArg->z;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ctx.pFunc = (func_def_t*)pOp->p3;
|
|
|
|
ctx.s.flags = MEM_Null;
|
|
|
|
ctx.s.z = 0;
|
|
|
|
ctx.isError = 0;
|
|
|
|
ctx.isStep = 0;
|
|
|
|
if (__safety_off(db))
|
|
|
|
goto abort_due_to_misuse;
|
|
|
|
(*ctx.pFunc->xFunc)(&ctx, n, (const char**)azArgv);
|
|
|
|
if (__safety_on(db))
|
|
|
|
goto abort_due_to_misuse;
|
|
|
|
__pop_stack(&pTos, n);
|
|
|
|
pTos++;
|
|
|
|
*pTos = ctx.s;
|
|
|
|
if (pTos->flags & MEM_Short) {
|
|
|
|
pTos->z = pTos->zShort;
|
|
|
|
}
|
|
|
|
if (ctx.isError) {
|
|
|
|
__str_append(&p->zErrMsg, ((pTos->flags & MEM_Str) != 0) ?
|
|
|
|
pTos->z : "user function error", (char*)0);
|
|
|
|
rc = DBSQL_ERROR;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: BitAnd * * *
|
|
|
|
**
|
|
|
|
** Pop the top two elements from the stack. Convert both elements
|
|
|
|
** to integers. Push back onto the stack the bit-wise AND of the
|
|
|
|
** two elements.
|
|
|
|
** If either operand is NULL, the result is NULL.
|
|
|
|
*/
|
|
|
|
/* Opcode: BitOr * * *
|
|
|
|
**
|
|
|
|
** Pop the top two elements from the stack. Convert both elements
|
|
|
|
** to integers. Push back onto the stack the bit-wise OR of the
|
|
|
|
** two elements.
|
|
|
|
** If either operand is NULL, the result is NULL.
|
|
|
|
*/
|
|
|
|
/* Opcode: ShiftLeft * * *
|
|
|
|
**
|
|
|
|
** Pop the top two elements from the stack. Convert both elements
|
|
|
|
** to integers. Push back onto the stack the top element shifted
|
|
|
|
** left by N bits where N is the second element on the stack.
|
|
|
|
** If either operand is NULL, the result is NULL.
|
|
|
|
*/
|
|
|
|
/* Opcode: ShiftRight * * *
|
|
|
|
**
|
|
|
|
** Pop the top two elements from the stack. Convert both elements
|
|
|
|
** to integers. Push back onto the stack the top element shifted
|
|
|
|
** right by N bits where N is the second element on the stack.
|
|
|
|
** If either operand is NULL, the result is NULL.
|
|
|
|
*/
|
|
|
|
case OP_BitAnd: /* FALLTHROUGH */
|
|
|
|
case OP_BitOr: /* FALLTHROUGH */
|
|
|
|
case OP_ShiftLeft: /* FALLTHROUGH */
|
|
|
|
case OP_ShiftRight: {
|
|
|
|
mem_t *pNos = &pTos[-1];
|
|
|
|
int a, b;
|
|
|
|
|
|
|
|
DBSQL_ASSERT(pNos >= p->aStack);
|
|
|
|
if ((pTos->flags | pNos->flags) & MEM_Null) {
|
|
|
|
__pop_stack(&pTos, 2);
|
|
|
|
pTos++;
|
|
|
|
pTos->flags = MEM_Null;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
__entity_to_int(pTos);
|
|
|
|
__entity_to_int(pNos);
|
|
|
|
a = pTos->i;
|
|
|
|
b = pNos->i;
|
|
|
|
switch(pOp->opcode) {
|
|
|
|
case OP_BitAnd: a &= b; break;
|
|
|
|
case OP_BitOr: a |= b; break;
|
|
|
|
case OP_ShiftLeft: a <<= b; break;
|
|
|
|
case OP_ShiftRight: a >>= b; break;
|
|
|
|
default: /* CANT HAPPEN */ break;
|
|
|
|
}
|
|
|
|
DBSQL_ASSERT((pTos->flags & MEM_Dyn) == 0);
|
|
|
|
DBSQL_ASSERT((pNos->flags & MEM_Dyn) == 0);
|
|
|
|
pTos--;
|
|
|
|
pTos->i = a;
|
|
|
|
DBSQL_ASSERT(pTos->flags == MEM_Int);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: AddImm P1 * *
|
|
|
|
**
|
|
|
|
** Add the value P1 to whatever is on top of the stack. The result
|
|
|
|
** is always an integer.
|
|
|
|
**
|
|
|
|
** To force the top of the stack to be an integer, just add 0.
|
|
|
|
*/
|
|
|
|
case OP_AddImm: {
|
|
|
|
DBSQL_ASSERT(pTos >= p->aStack);
|
|
|
|
__entity_to_int(pTos);
|
|
|
|
pTos->i += pOp->p1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: ForceInt P1 P2 *
|
|
|
|
**
|
|
|
|
** Convert the top of the stack into an integer. If the current top of
|
|
|
|
** the stack is not numeric (meaning that is is a NULL or a string that
|
|
|
|
** does not look like an integer or floating point number) then pop the
|
|
|
|
** stack and jump to P2. If the top of the stack is numeric then
|
|
|
|
** convert it into the least integer that is greater than or equal to its
|
|
|
|
** current value if P1==0, or to the least integer that is strictly
|
|
|
|
** greater than its current value if P1==1.
|
|
|
|
*/
|
|
|
|
case OP_ForceInt: {
|
|
|
|
int v;
|
|
|
|
DBSQL_ASSERT(pTos >= p->aStack);
|
|
|
|
if ((pTos->flags & (MEM_Int | MEM_Real)) == 0 &&
|
|
|
|
((pTos->flags & MEM_Str) == 0 || __str_is_numeric(pTos->z) ==0)) {
|
|
|
|
__entity_release_mem(pTos);
|
|
|
|
pTos--;
|
|
|
|
pc = pOp->p2 - 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (pTos->flags & MEM_Int) {
|
|
|
|
v = pTos->i + (pOp->p1 != 0);
|
|
|
|
} else {
|
|
|
|
__entity_to_real(pTos);
|
|
|
|
v = (int)pTos->r;
|
|
|
|
if (pTos->r>(double)v)
|
|
|
|
v++;
|
|
|
|
if (pOp->p1 && pTos->r == (double)v)
|
|
|
|
v++;
|
|
|
|
}
|
|
|
|
__entity_release_mem(pTos);
|
|
|
|
pTos->i = v;
|
|
|
|
pTos->flags = MEM_Int;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: MustBeInt P1 P2 *
|
|
|
|
**
|
|
|
|
** Force the top of the stack to be an integer. If the top of the
|
|
|
|
** stack is not an integer and cannot be converted into an integer
|
|
|
|
** with out data loss, then jump immediately to P2, or if P2==0
|
|
|
|
** raise an DBSQL_MISMATCH exception.
|
|
|
|
**
|
|
|
|
** If the top of the stack is not an integer and P2 is not zero and
|
|
|
|
** P1 is 1, then the stack is popped. In all other cases, the depth
|
|
|
|
** of the stack is unchanged.
|
|
|
|
*/
|
|
|
|
case OP_MustBeInt: {
|
|
|
|
DBSQL_ASSERT(pTos >= p->aStack);
|
|
|
|
if (pTos->flags & MEM_Int) {
|
|
|
|
/* Do nothing */
|
|
|
|
} else if (pTos->flags & MEM_Real) {
|
|
|
|
int i = (int)pTos->r;
|
|
|
|
double r = (double)i;
|
|
|
|
if (r != pTos->r) {
|
|
|
|
goto mismatch;
|
|
|
|
}
|
|
|
|
pTos->i = i;
|
|
|
|
} else if (pTos->flags & MEM_Str) {
|
|
|
|
int v;
|
|
|
|
if (!__dbsql_atoi(pTos->z, &v)) {
|
|
|
|
double r;
|
|
|
|
if (!__str_is_numeric(pTos->z)) {
|
|
|
|
goto mismatch;
|
|
|
|
}
|
|
|
|
__entity_to_real(pTos);
|
|
|
|
v = (int)pTos->r;
|
|
|
|
r = (double)v;
|
|
|
|
if (r != pTos->r) {
|
|
|
|
goto mismatch;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
pTos->i = v;
|
|
|
|
} else {
|
|
|
|
goto mismatch;
|
|
|
|
}
|
|
|
|
__entity_release_mem(pTos);
|
|
|
|
pTos->flags = MEM_Int;
|
|
|
|
break;
|
|
|
|
|
|
|
|
mismatch:
|
|
|
|
if (pOp->p2 == 0) {
|
|
|
|
rc = DBSQL_MISMATCH;
|
|
|
|
goto abort_due_to_error;
|
|
|
|
} else {
|
|
|
|
if (pOp->p1)
|
|
|
|
__pop_stack(&pTos, 1);
|
|
|
|
pc = pOp->p2 - 1;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: Eq P1 P2 *
|
|
|
|
**
|
|
|
|
** Pop the top two elements from the stack. If they are equal, then
|
|
|
|
** jump to instruction P2. Otherwise, continue to the next instruction.
|
|
|
|
**
|
|
|
|
** If either operand is NULL (and thus if the result is unknown) then
|
|
|
|
** take the jump if P1 is true.
|
|
|
|
**
|
|
|
|
** If both values are numeric, they are converted to doubles using atof()
|
|
|
|
** and compared for equality that way. Otherwise the strcmp() library
|
|
|
|
** routine is used for the comparison. For a pure text comparison
|
|
|
|
** use OP_StrEq.
|
|
|
|
**
|
|
|
|
** If P2 is zero, do not jump. Instead, push an integer 1 onto the
|
|
|
|
** stack if the jump would have been taken, or a 0 if not. Push a
|
|
|
|
** NULL if either operand was NULL.
|
|
|
|
*/
|
|
|
|
/* Opcode: Ne P1 P2 *
|
|
|
|
**
|
|
|
|
** Pop the top two elements from the stack. If they are not equal, then
|
|
|
|
** jump to instruction P2. Otherwise, continue to the next instruction.
|
|
|
|
**
|
|
|
|
** If either operand is NULL (and thus if the result is unknown) then
|
|
|
|
** take the jump if P1 is true.
|
|
|
|
**
|
|
|
|
** If both values are numeric, they are converted to doubles using atof()
|
|
|
|
** and compared in that format. Otherwise the strcmp() library
|
|
|
|
** routine is used for the comparison. For a pure text comparison
|
|
|
|
** use OP_StrNe.
|
|
|
|
**
|
|
|
|
** If P2 is zero, do not jump. Instead, push an integer 1 onto the
|
|
|
|
** stack if the jump would have been taken, or a 0 if not. Push a
|
|
|
|
** NULL if either operand was NULL.
|
|
|
|
*/
|
|
|
|
/* Opcode: Lt P1 P2 *
|
|
|
|
**
|
|
|
|
** Pop the top two elements from the stack. If second element (the
|
|
|
|
** next on stack) is less than the first (the top of stack), then
|
|
|
|
** jump to instruction P2. Otherwise, continue to the next instruction.
|
|
|
|
** In other words, jump if NOS<TOS.
|
|
|
|
**
|
|
|
|
** If either operand is NULL (and thus if the result is unknown) then
|
|
|
|
** take the jump if P1 is true.
|
|
|
|
**
|
|
|
|
** If both values are numeric, they are converted to doubles using atof()
|
|
|
|
** and compared in that format. Numeric values are always less than
|
|
|
|
** non-numeric values. If both operands are non-numeric, the strcmp() library
|
|
|
|
** routine is used for the comparison. For a pure text comparison
|
|
|
|
** use OP_StrLt.
|
|
|
|
**
|
|
|
|
** If P2 is zero, do not jump. Instead, push an integer 1 onto the
|
|
|
|
** stack if the jump would have been taken, or a 0 if not. Push a
|
|
|
|
** NULL if either operand was NULL.
|
|
|
|
*/
|
|
|
|
/* Opcode: Le P1 P2 *
|
|
|
|
**
|
|
|
|
** Pop the top two elements from the stack. If second element (the
|
|
|
|
** next on stack) is less than or equal to the first (the top of stack),
|
|
|
|
** then jump to instruction P2. In other words, jump if NOS<=TOS.
|
|
|
|
**
|
|
|
|
** If either operand is NULL (and thus if the result is unknown) then
|
|
|
|
** take the jump if P1 is true.
|
|
|
|
**
|
|
|
|
** If both values are numeric, they are converted to doubles using atof()
|
|
|
|
** and compared in that format. Numeric values are always less than
|
|
|
|
** non-numeric values. If both operands are non-numeric, the strcmp() library
|
|
|
|
** routine is used for the comparison. For a pure text comparison
|
|
|
|
** use OP_StrLe.
|
|
|
|
**
|
|
|
|
** If P2 is zero, do not jump. Instead, push an integer 1 onto the
|
|
|
|
** stack if the jump would have been taken, or a 0 if not. Push a
|
|
|
|
** NULL if either operand was NULL.
|
|
|
|
*/
|
|
|
|
/* Opcode: Gt P1 P2 *
|
|
|
|
**
|
|
|
|
** Pop the top two elements from the stack. If second element (the
|
|
|
|
** next on stack) is greater than the first (the top of stack),
|
|
|
|
** then jump to instruction P2. In other words, jump if NOS>TOS.
|
|
|
|
**
|
|
|
|
** If either operand is NULL (and thus if the result is unknown) then
|
|
|
|
** take the jump if P1 is true.
|
|
|
|
**
|
|
|
|
** If both values are numeric, they are converted to doubles using atof()
|
|
|
|
** and compared in that format. Numeric values are always less than
|
|
|
|
** non-numeric values. If both operands are non-numeric, the strcmp() library
|
|
|
|
** routine is used for the comparison. For a pure text comparison
|
|
|
|
** use OP_StrGt.
|
|
|
|
**
|
|
|
|
** If P2 is zero, do not jump. Instead, push an integer 1 onto the
|
|
|
|
** stack if the jump would have been taken, or a 0 if not. Push a
|
|
|
|
** NULL if either operand was NULL.
|
|
|
|
*/
|
|
|
|
/* Opcode: Ge P1 P2 *
|
|
|
|
**
|
|
|
|
** Pop the top two elements from the stack. If second element (the next
|
|
|
|
** on stack) is greater than or equal to the first (the top of stack),
|
|
|
|
** then jump to instruction P2. In other words, jump if NOS>=TOS.
|
|
|
|
**
|
|
|
|
** If either operand is NULL (and thus if the result is unknown) then
|
|
|
|
** take the jump if P1 is true.
|
|
|
|
**
|
|
|
|
** If both values are numeric, they are converted to doubles using atof()
|
|
|
|
** and compared in that format. Numeric values are always less than
|
|
|
|
** non-numeric values. If both operands are non-numeric, the strcmp() library
|
|
|
|
** routine is used for the comparison. For a pure text comparison
|
|
|
|
** use OP_StrGe.
|
|
|
|
**
|
|
|
|
** If P2 is zero, do not jump. Instead, push an integer 1 onto the
|
|
|
|
** stack if the jump would have been taken, or a 0 if not. Push a
|
|
|
|
** NULL if either operand was NULL.
|
|
|
|
*/
|
|
|
|
case OP_Eq: /* FALLTHROUGH */
|
|
|
|
case OP_Ne: /* FALLTHROUGH */
|
|
|
|
case OP_Lt: /* FALLTHROUGH */
|
|
|
|
case OP_Le: /* FALLTHROUGH */
|
|
|
|
case OP_Gt: /* FALLTHROUGH */
|
|
|
|
case OP_Ge: {
|
|
|
|
mem_t *pNos = &pTos[-1];
|
|
|
|
int c, v;
|
|
|
|
int ft, fn;
|
|
|
|
DBSQL_ASSERT(pNos >= p->aStack);
|
|
|
|
ft = pTos->flags;
|
|
|
|
fn = pNos->flags;
|
|
|
|
if ((ft | fn) & MEM_Null) {
|
|
|
|
__pop_stack(&pTos, 2);
|
|
|
|
if (pOp->p2) {
|
|
|
|
if (pOp->p1)
|
|
|
|
pc = pOp->p2-1;
|
|
|
|
} else {
|
|
|
|
pTos++;
|
|
|
|
pTos->flags = MEM_Null;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
} else if ((ft & fn & MEM_Int) == MEM_Int) {
|
|
|
|
c = pNos->i - pTos->i;
|
|
|
|
} else if ((ft & MEM_Int) != 0 && (fn & MEM_Str) !=0
|
|
|
|
&& __dbsql_atoi(pNos->z,&v)) {
|
|
|
|
c = v - pTos->i;
|
|
|
|
} else if ((fn & MEM_Int) != 0 && (ft & MEM_Str) !=0 &&
|
|
|
|
__dbsql_atoi(pTos->z,&v)) {
|
|
|
|
c = pNos->i - v;
|
|
|
|
} else {
|
|
|
|
__entity_as_string(pTos);
|
|
|
|
__entity_as_string(pNos);
|
|
|
|
c = __str_numeric_cmp(pNos->z, pTos->z);
|
|
|
|
}
|
|
|
|
switch(pOp->opcode) {
|
|
|
|
case OP_Eq: c = (c == 0); break;
|
|
|
|
case OP_Ne: c = (c != 0); break;
|
|
|
|
case OP_Lt: c = (c < 0); break;
|
|
|
|
case OP_Le: c = (c <= 0); break;
|
|
|
|
case OP_Gt: c = (c > 0); break;
|
|
|
|
default: c = (c >= 0); break;
|
|
|
|
}
|
|
|
|
__pop_stack(&pTos, 2);
|
|
|
|
if (pOp->p2) {
|
|
|
|
if (c)
|
|
|
|
pc = pOp->p2 - 1;
|
|
|
|
} else {
|
|
|
|
pTos++;
|
|
|
|
pTos->i = c;
|
|
|
|
pTos->flags = MEM_Int;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* !!!
|
|
|
|
** WARNING, INSERT NO CODE AT THIS POINT!
|
|
|
|
**
|
|
|
|
** The opcode numbers are extracted from this source file by doing
|
|
|
|
**
|
|
|
|
** grep '^case OP_' vdbe.c | ... >opcodes.h
|
|
|
|
**
|
|
|
|
** The opcodes are numbered in the order that they appear in this file.
|
|
|
|
** But in order for the expression generating code to work right, the
|
|
|
|
** string comparison operators that follow must be numbered exactly 6
|
|
|
|
** greater than the numeric comparison opcodes above. So no other
|
|
|
|
** cases can appear between the two.
|
|
|
|
** !!!
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* Opcode: StrEq P1 P2 *
|
|
|
|
**
|
|
|
|
** Pop the top two elements from the stack. If they are equal, then
|
|
|
|
** jump to instruction P2. Otherwise, continue to the next instruction.
|
|
|
|
**
|
|
|
|
** If either operand is NULL (and thus if the result is unknown) then
|
|
|
|
** take the jump if P1 is true.
|
|
|
|
**
|
|
|
|
** The strcmp() library routine is used for the comparison. For a
|
|
|
|
** numeric comparison, use OP_Eq.
|
|
|
|
**
|
|
|
|
** If P2 is zero, do not jump. Instead, push an integer 1 onto the
|
|
|
|
** stack if the jump would have been taken, or a 0 if not. Push a
|
|
|
|
** NULL if either operand was NULL.
|
|
|
|
*/
|
|
|
|
/* Opcode: StrNe P1 P2 *
|
|
|
|
**
|
|
|
|
** Pop the top two elements from the stack. If they are not equal, then
|
|
|
|
** jump to instruction P2. Otherwise, continue to the next instruction.
|
|
|
|
**
|
|
|
|
** If either operand is NULL (and thus if the result is unknown) then
|
|
|
|
** take the jump if P1 is true.
|
|
|
|
**
|
|
|
|
** The strcmp() library routine is used for the comparison. For a
|
|
|
|
** numeric comparison, use OP_Ne.
|
|
|
|
**
|
|
|
|
** If P2 is zero, do not jump. Instead, push an integer 1 onto the
|
|
|
|
** stack if the jump would have been taken, or a 0 if not. Push a
|
|
|
|
** NULL if either operand was NULL.
|
|
|
|
*/
|
|
|
|
/* Opcode: StrLt P1 P2 *
|
|
|
|
**
|
|
|
|
** Pop the top two elements from the stack. If second element (the
|
|
|
|
** next on stack) is less than the first (the top of stack), then
|
|
|
|
** jump to instruction P2. Otherwise, continue to the next instruction.
|
|
|
|
** In other words, jump if NOS<TOS.
|
|
|
|
**
|
|
|
|
** If either operand is NULL (and thus if the result is unknown) then
|
|
|
|
** take the jump if P1 is true.
|
|
|
|
**
|
|
|
|
** The strcmp() library routine is used for the comparison. For a
|
|
|
|
** numeric comparison, use OP_Lt.
|
|
|
|
**
|
|
|
|
** If P2 is zero, do not jump. Instead, push an integer 1 onto the
|
|
|
|
** stack if the jump would have been taken, or a 0 if not. Push a
|
|
|
|
** NULL if either operand was NULL.
|
|
|
|
*/
|
|
|
|
/* Opcode: StrLe P1 P2 *
|
|
|
|
**
|
|
|
|
** Pop the top two elements from the stack. If second element (the
|
|
|
|
** next on stack) is less than or equal to the first (the top of stack),
|
|
|
|
** then jump to instruction P2. In other words, jump if NOS<=TOS.
|
|
|
|
**
|
|
|
|
** If either operand is NULL (and thus if the result is unknown) then
|
|
|
|
** take the jump if P1 is true.
|
|
|
|
**
|
|
|
|
** The strcmp() library routine is used for the comparison. For a
|
|
|
|
** numeric comparison, use OP_Le.
|
|
|
|
**
|
|
|
|
** If P2 is zero, do not jump. Instead, push an integer 1 onto the
|
|
|
|
** stack if the jump would have been taken, or a 0 if not. Push a
|
|
|
|
** NULL if either operand was NULL.
|
|
|
|
*/
|
|
|
|
/* Opcode: StrGt P1 P2 *
|
|
|
|
**
|
|
|
|
** Pop the top two elements from the stack. If second element (the
|
|
|
|
** next on stack) is greater than the first (the top of stack),
|
|
|
|
** then jump to instruction P2. In other words, jump if NOS>TOS.
|
|
|
|
**
|
|
|
|
** If either operand is NULL (and thus if the result is unknown) then
|
|
|
|
** take the jump if P1 is true.
|
|
|
|
**
|
|
|
|
** The strcmp() library routine is used for the comparison. For a
|
|
|
|
** numeric comparison, use OP_Gt.
|
|
|
|
**
|
|
|
|
** If P2 is zero, do not jump. Instead, push an integer 1 onto the
|
|
|
|
** stack if the jump would have been taken, or a 0 if not. Push a
|
|
|
|
** NULL if either operand was NULL.
|
|
|
|
*/
|
|
|
|
/* Opcode: StrGe P1 P2 *
|
|
|
|
**
|
|
|
|
** Pop the top two elements from the stack. If second element (the next
|
|
|
|
** on stack) is greater than or equal to the first (the top of stack),
|
|
|
|
** then jump to instruction P2. In other words, jump if NOS>=TOS.
|
|
|
|
**
|
|
|
|
** If either operand is NULL (and thus if the result is unknown) then
|
|
|
|
** take the jump if P1 is true.
|
|
|
|
**
|
|
|
|
** The strcmp() library routine is used for the comparison. For a
|
|
|
|
** numeric comparison, use OP_Ge.
|
|
|
|
**
|
|
|
|
** If P2 is zero, do not jump. Instead, push an integer 1 onto the
|
|
|
|
** stack if the jump would have been taken, or a 0 if not. Push a
|
|
|
|
** NULL if either operand was NULL.
|
|
|
|
*/
|
|
|
|
case OP_StrEq: /* FALLTHROUGH */
|
|
|
|
case OP_StrNe: /* FALLTHROUGH */
|
|
|
|
case OP_StrLt: /* FALLTHROUGH */
|
|
|
|
case OP_StrLe: /* FALLTHROUGH */
|
|
|
|
case OP_StrGt: /* FALLTHROUGH */
|
|
|
|
case OP_StrGe: {
|
|
|
|
mem_t *pNos = &pTos[-1];
|
|
|
|
int c;
|
|
|
|
DBSQL_ASSERT(pNos >= p->aStack);
|
|
|
|
if ((pNos->flags | pTos->flags) & MEM_Null) {
|
|
|
|
__pop_stack(&pTos, 2);
|
|
|
|
if (pOp->p2) {
|
|
|
|
if (pOp->p1)
|
|
|
|
pc = (pOp->p2 - 1);
|
|
|
|
} else {
|
|
|
|
pTos++;
|
|
|
|
pTos->flags = MEM_Null;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
__entity_as_string(pTos);
|
|
|
|
__entity_as_string(pNos);
|
|
|
|
c = strcmp(pNos->z, pTos->z);
|
|
|
|
}
|
|
|
|
/* !!!
|
|
|
|
* The DBSQL_ASSERTs on each case of the following switch are there to verify
|
|
|
|
* that string comparison opcodes are always exactly 6 greater than the
|
|
|
|
* corresponding numeric comparison opcodes. The code generator
|
|
|
|
* depends on this fact.
|
|
|
|
* !!!
|
|
|
|
*/
|
|
|
|
switch(pOp->opcode) {
|
|
|
|
case OP_StrEq:
|
|
|
|
c = (c == 0);
|
|
|
|
DBSQL_ASSERT(pOp->opcode-6 == OP_Eq);
|
|
|
|
break;
|
|
|
|
case OP_StrNe:
|
|
|
|
c = (c != 0);
|
|
|
|
DBSQL_ASSERT(pOp->opcode-6 == OP_Ne);
|
|
|
|
break;
|
|
|
|
case OP_StrLt:
|
|
|
|
c = (c < 0);
|
|
|
|
DBSQL_ASSERT(pOp->opcode-6 == OP_Lt);
|
|
|
|
break;
|
|
|
|
case OP_StrLe:
|
|
|
|
c = (c <= 0);
|
|
|
|
DBSQL_ASSERT(pOp->opcode-6 == OP_Le);
|
|
|
|
break;
|
|
|
|
case OP_StrGt:
|
|
|
|
c = (c > 0);
|
|
|
|
DBSQL_ASSERT(pOp->opcode-6 == OP_Gt);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
c = (c >= 0);
|
|
|
|
DBSQL_ASSERT(pOp->opcode-6 == OP_Ge);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
__pop_stack(&pTos, 2);
|
|
|
|
if (pOp->p2) {
|
|
|
|
if (c)
|
|
|
|
pc = (pOp->p2 - 1);
|
|
|
|
} else {
|
|
|
|
pTos++;
|
|
|
|
pTos->flags = MEM_Int;
|
|
|
|
pTos->i = c;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: And * * *
|
|
|
|
**
|
|
|
|
** Pop two values off the stack. Take the logical AND of the
|
|
|
|
** two values and push the resulting boolean value back onto the
|
|
|
|
** stack.
|
|
|
|
*/
|
|
|
|
/* Opcode: Or * * *
|
|
|
|
**
|
|
|
|
** Pop two values off the stack. Take the logical OR of the
|
|
|
|
** two values and push the resulting boolean value back onto the
|
|
|
|
** stack.
|
|
|
|
*/
|
|
|
|
case OP_And: /* FALLTHROUGH */
|
|
|
|
case OP_Or: {
|
|
|
|
mem_t *pNos = &pTos[-1];
|
|
|
|
int v1, v2; /* 0==TRUE, 1==FALSE, 2==UNKNOWN or NULL */
|
|
|
|
|
|
|
|
DBSQL_ASSERT(pNos >= p->aStack);
|
|
|
|
if (pTos->flags & MEM_Null) {
|
|
|
|
v1 = 2;
|
|
|
|
} else {
|
|
|
|
__entity_to_int(pTos);
|
|
|
|
v1 = pTos->i == 0;
|
|
|
|
}
|
|
|
|
if (pNos->flags & MEM_Null) {
|
|
|
|
v2 = 2;
|
|
|
|
} else {
|
|
|
|
__entity_to_int(pNos);
|
|
|
|
v2 = pNos->i == 0;
|
|
|
|
}
|
|
|
|
if (pOp->opcode == OP_And) {
|
|
|
|
static const unsigned char and_logic[] =
|
|
|
|
{ 0, 1, 2, 1, 1, 1, 2, 1, 2 };
|
|
|
|
v1 = and_logic[(v1 * 3) + v2];
|
|
|
|
} else {
|
|
|
|
static const unsigned char or_logic[] =
|
|
|
|
{ 0, 0, 0, 0, 1, 2, 0, 2, 2 };
|
|
|
|
v1 = or_logic[(v1 * 3) + v2];
|
|
|
|
}
|
|
|
|
__pop_stack(&pTos, 2);
|
|
|
|
pTos++;
|
|
|
|
if (v1 == 2) {
|
|
|
|
pTos->flags = MEM_Null;
|
|
|
|
} else {
|
|
|
|
pTos->i = v1==0;
|
|
|
|
pTos->flags = MEM_Int;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: Negative * * *
|
|
|
|
**
|
|
|
|
** Treat the top of the stack as a numeric quantity. Replace it
|
|
|
|
** with its additive inverse. If the top of the stack is NULL
|
|
|
|
** its value is unchanged.
|
|
|
|
*/
|
|
|
|
/* Opcode: AbsValue * * *
|
|
|
|
**
|
|
|
|
** Treat the top of the stack as a numeric quantity. Replace it
|
|
|
|
** with its absolute value. If the top of the stack is NULL
|
|
|
|
** its value is unchanged.
|
|
|
|
*/
|
|
|
|
case OP_Negative: /* FALLTHROUGH */
|
|
|
|
case OP_AbsValue: {
|
|
|
|
DBSQL_ASSERT(pTos >= p->aStack);
|
|
|
|
if (pTos->flags & MEM_Real) {
|
|
|
|
__entity_release_mem(pTos);
|
|
|
|
if (pOp->opcode == OP_Negative || pTos->r < 0.0) {
|
|
|
|
pTos->r = -pTos->r;
|
|
|
|
}
|
|
|
|
pTos->flags = MEM_Real;
|
|
|
|
} else if (pTos->flags & MEM_Int) {
|
|
|
|
__entity_release_mem(pTos);
|
|
|
|
if (pOp->opcode == OP_Negative || pTos->i < 0) {
|
|
|
|
pTos->i = -pTos->i;
|
|
|
|
}
|
|
|
|
pTos->flags = MEM_Int;
|
|
|
|
} else if (pTos->flags & MEM_Null) {
|
|
|
|
/* Do nothing */
|
|
|
|
} else {
|
|
|
|
__entity_to_real(pTos);
|
|
|
|
__entity_release_mem(pTos);
|
|
|
|
if (pOp->opcode == OP_Negative || pTos->r < 0.0) {
|
|
|
|
pTos->r = -pTos->r;
|
|
|
|
}
|
|
|
|
pTos->flags = MEM_Real;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: Not * * *
|
|
|
|
**
|
|
|
|
** Interpret the top of the stack as a boolean value. Replace it
|
|
|
|
** with its complement. If the top of the stack is NULL its value
|
|
|
|
** is unchanged.
|
|
|
|
*/
|
|
|
|
case OP_Not: {
|
|
|
|
DBSQL_ASSERT(pTos >= p->aStack);
|
|
|
|
if (pTos->flags & MEM_Null)
|
|
|
|
break; /* Do nothing to NULLs */
|
|
|
|
__entity_to_int(pTos);
|
|
|
|
DBSQL_ASSERT(pTos->flags == MEM_Int);
|
|
|
|
pTos->i = !pTos->i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: BitNot * * *
|
|
|
|
**
|
|
|
|
** Interpret the top of the stack as an value. Replace it
|
|
|
|
** with its ones-complement. If the top of the stack is NULL its
|
|
|
|
** value is unchanged.
|
|
|
|
*/
|
|
|
|
case OP_BitNot: {
|
|
|
|
DBSQL_ASSERT(pTos >= p->aStack);
|
|
|
|
if (pTos->flags & MEM_Null)
|
|
|
|
break; /* Do nothing to NULLs */
|
|
|
|
__entity_to_int(pTos);
|
|
|
|
DBSQL_ASSERT(pTos->flags == MEM_Int);
|
|
|
|
pTos->i = ~pTos->i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: Noop * * *
|
|
|
|
**
|
|
|
|
** Do nothing. This instruction is often useful as a jump
|
|
|
|
** destination.
|
|
|
|
*/
|
|
|
|
case OP_Noop: {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: If P1 P2 *
|
|
|
|
**
|
|
|
|
** Pop a single boolean from the stack. If the boolean popped is
|
|
|
|
** true, then jump to P2. Otherwise continue to the next instruction.
|
|
|
|
** An integer is false if zero and true otherwise. A string is
|
|
|
|
** false if it has zero length and true otherwise.
|
|
|
|
**
|
|
|
|
** If the value popped of the stack is NULL, then take the jump if P1
|
|
|
|
** is true and fall through if P1 is false.
|
|
|
|
*/
|
|
|
|
/* Opcode: IfNot P1 P2 *
|
|
|
|
**
|
|
|
|
** Pop a single boolean from the stack. If the boolean popped is
|
|
|
|
** false, then jump to p2. Otherwise continue to the next instruction.
|
|
|
|
** An integer is false if zero and true otherwise. A string is
|
|
|
|
** false if it has zero length and true otherwise.
|
|
|
|
**
|
|
|
|
** If the value popped of the stack is NULL, then take the jump if P1
|
|
|
|
** is true and fall through if P1 is false.
|
|
|
|
*/
|
|
|
|
case OP_If: /* FALLTHROUGH */
|
|
|
|
case OP_IfNot: {
|
|
|
|
int c;
|
|
|
|
DBSQL_ASSERT(pTos >= p->aStack);
|
|
|
|
if (pTos->flags & MEM_Null) {
|
|
|
|
c = pOp->p1;
|
|
|
|
} else {
|
|
|
|
__entity_to_int(pTos);
|
|
|
|
c = pTos->i;
|
|
|
|
if (pOp->opcode == OP_IfNot)
|
|
|
|
c = !c;
|
|
|
|
}
|
|
|
|
DBSQL_ASSERT((pTos->flags & MEM_Dyn) == 0);
|
|
|
|
pTos--;
|
|
|
|
if (c)
|
|
|
|
pc = pOp->p2-1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: IsNull P1 P2 *
|
|
|
|
**
|
|
|
|
** If any of the top abs(P1) values on the stack are NULL, then jump
|
|
|
|
** to P2. Pop the stack P1 times if P1>0. If P1<0 leave the stack
|
|
|
|
** unchanged.
|
|
|
|
*/
|
|
|
|
case OP_IsNull: {
|
|
|
|
int i, cnt;
|
|
|
|
mem_t *pTerm;
|
|
|
|
cnt = pOp->p1;
|
|
|
|
if (cnt < 0)
|
|
|
|
cnt = -cnt;
|
|
|
|
pTerm = &pTos[1-cnt];
|
|
|
|
DBSQL_ASSERT(pTerm >= p->aStack);
|
|
|
|
for (i = 0; i < cnt; i++, pTerm++) {
|
|
|
|
if (pTerm->flags & MEM_Null) {
|
|
|
|
pc = pOp->p2-1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (pOp->p1 > 0)
|
|
|
|
__pop_stack(&pTos, cnt);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: NotNull P1 P2 *
|
|
|
|
**
|
|
|
|
** Jump to P2 if the top P1 values on the stack are all not NULL. Pop the
|
|
|
|
** stack if P1 times if P1 is greater than zero. If P1 is less than
|
|
|
|
** zero then leave the stack unchanged.
|
|
|
|
*/
|
|
|
|
case OP_NotNull: {
|
|
|
|
int i, cnt;
|
|
|
|
cnt = pOp->p1;
|
|
|
|
if (cnt < 0)
|
|
|
|
cnt = -cnt;
|
|
|
|
DBSQL_ASSERT(&pTos[1 - cnt] >= p->aStack);
|
|
|
|
i = 0;
|
|
|
|
while(i < cnt && (pTos[1 + i - cnt].flags & MEM_Null) == 0) {
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
if (i >= cnt )
|
|
|
|
pc = pOp->p2 - 1;
|
|
|
|
if (pOp->p1 > 0)
|
|
|
|
__pop_stack(&pTos, cnt);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: MakeRecord P1 P2 *
|
|
|
|
**
|
|
|
|
** Convert the top P1 entries of the stack into a single entry
|
|
|
|
** suitable for use as a data record in a database table. The
|
|
|
|
** details of the format are irrelavant as long as the OP_Column
|
|
|
|
** opcode can decode the record later. Refer to source code
|
|
|
|
** comments for the details of the record format.
|
|
|
|
**
|
|
|
|
** If P2 is true (non-zero) and one or more of the P1 entries
|
|
|
|
** that go into building the record is NULL, then add some extra
|
|
|
|
** bytes to the record to make it distinct for other entries created
|
|
|
|
** during the same run of the VDBE. The extra bytes added are a
|
|
|
|
** counter that is reset with each run of the VDBE, so records
|
|
|
|
** created this way will not necessarily be distinct across runs.
|
|
|
|
** But they should be distinct for transient tables (created using
|
|
|
|
** OP_OpenTemp) which is what they are intended for.
|
|
|
|
**
|
|
|
|
** (Later:) The P2==1 option was intended to make NULLs distinct
|
|
|
|
** for the UNION operator. But I have since discovered that NULLs
|
|
|
|
** are indistinct for UNION. So this option is never used.
|
|
|
|
*/
|
|
|
|
case OP_MakeRecord: {
|
|
|
|
char *zNewRecord;
|
|
|
|
int nByte;
|
|
|
|
int nField;
|
|
|
|
int i, j;
|
|
|
|
int idxWidth;
|
|
|
|
u_int32_t addr;
|
|
|
|
mem_t *pRec;
|
|
|
|
int addUnique = 0; /* True to cause bytes to be added to make the
|
|
|
|
* generated record distinct */
|
|
|
|
char zTemp[NBFS]; /* Temp space for small records */
|
|
|
|
|
|
|
|
/* Assuming the record contains N fields, the record format looks
|
|
|
|
* like this:
|
|
|
|
*
|
|
|
|
* -------------------------------------------------------------------
|
|
|
|
* | idx0 | idx1 | ... | idx(N-1) | idx(N) | data0 | ... | data(N-1) |
|
|
|
|
* -------------------------------------------------------------------
|
|
|
|
*
|
|
|
|
* All data fields are converted to strings before being stored and
|
|
|
|
* are stored with their null terminators. NULL entries omit the
|
|
|
|
* null terminator. Thus an empty string uses 1 byte and a NULL uses
|
|
|
|
* zero bytes. Data(0) is taken from the lowest element of the stack
|
|
|
|
* and data(N-1) is the top of the stack.
|
|
|
|
*
|
|
|
|
* Each of the idx() entries is either 1, 2, or 3 bytes depending on
|
|
|
|
* how big the total record is. Idx(0) contains the offset to the
|
|
|
|
* start of data(0). Idx(k) contains the offset to the start of
|
|
|
|
* data(k).
|
|
|
|
* Idx(N) contains the total number of bytes in the record.
|
|
|
|
*/
|
|
|
|
nField = pOp->p1;
|
|
|
|
pRec = &pTos[1 - nField];
|
|
|
|
DBSQL_ASSERT(pRec >= p->aStack);
|
|
|
|
nByte = 0;
|
|
|
|
for (i = 0; i < nField; i++, pRec++) {
|
|
|
|
if (pRec->flags & MEM_Null) {
|
|
|
|
addUnique = pOp->p2;
|
|
|
|
} else {
|
|
|
|
__entity_as_string(pRec);
|
|
|
|
nByte += pRec->n;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (addUnique)
|
|
|
|
nByte += sizeof(p->uniqueCnt);
|
|
|
|
if (nByte + nField + 1 < 256) {
|
|
|
|
idxWidth = 1;
|
|
|
|
} else if (nByte + (2 * nField) + 2 < 65536) {
|
|
|
|
idxWidth = 2;
|
|
|
|
} else {
|
|
|
|
idxWidth = 3;
|
|
|
|
}
|
|
|
|
nByte += idxWidth * (nField + 1);
|
|
|
|
if (nByte <= NBFS) {
|
|
|
|
zNewRecord = zTemp;
|
|
|
|
} else {
|
|
|
|
if (__dbsql_calloc(NULL, 1, nByte, &zNewRecord) == ENOMEM)
|
|
|
|
goto no_mem;
|
|
|
|
}
|
|
|
|
j = 0;
|
|
|
|
addr = idxWidth * (nField + 1) + addUnique * sizeof(p->uniqueCnt);
|
|
|
|
for (i = 0, pRec = &pTos[1 - nField]; i < nField; i++, pRec++) {
|
|
|
|
zNewRecord[j++] = addr & 0xff;
|
|
|
|
if (idxWidth > 1) {
|
|
|
|
zNewRecord[j++] = (addr >> 8) & 0xff;
|
|
|
|
if (idxWidth > 2) {
|
|
|
|
zNewRecord[j++] = (addr >> 16) & 0xff;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ((pRec->flags & MEM_Null) == 0) {
|
|
|
|
addr += pRec->n;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
zNewRecord[j++] = addr & 0xff;
|
|
|
|
if (idxWidth > 1) {
|
|
|
|
zNewRecord[j++] = (addr >> 8) & 0xff;
|
|
|
|
if (idxWidth > 2) {
|
|
|
|
zNewRecord[j++] = (addr >> 16) & 0xff;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (addUnique) {
|
|
|
|
memcpy(&zNewRecord[j], &p->uniqueCnt, sizeof(p->uniqueCnt));
|
|
|
|
p->uniqueCnt++;
|
|
|
|
j += sizeof(p->uniqueCnt);
|
|
|
|
}
|
|
|
|
for (i = 0, pRec = &pTos[1 - nField]; i < nField; i++, pRec++) {
|
|
|
|
if ((pRec->flags & MEM_Null) == 0) {
|
|
|
|
memcpy(&zNewRecord[j], pRec->z, pRec->n);
|
|
|
|
j += pRec->n;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
__pop_stack(&pTos, nField);
|
|
|
|
pTos++;
|
|
|
|
pTos->n = nByte;
|
|
|
|
if (nByte <= NBFS) {
|
|
|
|
DBSQL_ASSERT(zNewRecord == zTemp);
|
|
|
|
memcpy(pTos->zShort, zTemp, nByte);
|
|
|
|
pTos->z = pTos->zShort;
|
|
|
|
pTos->flags = MEM_Str | MEM_Short;
|
|
|
|
} else {
|
|
|
|
DBSQL_ASSERT(zNewRecord != zTemp);
|
|
|
|
pTos->z = zNewRecord;
|
|
|
|
pTos->flags = MEM_Str | MEM_Dyn;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: MakeKey P1 P2 P3
|
|
|
|
**
|
|
|
|
** Convert the top P1 entries of the stack into a single entry suitable
|
|
|
|
** for use as the key in an index. The top P1 records are
|
|
|
|
** converted to strings and merged. The null-terminators
|
|
|
|
** are retained and used as separators.
|
|
|
|
** The lowest entry in the stack is the first field and the top of the
|
|
|
|
** stack becomes the last.
|
|
|
|
**
|
|
|
|
** If P2 is not zero, then the original entries remain on the stack
|
|
|
|
** and the new key is pushed on top. If P2 is zero, the original
|
|
|
|
** data is popped off the stack first then the new key is pushed
|
|
|
|
** back in its place.
|
|
|
|
**
|
|
|
|
** P3 is a string that is P1 characters long. Each character is either
|
|
|
|
** an 'n' or a 't' to indicates if the argument should be intepreted as
|
|
|
|
** numeric or text type. The first character of P3 corresponds to the
|
|
|
|
** lowest element on the stack. If P3 is NULL then all arguments are
|
|
|
|
** assumed to be of the numeric type.
|
|
|
|
**
|
|
|
|
** The type makes a difference in that text-type fields may not be
|
|
|
|
** introduced by 'b' (as described in the next paragraph). The
|
|
|
|
** first character of a text-type field must be either 'a' (if it is NULL)
|
|
|
|
** or 'c'. Numeric fields will be introduced by 'b' if their content
|
|
|
|
** looks like a well-formed number. Otherwise the 'a' or 'c' will be
|
|
|
|
** used.
|
|
|
|
**
|
|
|
|
** The key is a concatenation of fields. Each field is terminated by
|
|
|
|
** a single 0x00 character. A NULL field is introduced by an 'a' and
|
|
|
|
** is followed immediately by its 0x00 terminator. A numeric field is
|
|
|
|
** introduced by a single character 'b' and is followed by a sequence
|
|
|
|
** of characters that represent the number such that a comparison of
|
|
|
|
** the character string using memcpy() sorts the numbers in numerical
|
|
|
|
** order. The character strings for numbers are generated using the
|
|
|
|
** __str_real_as_sortable() function. A text field is introduced by a
|
|
|
|
** 'c' character and is followed by the exact text of the field. The
|
|
|
|
** use of an 'a', 'b', or 'c' character at the beginning of each field
|
|
|
|
** guarantees that NULLs sort before numbers and that numbers sort
|
|
|
|
** before text. 0x00 characters do not occur except as separators
|
|
|
|
** between fields.
|
|
|
|
**
|
|
|
|
** See also: MakeIdxKey, SortMakeKey
|
|
|
|
*/
|
|
|
|
/* Opcode: MakeIdxKey P1 P2 P3
|
|
|
|
**
|
|
|
|
** Convert the top P1 entries of the stack into a single entry suitable
|
|
|
|
** for use as the key in an index. In addition, take one additional integer
|
|
|
|
** off of the stack, treat that integer as a four-byte record number, and
|
|
|
|
** append the four bytes to the key. Thus a total of P1+1 entries are
|
|
|
|
** popped from the stack for this instruction and a single entry is pushed
|
|
|
|
** back. The first P1 entries that are popped are strings and the last
|
|
|
|
** entry (the lowest on the stack) is an integer record number.
|
|
|
|
**
|
|
|
|
** The converstion of the first P1 string entries occurs just like in
|
|
|
|
** MakeKey. Each entry is separated from the others by a null.
|
|
|
|
** The entire concatenation is null-terminated. The lowest entry
|
|
|
|
** in the stack is the first field and the top of the stack becomes the
|
|
|
|
** last.
|
|
|
|
**
|
|
|
|
** If P2 is not zero and one or more of the P1 entries that go into the
|
|
|
|
** generated key is NULL, then jump to P2 after the new key has been
|
|
|
|
** pushed on the stack. In other words, jump to P2 if the key is
|
|
|
|
** guaranteed to be unique. This jump can be used to skip a subsequent
|
|
|
|
** uniqueness test.
|
|
|
|
**
|
|
|
|
** P3 is a string that is P1 characters long. Each character is either
|
|
|
|
** an 'n' or a 't' to indicates if the argument should be numeric or
|
|
|
|
** text. The first character corresponds to the lowest element on the
|
|
|
|
** stack. If P3 is null then all arguments are assumed to be numeric.
|
|
|
|
**
|
|
|
|
** See also: MakeKey, SortMakeKey
|
|
|
|
*/
|
|
|
|
case OP_MakeIdxKey: /* FALLTHROUGH */
|
|
|
|
case OP_MakeKey: {
|
|
|
|
char *zNewKey;
|
|
|
|
int nByte;
|
|
|
|
int nField;
|
|
|
|
int addRowid;
|
|
|
|
int i, j;
|
|
|
|
int containsNull = 0;
|
|
|
|
mem_t *pRec;
|
|
|
|
char zTemp[NBFS];
|
|
|
|
|
|
|
|
addRowid = (pOp->opcode == OP_MakeIdxKey);
|
|
|
|
nField = pOp->p1;
|
|
|
|
pRec = &pTos[1 - nField];
|
|
|
|
DBSQL_ASSERT(pRec >= p->aStack);
|
|
|
|
nByte = 0;
|
|
|
|
for (j = 0, i = 0; i < nField; i++, j++, pRec++) {
|
|
|
|
int flags = pRec->flags;
|
|
|
|
int len;
|
|
|
|
char *z;
|
|
|
|
if (flags & MEM_Null) {
|
|
|
|
nByte += 2;
|
|
|
|
containsNull = 1;
|
|
|
|
} else if (pOp->p3 && pOp->p3[j] == 't') {
|
|
|
|
__entity_as_string(pRec);
|
|
|
|
pRec->flags &= ~(MEM_Int | MEM_Real);
|
|
|
|
nByte += (pRec->n + 1);
|
|
|
|
} else if ((flags & (MEM_Real | MEM_Int)) != 0 ||
|
|
|
|
__str_is_numeric(pRec->z)) {
|
|
|
|
if ((flags & (MEM_Real | MEM_Int)) == MEM_Int) {
|
|
|
|
pRec->r = pRec->i;
|
|
|
|
} else if ((flags & (MEM_Real | MEM_Int)) == 0) {
|
|
|
|
pRec->r = __dbsql_atof(pRec->z);
|
|
|
|
}
|
|
|
|
__entity_release_mem(pRec);
|
|
|
|
z = pRec->zShort;
|
|
|
|
__str_real_as_sortable(pRec->r, z);
|
|
|
|
len = strlen(z);
|
|
|
|
pRec->z = 0;
|
|
|
|
pRec->flags = MEM_Real;
|
|
|
|
pRec->n = len+1;
|
|
|
|
nByte += (pRec->n + 1);
|
|
|
|
} else {
|
|
|
|
nByte += (pRec->n + 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (addRowid)
|
|
|
|
nByte += sizeof(u_int32_t);
|
|
|
|
if (nByte <= NBFS) {
|
|
|
|
zNewKey = zTemp;
|
|
|
|
} else {
|
|
|
|
if (__dbsql_calloc(NULL, 1, nByte, &zNewKey) == ENOMEM)
|
|
|
|
goto no_mem;
|
|
|
|
}
|
|
|
|
j = 0;
|
|
|
|
pRec = &pTos[1 - nField];
|
|
|
|
for (i = 0; i < nField; i++, pRec++) {
|
|
|
|
if (pRec->flags & MEM_Null) {
|
|
|
|
zNewKey[j++] = 'a';
|
|
|
|
zNewKey[j++] = 0;
|
|
|
|
} else if (pRec->flags == MEM_Real) {
|
|
|
|
zNewKey[j++] = 'b';
|
|
|
|
memcpy(&zNewKey[j], pRec->zShort, pRec->n);
|
|
|
|
j += pRec->n;
|
|
|
|
} else {
|
|
|
|
DBSQL_ASSERT(pRec->flags & MEM_Str);
|
|
|
|
zNewKey[j++] = 'c';
|
|
|
|
memcpy(&zNewKey[j], pRec->z, pRec->n);
|
|
|
|
j += pRec->n;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (addRowid) {
|
|
|
|
u_int32_t iKey;
|
|
|
|
pRec = &pTos[-nField];
|
|
|
|
DBSQL_ASSERT(pRec >= p->aStack);
|
|
|
|
__entity_to_int(pRec);
|
|
|
|
iKey = INT_TO_KEY(pRec->i);
|
|
|
|
memcpy(&zNewKey[j], &iKey, sizeof(u_int32_t));
|
|
|
|
__pop_stack(&pTos, (nField + 1));
|
|
|
|
if (pOp->p2 && containsNull)
|
|
|
|
pc = pOp->p2 - 1;
|
|
|
|
} else {
|
|
|
|
if (pOp->p2 == 0)
|
|
|
|
__pop_stack(&pTos, nField);
|
|
|
|
}
|
|
|
|
pTos++;
|
|
|
|
pTos->n = nByte;
|
|
|
|
if (nByte <= NBFS) {
|
|
|
|
DBSQL_ASSERT(zNewKey == zTemp);
|
|
|
|
pTos->z = pTos->zShort;
|
|
|
|
memcpy(pTos->zShort, zTemp, nByte);
|
|
|
|
pTos->flags = MEM_Str | MEM_Short;
|
|
|
|
} else {
|
|
|
|
pTos->z = zNewKey;
|
|
|
|
pTos->flags = MEM_Str | MEM_Dyn;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: IncrKey * * *
|
|
|
|
**
|
|
|
|
** The top of the stack should contain an index key generated by
|
|
|
|
** The MakeKey opcode. This routine increases the least significant
|
|
|
|
** byte of that key by one. This is used so that the MoveTo opcode
|
|
|
|
** will move to the first entry greater than the key rather than to
|
|
|
|
** the key itself.
|
|
|
|
*/
|
|
|
|
case OP_IncrKey: {
|
|
|
|
DBSQL_ASSERT(pTos >= p->aStack);
|
|
|
|
/*
|
|
|
|
* The IncrKey opcode is only applied to keys generated by
|
|
|
|
* MakeKey or MakeIdxKey and the results of those operands
|
|
|
|
* are always dynamic strings or zShort[] strings. So we
|
|
|
|
* are always free to modify the string in place.
|
|
|
|
*/
|
|
|
|
DBSQL_ASSERT(pTos->flags & (MEM_Dyn | MEM_Short));
|
|
|
|
pTos->z[pTos->n-1]++;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: Checkpoint P1 * *
|
|
|
|
**
|
|
|
|
** A checkpoint will bound recovery time.
|
|
|
|
** Begin a checkpoint. A checkpoint is the beginning of a operation that
|
|
|
|
** is part of a larger transaction but which might need to be rolled back
|
|
|
|
** itself without effecting the containing transaction. A checkpoint will
|
|
|
|
** automatically either be committed or rolled back (aborted) when the VDBE
|
|
|
|
** halts.
|
|
|
|
**
|
|
|
|
** The checkpoint is begun on the database file with index P1. The main
|
|
|
|
** database file has an index of 0 and the file used for temporary tables
|
|
|
|
** has an index of 1.
|
|
|
|
*/
|
|
|
|
case OP_Checkpoint: {
|
|
|
|
int i = pOp->p1;
|
|
|
|
if (i >= 0 && i < db->nDb && db->aDb[i].pBt) {
|
|
|
|
rc = __sm_checkpoint(db->aDb[i].pBt);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: Transaction * * *
|
|
|
|
**
|
|
|
|
** Begin a transaction. The transaction ends when a Commit or Rollback
|
|
|
|
** opcode is encountered. Depending on the ON CONFLICT setting, the
|
|
|
|
** transaction might also be rolled back if an error is encountered.
|
|
|
|
** If the database was not opened with an environment, or the environment
|
|
|
|
** does not support transactions (i.e. it is a DS or CDS environment) this
|
|
|
|
** will not actually begin a transaction.
|
|
|
|
*/
|
|
|
|
case OP_Transaction: {
|
|
|
|
int i = pOp->p1;
|
|
|
|
DBSQL_ASSERT(i >= 0 && i < db->nDb);
|
|
|
|
if (db->aDb[i].inTrans) /* TODO: do we need this? */
|
|
|
|
break;
|
|
|
|
rc = __sm_begin_txn(db->aDb[i].pBt);
|
|
|
|
db->aDb[i].inTrans = 1;
|
|
|
|
p->undoTransOnError = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: Commit * * *
|
|
|
|
**
|
|
|
|
** Cause all modifications to the database that have been made during this
|
|
|
|
** transaction to actually take effect. No additional modifications
|
|
|
|
** are allowed until another transaction is started.
|
|
|
|
*/
|
|
|
|
case OP_Commit: {
|
|
|
|
int i = pOp->p1;
|
|
|
|
DBSQL_ASSERT(i >= 0 && i < db->nDb);
|
|
|
|
if (db->xCommitCallback != 0) {
|
|
|
|
if (__safety_off(db))
|
|
|
|
goto abort_due_to_misuse;
|
|
|
|
if (db->xCommitCallback(db->pCommitArg) != 0) {
|
|
|
|
rc = DBSQL_CONSTRAINT;
|
|
|
|
}
|
|
|
|
if (__safety_on(db))
|
|
|
|
goto abort_due_to_misuse;
|
|
|
|
}
|
|
|
|
if (db->aDb[i].inTrans) {
|
|
|
|
rc = __sm_commit_txn(db->aDb[i].pBt);
|
|
|
|
db->aDb[i].inTrans = 0;
|
|
|
|
}
|
|
|
|
if (rc == DBSQL_SUCCESS) {
|
|
|
|
__commit_internal_changes(db);
|
|
|
|
} else {
|
|
|
|
__sm_abort_txn(db->aDb[i].pBt);
|
|
|
|
__rollback_internal_changes(db);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: Rollback * * *
|
|
|
|
**
|
|
|
|
** Cause all modifications to the database that have been made since the
|
|
|
|
** start of the transaction to be undone. The database is restored to its state
|
|
|
|
** before the OP_Transaction opcode was executed. No additional modifications
|
|
|
|
** are allowed until another transaction is started.
|
|
|
|
**
|
|
|
|
** This instruction automatically closes all cursors and releases both
|
|
|
|
** the read and write locks on the indicated database.
|
|
|
|
*/
|
|
|
|
case OP_Rollback: {
|
|
|
|
int i = pOp->p1;
|
|
|
|
DBSQL_ASSERT(i >= 0 && i < db->nDb);
|
|
|
|
DBSQL_ASSERT(db->aDb[i].inTrans); /* TODO: do we need this? */
|
|
|
|
if (db->aDb[i].inTrans) {
|
|
|
|
__sm_abort_txn(db->aDb[i].pBt);
|
|
|
|
__rollback_internal_changes(db);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: SetFormatVersion P1 * *
|
|
|
|
**
|
|
|
|
** Write the top of the stack into database P1 as its format_version.
|
|
|
|
** A transaction must be started before executing this opcode.
|
|
|
|
*/
|
|
|
|
case OP_SetFormatVersion: {
|
|
|
|
DBSQL_ASSERT(pOp->p1 >= 0 && pOp->p1 < db->nDb);
|
|
|
|
DBSQL_ASSERT(db->aDb[pOp->p1].pBt != 0);
|
|
|
|
DBSQL_ASSERT(pTos >= p->aStack);
|
|
|
|
__entity_to_int(pTos);
|
|
|
|
rc = __sm_set_format_version(db->aDb[pOp->p1].pBt, pOp->p1, pTos->i);
|
|
|
|
DBSQL_ASSERT(pTos->flags == MEM_Int);
|
|
|
|
pTos--;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: SetSchemaSignature P1 * *
|
|
|
|
**
|
|
|
|
** The top of the stack is the schema generation version number.
|
|
|
|
** Every time something changes effecting the schema it is updated.
|
|
|
|
** This value needs to be stored. P1 is the database to effect.
|
|
|
|
*/
|
|
|
|
case OP_SetSchemaSignature: {
|
|
|
|
DBSQL_ASSERT(pTos >= p->aStack);
|
|
|
|
__entity_to_int(pTos);
|
|
|
|
rc = __sm_set_schema_sig(db->aDb[pOp->p1].pBt, pTos->i);
|
|
|
|
DBSQL_ASSERT(pTos->flags == MEM_Int);
|
|
|
|
pTos--;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: VerifySchemaSignature P1 P2 *
|
|
|
|
**
|
|
|
|
** The schema signature is shared by all the DB that make up
|
|
|
|
** this database. Ask the storage manager to grab that local
|
|
|
|
** value and make sure that it is equal to P2. P1 is the database
|
|
|
|
** number which is 0 for the main database file and 1 for the file
|
|
|
|
** holding temporary tables and some higher number for auxiliary
|
|
|
|
** databases.
|
|
|
|
**
|
|
|
|
** The cookie changes its value whenever the database schema changes.
|
|
|
|
** This operation is used to detect when that the cookie has changed.
|
|
|
|
** If not in sync, the current process needs to reread the schema.
|
|
|
|
**
|
|
|
|
** This operation should only occur during a transaction as a schema
|
|
|
|
** change in the post-check and pre-commit would spell trouble for
|
|
|
|
** everyone (and their data).
|
|
|
|
*/
|
|
|
|
case OP_VerifySchemaSignature: {
|
2007-10-21 00:33:16 +00:00
|
|
|
int sig;
|
2007-03-10 19:04:07 +00:00
|
|
|
DBSQL_ASSERT(pOp->p1 >= 0 && pOp->p1 < db->nDb);
|
|
|
|
rc = __sm_get_schema_sig(db->aDb[pOp->p1].pBt, &sig);
|
|
|
|
if (rc == DBSQL_SUCCESS && sig != pOp->p2) {
|
|
|
|
__str_append(&p->zErrMsg, "database schema has changed",
|
|
|
|
(char*)0);
|
|
|
|
rc = DBSQL_SCHEMA;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: OpenRead P1 P2 P3
|
|
|
|
**
|
|
|
|
** Open a read-only cursor for the database table whose root page is
|
|
|
|
** P2 in a database file. The database file is determined by an
|
|
|
|
** integer from the top of the stack. 0 means the main database and
|
|
|
|
** 1 means the database used for temporary tables. Give the new
|
|
|
|
** cursor an identifier of P1. The P1 values need not be contiguous
|
|
|
|
** but all P1 values should be small integers. It is an error for
|
|
|
|
** P1 to be negative.
|
|
|
|
**
|
|
|
|
** If P2==0 then take the root page number from the next of the stack.
|
|
|
|
**
|
|
|
|
** There will be a read lock on the database whenever there is an
|
|
|
|
** open cursor. If the database was unlocked prior to this instruction
|
|
|
|
** then a read lock is acquired as part of this instruction. A read
|
|
|
|
** lock allows other processes to read the database but prohibits
|
|
|
|
** any other process from modifying the database. The read lock is
|
|
|
|
** released when all cursors are closed. If this instruction attempts
|
|
|
|
** to get a read lock but fails, the script terminates with an
|
|
|
|
** DBSQL_BUSY error code.
|
|
|
|
**
|
|
|
|
** The P3 value is the name of the table or index being opened.
|
|
|
|
** The P3 value is not actually used by this opcode and may be
|
|
|
|
** omitted. But the code generator usually inserts the index or
|
|
|
|
** table name into P3 to make the code easier to read.
|
|
|
|
**
|
|
|
|
** See also OpenWrite.
|
|
|
|
*/
|
|
|
|
/* Opcode: OpenWrite P1 P2 P3
|
|
|
|
**
|
|
|
|
** Open a read/write cursor named P1 on the table or index whose root
|
|
|
|
** page is P2. If P2==0 then take the root page number from the stack.
|
|
|
|
**
|
|
|
|
** The P3 value is the name of the table or index being opened.
|
|
|
|
** The P3 value is not actually used by this opcode and may be
|
|
|
|
** omitted. But the code generator usually inserts the index or
|
|
|
|
** table name into P3 to make the code easier to read.
|
|
|
|
**
|
|
|
|
** This instruction works just like OpenRead except that it opens the cursor
|
|
|
|
** in read/write mode. For a given table, there can be one or more read-only
|
|
|
|
** cursors or a single read/write cursor but not both.
|
|
|
|
**
|
|
|
|
** See also OpenRead.
|
|
|
|
*/
|
|
|
|
case OP_OpenRead: /* FALLTHROUGH */
|
|
|
|
case OP_OpenWrite: {
|
|
|
|
int busy = 0;
|
|
|
|
int i = pOp->p1;
|
|
|
|
int p2 = pOp->p2;
|
|
|
|
int wrFlag;
|
|
|
|
sm_t *pX;
|
|
|
|
int iDb;
|
|
|
|
|
|
|
|
DBSQL_ASSERT(pTos >= p->aStack);
|
|
|
|
__entity_to_int(pTos);
|
|
|
|
iDb = pTos->i;
|
|
|
|
pTos--;
|
|
|
|
DBSQL_ASSERT(iDb >= 0 && iDb < db->nDb);
|
|
|
|
pX = db->aDb[iDb].pBt;
|
|
|
|
DBSQL_ASSERT(pX != 0);
|
|
|
|
wrFlag = (pOp->opcode == OP_OpenWrite);
|
|
|
|
if (p2 <= 0) {
|
|
|
|
DBSQL_ASSERT(pTos >= p->aStack);
|
|
|
|
__entity_to_int(pTos);
|
|
|
|
p2 = pTos->i;
|
|
|
|
pTos--;
|
|
|
|
if(p2 < 2) {
|
|
|
|
__str_append(&p->zErrMsg,
|
|
|
|
"root page number less than 2", (char*)0);
|
|
|
|
rc = DBSQL_INTERNAL;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
DBSQL_ASSERT(i >= 0);
|
|
|
|
if (__expand_cursor_array_size(p, i))
|
|
|
|
goto no_mem;
|
|
|
|
__vdbe_cleanup_cursor(&p->aCsr[i]);
|
|
|
|
memset(&p->aCsr[i], 0, sizeof(cursor_t));
|
|
|
|
p->aCsr[i].nullRow = 1;
|
|
|
|
if (pX == 0)
|
|
|
|
break;
|
|
|
|
do {
|
|
|
|
rc = __sm_cursor(pX, p2, wrFlag, &p->aCsr[i].pCursor);
|
|
|
|
switch(rc) {
|
|
|
|
case DBSQL_BUSY: {
|
|
|
|
if (db->xBusyCallback == 0) {
|
|
|
|
p->pc = pc;
|
|
|
|
p->rc = DBSQL_BUSY;
|
|
|
|
/* Operands must remain on stack */
|
|
|
|
p->pTos = &pTos[1 + (pOp->p2 <= 0)];
|
|
|
|
return DBSQL_BUSY;
|
|
|
|
} else if ((*db->xBusyCallback)(db, db->pBusyArg,
|
|
|
|
pOp->p3, ++busy) == 0) {
|
|
|
|
__str_append(&p->zErrMsg, dbsql_strerror(rc),
|
|
|
|
(char*)0);
|
|
|
|
busy = 0;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case DBSQL_SUCCESS: {
|
|
|
|
busy = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default: {
|
|
|
|
goto abort_due_to_error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} while(busy);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: OpenTemp P1 P2 *
|
|
|
|
**
|
|
|
|
** Open a new cursor to a transient table.
|
|
|
|
** The transient cursor is always opened read/write even if
|
|
|
|
** the main database is read-only. The transient table is deleted
|
|
|
|
** automatically when the cursor is closed.
|
|
|
|
**
|
|
|
|
** The cursor points to a BTree table if P2==0 and to a BTree index
|
|
|
|
** if P2==1. A BTree table must have an integer key and can have arbitrary
|
|
|
|
** data. A BTree index has no data but can have an arbitrary key.
|
|
|
|
**
|
|
|
|
** This opcode is used for tables that exist for the duration of a single
|
|
|
|
** SQL statement only. Tables created using CREATE TEMPORARY TABLE
|
|
|
|
** are opened using OP_OpenRead or OP_OpenWrite. "Temporary" in the
|
|
|
|
** context of this opcode means for the duration of a single SQL statement
|
|
|
|
** whereas "Temporary" in the context of CREATE TABLE means for the duration
|
|
|
|
** of the connection to the database. Same word; different meanings.
|
|
|
|
*/
|
|
|
|
case OP_OpenTemp: {
|
|
|
|
int i = pOp->p1;
|
|
|
|
cursor_t *pCx;
|
|
|
|
DBSQL_ASSERT(i >= 0);
|
|
|
|
if (__expand_cursor_array_size(p, i))
|
|
|
|
goto no_mem;
|
|
|
|
pCx = &p->aCsr[i];
|
|
|
|
__vdbe_cleanup_cursor(pCx);
|
|
|
|
memset(pCx, 0, sizeof(*pCx));
|
|
|
|
pCx->nullRow = 1;
|
|
|
|
|
|
|
|
rc = __sm_create(db, 0, 1,
|
|
|
|
(F_ISSET(db, DBSQL_DurableTemp) == 0), &pCx->pBt);
|
|
|
|
|
|
|
|
if (rc == DBSQL_SUCCESS) {
|
|
|
|
rc = __sm_begin_txn(pCx->pBt);
|
|
|
|
}
|
|
|
|
if (rc == DBSQL_SUCCESS) {
|
|
|
|
if (pOp->p2) {
|
|
|
|
int pgno;
|
|
|
|
rc = __sm_create_index(pCx->pBt, &pgno);
|
|
|
|
if (rc == DBSQL_SUCCESS) {
|
|
|
|
rc = __sm_cursor(pCx->pBt, pgno, 1,
|
|
|
|
&pCx->pCursor);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
rc = __sm_cursor(pCx->pBt, 2, 1, &pCx->pCursor);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: OpenPseudo P1 * *
|
|
|
|
**
|
|
|
|
** Open a new cursor that points to a fake table that contains a single
|
|
|
|
** row of data. Any attempt to write a second row of data causes the
|
|
|
|
** first row to be deleted. All data is deleted when the cursor is
|
|
|
|
** closed.
|
|
|
|
**
|
|
|
|
** A pseudo-table created by this opcode is useful for holding the
|
|
|
|
** NEW or OLD tables in a trigger.
|
|
|
|
*/
|
|
|
|
case OP_OpenPseudo: {
|
|
|
|
int i = pOp->p1;
|
|
|
|
cursor_t *pCx;
|
|
|
|
DBSQL_ASSERT(i >= 0);
|
|
|
|
if (__expand_cursor_array_size(p, i))
|
|
|
|
goto no_mem;
|
|
|
|
pCx = &p->aCsr[i];
|
|
|
|
__vdbe_cleanup_cursor(pCx);
|
|
|
|
memset(pCx, 0, sizeof(*pCx));
|
|
|
|
pCx->nullRow = 1;
|
|
|
|
pCx->pseudoTable = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: Close P1 * *
|
|
|
|
**
|
|
|
|
** Close a cursor previously opened as P1. If P1 is not
|
|
|
|
** currently open, this instruction is a no-op.
|
|
|
|
*/
|
|
|
|
case OP_Close: {
|
|
|
|
int i = pOp->p1;
|
|
|
|
if (i >= 0 && i < p->nCursor) {
|
|
|
|
__vdbe_cleanup_cursor(&p->aCsr[i]);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: MoveTo P1 P2 *
|
|
|
|
**
|
|
|
|
** Pop the top of the stack and use its value as a key. Reposition
|
|
|
|
** cursor P1 so that it points to an entry with a matching key. If
|
|
|
|
** the table contains no record with a matching key, then the cursor
|
|
|
|
** is left pointing at the first record that is greater than the key.
|
|
|
|
** If there are no records greater than the key and P2 is not zero,
|
|
|
|
** then an immediate jump to P2 is made.
|
|
|
|
**
|
|
|
|
** See also: Found, NotFound, Distinct, MoveLt
|
|
|
|
*/
|
|
|
|
/* Opcode: MoveLt P1 P2 *
|
|
|
|
**
|
|
|
|
** Pop the top of the stack and use its value as a key. Reposition
|
|
|
|
** cursor P1 so that it points to the entry with the largest key that is
|
|
|
|
** less than the key popped from the stack.
|
|
|
|
** If there are no records less than than the key and P2
|
|
|
|
** is not zero then an immediate jump to P2 is made.
|
|
|
|
**
|
|
|
|
** See also: MoveTo
|
|
|
|
*/
|
|
|
|
case OP_MoveLt: /* FALLTHROUGH */
|
|
|
|
case OP_MoveTo: {
|
|
|
|
int i = pOp->p1;
|
|
|
|
cursor_t *pC;
|
|
|
|
|
|
|
|
DBSQL_ASSERT(pTos >= p->aStack);
|
|
|
|
DBSQL_ASSERT(i >= 0 && i < p->nCursor);
|
|
|
|
pC = &p->aCsr[i];
|
|
|
|
if (pC->pCursor != 0) {
|
|
|
|
int res, oc;
|
|
|
|
pC->nullRow = 0;
|
|
|
|
if (pTos->flags & MEM_Int) {
|
|
|
|
int iKey = INT_TO_KEY(pTos->i);
|
|
|
|
if (pOp->p2 == 0 && pOp->opcode == OP_MoveTo) {
|
|
|
|
pC->movetoTarget = iKey;
|
|
|
|
pC->deferredMoveto = 1;
|
|
|
|
__entity_release_mem(pTos);
|
|
|
|
pTos--;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
__sm_moveto(pC->pCursor, (char*)&iKey, sizeof(int),
|
|
|
|
&res);
|
|
|
|
pC->lastRecno = pTos->i;
|
|
|
|
pC->recnoIsValid = (res == 0);
|
|
|
|
} else {
|
|
|
|
__entity_as_string(pTos);
|
|
|
|
__sm_moveto(pC->pCursor, pTos->z, pTos->n, &res);
|
|
|
|
pC->recnoIsValid = 0;
|
|
|
|
}
|
|
|
|
pC->deferredMoveto = 0;
|
|
|
|
#ifdef CONFIG_TEST
|
|
|
|
dbsql_search_count++;
|
|
|
|
#endif
|
|
|
|
oc = pOp->opcode;
|
|
|
|
if (oc == OP_MoveTo && res < 0) {
|
|
|
|
__sm_next(pC->pCursor, &res);
|
|
|
|
pC->recnoIsValid = 0;
|
|
|
|
if (res && pOp->p2 > 0) {
|
|
|
|
pc = pOp->p2 - 1;
|
|
|
|
}
|
|
|
|
} else if (oc == OP_MoveLt) {
|
|
|
|
if (res >= 0) {
|
|
|
|
__sm_prev(pC->pCursor, &res);
|
|
|
|
pC->recnoIsValid = 0;
|
|
|
|
} else {
|
|
|
|
/*
|
|
|
|
* 'res' might be negative because the table is
|
|
|
|
* empty. Check to see if this is the case.
|
|
|
|
*/
|
|
|
|
int keysize;
|
|
|
|
res = (__sm_key_size(pC->pCursor, &keysize)
|
|
|
|
!= 0 || keysize == 0);
|
|
|
|
}
|
|
|
|
if (res && pOp->p2 > 0) {
|
|
|
|
pc = pOp->p2 - 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
__entity_release_mem(pTos);
|
|
|
|
pTos--;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: Distinct P1 P2 *
|
|
|
|
**
|
|
|
|
** Use the top of the stack as a string key. If a record with that key does
|
|
|
|
** not exist in the table of cursor P1, then jump to P2. If the record
|
|
|
|
** does already exist, then fall thru. The cursor is left pointing
|
|
|
|
** at the record if it exists. The key is not popped from the stack.
|
|
|
|
**
|
|
|
|
** This operation is similar to NotFound except that this operation
|
|
|
|
** does not pop the key from the stack.
|
|
|
|
**
|
|
|
|
** See also: Found, NotFound, MoveTo, IsUnique, NotExists
|
|
|
|
*/
|
|
|
|
/* Opcode: Found P1 P2 *
|
|
|
|
**
|
|
|
|
** Use the top of the stack as a string key. If a record with that key
|
|
|
|
** does exist in table of P1, then jump to P2. If the record
|
|
|
|
** does not exist, then fall thru. The cursor is left pointing
|
|
|
|
** to the record if it exists. The key is popped from the stack.
|
|
|
|
**
|
|
|
|
** See also: Distinct, NotFound, MoveTo, IsUnique, NotExists
|
|
|
|
*/
|
|
|
|
/* Opcode: NotFound P1 P2 *
|
|
|
|
**
|
|
|
|
** Use the top of the stack as a string key. If a record with that key
|
|
|
|
** does not exist in table of P1, then jump to P2. If the record
|
|
|
|
** does exist, then fall thru. The cursor is left pointing to the
|
|
|
|
** record if it exists. The key is popped from the stack.
|
|
|
|
**
|
|
|
|
** The difference between this operation and Distinct is that
|
|
|
|
** Distinct does not pop the key from the stack.
|
|
|
|
**
|
|
|
|
** See also: Distinct, Found, MoveTo, NotExists, IsUnique
|
|
|
|
*/
|
|
|
|
case OP_Distinct: /* FALLTHROUGH */
|
|
|
|
case OP_NotFound: /* FALLTHROUGH */
|
|
|
|
case OP_Found: {
|
|
|
|
int i = pOp->p1;
|
|
|
|
int alreadyExists = 0;
|
|
|
|
cursor_t *pC;
|
|
|
|
DBSQL_ASSERT(pTos >= p->aStack);
|
|
|
|
DBSQL_ASSERT(i >= 0 && i < p->nCursor);
|
|
|
|
if ((pC = &p->aCsr[i])->pCursor != 0) {
|
|
|
|
int res, rx;
|
|
|
|
__entity_as_string(pTos);
|
|
|
|
rx = __sm_moveto(pC->pCursor, pTos->z, pTos->n, &res);
|
|
|
|
alreadyExists = (rx == DBSQL_SUCCESS && res == 0);
|
|
|
|
pC->deferredMoveto = 0;
|
|
|
|
}
|
|
|
|
if (pOp->opcode == OP_Found) {
|
|
|
|
if (alreadyExists)
|
|
|
|
pc = pOp->p2 - 1;
|
|
|
|
} else {
|
|
|
|
if (!alreadyExists)
|
|
|
|
pc = pOp->p2 - 1;
|
|
|
|
}
|
|
|
|
if (pOp->opcode != OP_Distinct) {
|
|
|
|
__entity_release_mem(pTos);
|
|
|
|
pTos--;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: IsUnique P1 P2 *
|
|
|
|
**
|
|
|
|
** The top of the stack is an integer record number. Call this
|
|
|
|
** record number R. The next on the stack is an index key created
|
|
|
|
** using MakeIdxKey. Call it K. This instruction pops R from the
|
|
|
|
** stack but it leaves K unchanged.
|
|
|
|
**
|
|
|
|
** P1 is an index. So all but the last four bytes of K are an
|
|
|
|
** index string. The last four bytes of K are a record number.
|
|
|
|
**
|
|
|
|
** This instruction asks if there is an entry in P1 where the
|
|
|
|
** index string matches K but the record number is different
|
|
|
|
** from R. If there is no such entry, then there is an immediate
|
|
|
|
** jump to P2. If any entry does exist where the index string
|
|
|
|
** matches K but the record number is not R, then the record
|
|
|
|
** number for that entry is pushed onto the stack and control
|
|
|
|
** falls through to the next instruction.
|
|
|
|
**
|
|
|
|
** See also: Distinct, NotFound, NotExists, Found
|
|
|
|
*/
|
|
|
|
case OP_IsUnique: {
|
|
|
|
int i = pOp->p1;
|
|
|
|
mem_t *pNos = &pTos[-1];
|
|
|
|
sm_cursor_t *pCrsr;
|
|
|
|
int R;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Pop the value R off the top of the stack
|
|
|
|
*/
|
|
|
|
DBSQL_ASSERT(pNos >= p->aStack);
|
|
|
|
__entity_to_int(pTos);
|
|
|
|
R = pTos->i;
|
|
|
|
pTos--;
|
|
|
|
DBSQL_ASSERT(i >= 0 && i <= p->nCursor);
|
|
|
|
if ((pCrsr = p->aCsr[i].pCursor) != 0) {
|
|
|
|
int res, rc;
|
|
|
|
int v; /* The record number on the P1 entry
|
|
|
|
that matches K. */
|
|
|
|
char *zKey; /* The value of K */
|
|
|
|
int nKey; /* Number of bytes in K */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Make sure K is a string and make zKey point to K
|
|
|
|
*/
|
|
|
|
__entity_as_string(pNos);
|
|
|
|
zKey = pNos->z;
|
|
|
|
nKey = pNos->n;
|
|
|
|
DBSQL_ASSERT(nKey >= 4);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Search for an entry in P1 where all but the last four
|
|
|
|
* bytes match K. If there is no such entry, jump immediately
|
|
|
|
* to P2.
|
|
|
|
*/
|
|
|
|
DBSQL_ASSERT(p->aCsr[i].deferredMoveto == 0);
|
|
|
|
rc = __sm_moveto(pCrsr, zKey, (nKey - 4), &res);
|
|
|
|
if (rc != DBSQL_SUCCESS)
|
|
|
|
goto abort_due_to_error;
|
|
|
|
if (res < 0) {
|
|
|
|
rc = __sm_next(pCrsr, &res);
|
|
|
|
if (res) {
|
|
|
|
pc = pOp->p2 - 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
rc = __sm_key_compare(pCrsr, zKey, (nKey - 4), 4, &res);
|
|
|
|
if (rc != DBSQL_SUCCESS)
|
|
|
|
goto abort_due_to_error;
|
|
|
|
if (res > 0) {
|
|
|
|
pc = pOp->p2 - 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* At this point, pCrsr is pointing to an entry in P1 where
|
|
|
|
* all but the last for bytes of the key match K. Check to
|
|
|
|
* see if the last four bytes of the key are different from
|
|
|
|
* R. If the last four bytes equal R then jump immediately
|
|
|
|
* to P2.
|
|
|
|
*/
|
|
|
|
__sm_key(pCrsr, (nKey - 4), 4, (char*)&v);
|
|
|
|
v = KEY_TO_INT(v);
|
|
|
|
if (v == R) {
|
|
|
|
pc = pOp->p2 - 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* The last four bytes of the key are different from R.
|
|
|
|
* Convert the last four bytes of the key into an integer
|
|
|
|
* and push it onto the stack. (These bytes are the record
|
|
|
|
* number of an entry that violates a UNIQUE constraint.)
|
|
|
|
*/
|
|
|
|
pTos++;
|
|
|
|
pTos->i = v;
|
|
|
|
pTos->flags = MEM_Int;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: NotExists P1 P2 *
|
|
|
|
**
|
|
|
|
** Use the top of the stack as a integer key. If a record with that key
|
|
|
|
** does not exist in table of P1, then jump to P2. If the record
|
|
|
|
** does exist, then fall thru. The cursor is left pointing to the
|
|
|
|
** record if it exists. The integer key is popped from the stack.
|
|
|
|
**
|
|
|
|
** The difference between this operation and NotFound is that this
|
|
|
|
** operation assumes the key is an integer and NotFound assumes it
|
|
|
|
** is a string.
|
|
|
|
**
|
|
|
|
** See also: Distinct, Found, MoveTo, NotFound, IsUnique
|
|
|
|
*/
|
|
|
|
case OP_NotExists: {
|
|
|
|
int i = pOp->p1;
|
|
|
|
sm_cursor_t *pCrsr;
|
|
|
|
DBSQL_ASSERT(pTos >= p->aStack);
|
|
|
|
DBSQL_ASSERT(i >= 0 && i < p->nCursor);
|
|
|
|
if ((pCrsr = p->aCsr[i].pCursor) != 0) {
|
|
|
|
int res, rx, iKey;
|
|
|
|
DBSQL_ASSERT(pTos->flags & MEM_Int);
|
|
|
|
iKey = INT_TO_KEY(pTos->i);
|
|
|
|
rx = __sm_moveto(pCrsr, (char*)&iKey, sizeof(int), &res);
|
|
|
|
p->aCsr[i].lastRecno = pTos->i;
|
|
|
|
p->aCsr[i].recnoIsValid = res==0;
|
|
|
|
p->aCsr[i].nullRow = 0;
|
|
|
|
if (rx != DBSQL_SUCCESS || res != 0) {
|
|
|
|
pc = pOp->p2 - 1;
|
|
|
|
p->aCsr[i].recnoIsValid = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
__entity_release_mem(pTos);
|
|
|
|
pTos--;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: NewRecno P1 * *
|
|
|
|
**
|
|
|
|
** Get a new integer record number to be used as the key to a table.
|
|
|
|
** The record number is not previously used as a key in the database
|
|
|
|
** table that cursor P1 points to. The new record number is pushed
|
|
|
|
** onto the stack.
|
|
|
|
*/
|
|
|
|
case OP_NewRecno: {
|
2007-03-24 21:59:14 +00:00
|
|
|
static struct drand48_data rand;
|
|
|
|
static int first_time = 1;
|
|
|
|
if (first_time) {
|
|
|
|
first_time = 0;
|
2007-10-21 00:31:56 +00:00
|
|
|
srand48_r(1, &rand); /* XXX create a portable rand function */
|
2007-03-24 21:59:14 +00:00
|
|
|
}
|
2007-03-10 19:04:07 +00:00
|
|
|
int i = pOp->p1;
|
2007-10-21 00:33:16 +00:00
|
|
|
int v = 0;
|
2007-03-10 19:04:07 +00:00
|
|
|
cursor_t *pC;
|
|
|
|
DBSQL_ASSERT(i >= 0 && i < p->nCursor);
|
|
|
|
if ((pC = &p->aCsr[i])->pCursor == 0) {
|
|
|
|
v = 0;
|
|
|
|
} else {
|
|
|
|
/* !!!
|
|
|
|
* The next rowid or record number (different terms for the
|
|
|
|
* same thing) is obtained in a two-step algorithm.
|
|
|
|
*
|
|
|
|
* First we attempt to find the largest existing rowid and
|
|
|
|
* add one to that. But if the largest existing rowid is
|
|
|
|
* already the maximum positive integer, we have to fall
|
|
|
|
* through to the second probabilistic algorithm.
|
|
|
|
*
|
|
|
|
* The second algorithm is to select a rowid at random and
|
|
|
|
* see if it already exists in the table. If it does not
|
|
|
|
* exist, we have succeeded. If the random rowid does exist,
|
|
|
|
* we select a new one and try again, up to 1000 times.
|
|
|
|
*
|
|
|
|
* For a table with less than 2 billion entries, the
|
|
|
|
* probability of not finding a unused rowid is about
|
|
|
|
* 1.0e-300. This is a non-zero probability, but it is
|
|
|
|
* still vanishingly small and should never cause a problem.
|
|
|
|
* You are much, much more likely to have a hardware failure
|
|
|
|
* than for this algorithm to fail.
|
|
|
|
*
|
|
|
|
* The analysis in the previous paragraph assumes that you
|
|
|
|
* have a good source of random numbers. Is a library
|
|
|
|
* function like lrand48() good enough? Maybe. Maybe not.
|
|
|
|
* It's hard to know whether there might be subtle bugs is
|
|
|
|
* some implementations of lrand48() that could cause problems.
|
|
|
|
* To avoid uncertainty, we implement our own random number
|
|
|
|
* generator based on the RC4 algorithm.
|
|
|
|
*
|
|
|
|
* To promote locality of reference for repetitive inserts, the
|
|
|
|
* first few attempts at chosing a random rowid pick values
|
|
|
|
* just a little larger than the previous rowid. This has
|
|
|
|
* been shown experimentally to double the speed of the COPY
|
|
|
|
* operation.
|
|
|
|
*/
|
|
|
|
int res, rx, cnt, x;
|
|
|
|
cnt = 0;
|
|
|
|
if (!pC->useRandomRowid) {
|
|
|
|
if (pC->nextRowidValid) {
|
|
|
|
v = pC->nextRowid;
|
|
|
|
} else {
|
|
|
|
rx = __sm_last(pC->pCursor, &res);
|
|
|
|
if (res) {
|
|
|
|
v = 1;
|
|
|
|
} else {
|
|
|
|
__sm_key(pC->pCursor, 0, sizeof(v),
|
|
|
|
(void*)&v);
|
|
|
|
v = KEY_TO_INT(v);
|
|
|
|
if (v == 0x7fffffff) {
|
|
|
|
pC->useRandomRowid = 1;
|
|
|
|
} else {
|
|
|
|
v++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (v < 0x7fffffff) {
|
|
|
|
pC->nextRowidValid = 1;
|
|
|
|
pC->nextRowid = v + 1;
|
|
|
|
} else {
|
|
|
|
pC->nextRowidValid = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (pC->useRandomRowid) {
|
|
|
|
v = db->priorNewRowid;
|
|
|
|
cnt = 0;
|
|
|
|
do {
|
|
|
|
if (v == 0 || cnt > 2) {
|
2007-03-24 21:59:14 +00:00
|
|
|
rand32_r(&rand, &v);
|
2007-03-10 19:04:07 +00:00
|
|
|
if (cnt < 5)
|
|
|
|
v &= 0xffffff;
|
|
|
|
} else {
|
|
|
|
u_int8_t rb;
|
2007-03-24 21:59:14 +00:00
|
|
|
rand8_r(&rand, &rb);
|
2007-03-10 19:04:07 +00:00
|
|
|
v += rb + 1;
|
|
|
|
}
|
|
|
|
if (v == 0)
|
|
|
|
continue;
|
|
|
|
x = INT_TO_KEY(v);
|
|
|
|
rx = __sm_moveto(pC->pCursor, &x, sizeof(int),
|
|
|
|
&res);
|
|
|
|
cnt++;
|
|
|
|
} while(cnt < 1000 && rx == DBSQL_SUCCESS && res == 0);
|
|
|
|
db->priorNewRowid = v;
|
|
|
|
if (rx == DBSQL_SUCCESS && res == 0) {
|
|
|
|
rc = DBSQL_FULL;
|
|
|
|
goto abort_due_to_error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
pC->recnoIsValid = 0;
|
|
|
|
pC->deferredMoveto = 0;
|
|
|
|
}
|
|
|
|
pTos++;
|
|
|
|
pTos->i = v;
|
|
|
|
pTos->flags = MEM_Int;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: PutIntKey P1 P2 *
|
|
|
|
**
|
|
|
|
** Write an entry into the table of cursor P1. A new entry is
|
|
|
|
** created if it doesn't already exist or the data for an existing
|
|
|
|
** entry is overwritten. The data is the value on the top of the
|
|
|
|
** stack. The key is the next value down on the stack. The key must
|
|
|
|
** be an integer. The stack is popped twice by this instruction.
|
|
|
|
**
|
|
|
|
** If P2==1 then the row change count is incremented. If P2==0 the
|
|
|
|
** row change count is unmodified. The rowid is stored for subsequent
|
|
|
|
** return by the dbsql_last_inserted_rowid() function if P2 is 1.
|
|
|
|
*/
|
|
|
|
/* Opcode: PutStrKey P1 * *
|
|
|
|
**
|
|
|
|
** Write an entry into the table of cursor P1. A new entry is
|
|
|
|
** created if it doesn't already exist or the data for an existing
|
|
|
|
** entry is overwritten. The data is the value on the top of the
|
|
|
|
** stack. The key is the next value down on the stack. The key must
|
|
|
|
** be a string. The stack is popped twice by this instruction.
|
|
|
|
**
|
|
|
|
** P1 may not be a pseudo-table opened using the OpenPseudo opcode.
|
|
|
|
*/
|
|
|
|
case OP_PutIntKey: /* FALLTHROUGH */
|
|
|
|
case OP_PutStrKey: {
|
|
|
|
mem_t *pNos = &pTos[-1];
|
|
|
|
int i = pOp->p1;
|
|
|
|
cursor_t *pC;
|
|
|
|
DBSQL_ASSERT(pNos >= p->aStack);
|
|
|
|
DBSQL_ASSERT(i >= 0 && i < p->nCursor);
|
|
|
|
if (((pC = &p->aCsr[i])->pCursor != 0 || pC->pseudoTable)) {
|
|
|
|
char *zKey;
|
|
|
|
int nKey, iKey;
|
|
|
|
if (pOp->opcode == OP_PutStrKey) {
|
|
|
|
__entity_as_string(pNos);
|
|
|
|
nKey = pNos->n;
|
|
|
|
zKey = pNos->z;
|
|
|
|
} else {
|
|
|
|
DBSQL_ASSERT(pNos->flags & MEM_Int);
|
|
|
|
nKey = sizeof(int);
|
|
|
|
iKey = INT_TO_KEY(pNos->i);
|
|
|
|
zKey = (char*)&iKey;
|
|
|
|
if (pOp->p2) {
|
|
|
|
db->_num_last_changes++;
|
|
|
|
db->_num_total_changes++;
|
|
|
|
db->lastRowid = pNos->i;
|
|
|
|
}
|
|
|
|
if (pC->nextRowidValid && pTos->i >= pC->nextRowid) {
|
|
|
|
pC->nextRowidValid = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (pC->pseudoTable) {
|
|
|
|
/*
|
|
|
|
* PutStrKey does not work for pseudo-tables.
|
|
|
|
* The following DBSQL_ASSERT makes sure we are not
|
|
|
|
* trying to use PutStrKey on a pseudo-table
|
|
|
|
*/
|
|
|
|
DBSQL_ASSERT(pOp->opcode == OP_PutIntKey);
|
|
|
|
__dbsql_free(NULL, pC->pData);
|
|
|
|
pC->iKey = iKey;
|
|
|
|
pC->nData = pTos->n;
|
|
|
|
if (pTos->flags & MEM_Dyn) {
|
|
|
|
pC->pData = pTos->z;
|
|
|
|
pTos->flags = MEM_Null;
|
|
|
|
} else {
|
|
|
|
if (__dbsql_calloc(NULL, 1, pC->nData,
|
|
|
|
&pC->pData) != ENOMEM) {
|
|
|
|
memcpy(pC->pData, pTos->z, pC->nData);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
pC->nullRow = 0;
|
|
|
|
} else {
|
|
|
|
rc = __sm_insert(pC->pCursor, zKey, nKey, pTos->z,
|
|
|
|
pTos->n);
|
|
|
|
}
|
|
|
|
pC->recnoIsValid = 0;
|
|
|
|
pC->deferredMoveto = 0;
|
|
|
|
}
|
|
|
|
__pop_stack(&pTos, 2);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: Delete P1 P2 *
|
|
|
|
**
|
|
|
|
** Delete the record at which the P1 cursor is currently pointing.
|
|
|
|
**
|
|
|
|
** The cursor will be left pointing at either the next or the previous
|
|
|
|
** record in the table. If it is left pointing at the next record, then
|
|
|
|
** the next Next instruction will be a no-op. Hence it is OK to delete
|
|
|
|
** a record from within an Next loop.
|
|
|
|
**
|
|
|
|
** The row change counter is incremented if P2==1 and is unmodified
|
|
|
|
** if P2==0.
|
|
|
|
**
|
|
|
|
** If P1 is a pseudo-table, then this instruction is a no-op.
|
|
|
|
*/
|
|
|
|
case OP_Delete: {
|
|
|
|
int i = pOp->p1;
|
|
|
|
cursor_t *pC;
|
|
|
|
DBSQL_ASSERT(i >= 0 && i < p->nCursor);
|
|
|
|
pC = &p->aCsr[i];
|
|
|
|
if (pC->pCursor != 0) {
|
|
|
|
__vdbe_cursor_moveto(pC);
|
|
|
|
rc = __sm_delete(pC->pCursor);
|
|
|
|
pC->nextRowidValid = 0;
|
|
|
|
}
|
|
|
|
if (pOp->p2) {
|
|
|
|
db->_num_last_changes++;
|
|
|
|
db->_num_total_changes++;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: KeyAsData P1 P2 *
|
|
|
|
**
|
|
|
|
** Turn the key-as-data mode for cursor P1 either on (if P2==1) or
|
|
|
|
** off (if P2==0). In key-as-data mode, the OP_Column opcode pulls
|
|
|
|
** data off of the key rather than the data. This is used for
|
|
|
|
** processing compound selects.
|
|
|
|
*/
|
|
|
|
case OP_KeyAsData: {
|
|
|
|
int i = pOp->p1;
|
|
|
|
DBSQL_ASSERT(i >= 0 && i < p->nCursor);
|
|
|
|
p->aCsr[i].keyAsData = pOp->p2;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: RowData P1 * *
|
|
|
|
**
|
|
|
|
** Push onto the stack the complete row data for cursor P1.
|
|
|
|
** There is no interpretation of the data. It is just copied
|
|
|
|
** onto the stack exactly as it is found in the database file.
|
|
|
|
**
|
|
|
|
** If the cursor is not pointing to a valid row, a NULL is pushed
|
|
|
|
** onto the stack.
|
|
|
|
*/
|
|
|
|
/* Opcode: RowKey P1 * *
|
|
|
|
**
|
|
|
|
** Push onto the stack the complete row key for cursor P1.
|
|
|
|
** There is no interpretation of the key. It is just copied
|
|
|
|
** onto the stack exactly as it is found in the database file.
|
|
|
|
**
|
|
|
|
** If the cursor is not pointing to a valid row, a NULL is pushed
|
|
|
|
** onto the stack.
|
|
|
|
*/
|
|
|
|
case OP_RowKey: /* FALLTHROUGH */
|
|
|
|
case OP_RowData: {
|
|
|
|
int i = pOp->p1;
|
|
|
|
cursor_t *pC;
|
|
|
|
int n;
|
|
|
|
|
|
|
|
pTos++;
|
|
|
|
DBSQL_ASSERT(i >= 0 && i < p->nCursor);
|
|
|
|
pC = &p->aCsr[i];
|
|
|
|
if (pC->nullRow) {
|
|
|
|
pTos->flags = MEM_Null;
|
|
|
|
} else if (pC->pCursor != 0) {
|
|
|
|
sm_cursor_t *pCrsr = pC->pCursor;
|
|
|
|
__vdbe_cursor_moveto(pC);
|
|
|
|
if (pC->nullRow) {
|
|
|
|
pTos->flags = MEM_Null;
|
|
|
|
break;
|
|
|
|
} else if (pC->keyAsData || pOp->opcode == OP_RowKey) {
|
|
|
|
__sm_key_size(pCrsr, &n);
|
|
|
|
} else {
|
|
|
|
__sm_data_size(pCrsr, &n);
|
|
|
|
}
|
|
|
|
pTos->n = n;
|
|
|
|
if (n <= NBFS) {
|
|
|
|
pTos->flags = MEM_Str | MEM_Short;
|
|
|
|
pTos->z = pTos->zShort;
|
|
|
|
} else {
|
|
|
|
if (__dbsql_calloc(NULL, 1, n, &pTos->z) == ENOMEM)
|
|
|
|
goto no_mem;
|
|
|
|
pTos->flags = MEM_Str | MEM_Dyn;
|
|
|
|
}
|
|
|
|
if (pC->keyAsData || pOp->opcode == OP_RowKey) {
|
|
|
|
__sm_key(pCrsr, 0, n, pTos->z);
|
|
|
|
} else {
|
|
|
|
__sm_data(pCrsr, 0, n, pTos->z);
|
|
|
|
}
|
|
|
|
} else if (pC->pseudoTable) {
|
|
|
|
pTos->n = pC->nData;
|
|
|
|
pTos->z = pC->pData;
|
|
|
|
pTos->flags = MEM_Str|MEM_Ephem;
|
|
|
|
} else {
|
|
|
|
pTos->flags = MEM_Null;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: Column P1 P2 *
|
|
|
|
**
|
|
|
|
** Interpret the data that cursor P1 points to as
|
|
|
|
** a structure built using the MakeRecord instruction.
|
|
|
|
** (See the MakeRecord opcode for additional information about
|
|
|
|
** the format of the data.)
|
|
|
|
** Push onto the stack the value of the P2-th column contained
|
|
|
|
** in the data.
|
|
|
|
**
|
|
|
|
** If the KeyAsData opcode has previously executed on this cursor,
|
|
|
|
** then the field might be extracted from the key rather than the
|
|
|
|
** data.
|
|
|
|
**
|
|
|
|
** If P1 is negative, then the record is stored on the stack rather
|
|
|
|
** than in a table. For P1==-1, the top of the stack is used.
|
|
|
|
** For P1==-2, the next on the stack is used. And so forth. The
|
|
|
|
** value pushed is always just a pointer into the record which is
|
|
|
|
** stored further down on the stack. The column value is not copied.
|
|
|
|
*/
|
|
|
|
case OP_Column: {
|
|
|
|
int amt, offset, end, payloadSize;
|
|
|
|
int i = pOp->p1;
|
|
|
|
int p2 = pOp->p2;
|
|
|
|
cursor_t *pC;
|
|
|
|
char *zRec;
|
|
|
|
sm_cursor_t *pCrsr;
|
|
|
|
int idxWidth;
|
|
|
|
unsigned char aHdr[10];
|
|
|
|
|
|
|
|
DBSQL_ASSERT(i < p->nCursor);
|
|
|
|
pTos++;
|
|
|
|
if (i < 0) {
|
|
|
|
DBSQL_ASSERT(&pTos[i] >= p->aStack);
|
|
|
|
DBSQL_ASSERT(pTos[i].flags & MEM_Str);
|
|
|
|
zRec = pTos[i].z;
|
|
|
|
payloadSize = pTos[i].n;
|
|
|
|
} else if ((pC = &p->aCsr[i])->pCursor != 0) {
|
|
|
|
__vdbe_cursor_moveto(pC);
|
|
|
|
zRec = 0;
|
|
|
|
pCrsr = pC->pCursor;
|
|
|
|
if (pC->nullRow) {
|
|
|
|
payloadSize = 0;
|
|
|
|
} else if (pC->keyAsData) {
|
|
|
|
__sm_key_size(pCrsr, &payloadSize);
|
|
|
|
} else {
|
|
|
|
__sm_data_size(pCrsr, &payloadSize);
|
|
|
|
}
|
|
|
|
} else if (pC->pseudoTable) {
|
|
|
|
payloadSize = pC->nData;
|
|
|
|
zRec = pC->pData;
|
|
|
|
DBSQL_ASSERT(payloadSize == 0 || zRec != 0);
|
|
|
|
} else {
|
|
|
|
payloadSize = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Figure out how many bytes in the column data and where the column
|
|
|
|
* data begins.
|
|
|
|
*/
|
|
|
|
if (payloadSize == 0) {
|
|
|
|
pTos->flags = MEM_Null;
|
|
|
|
break;
|
|
|
|
} else if (payloadSize < 256) {
|
|
|
|
idxWidth = 1;
|
|
|
|
} else if (payloadSize < 65536) {
|
|
|
|
idxWidth = 2;
|
|
|
|
} else {
|
|
|
|
idxWidth = 3;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Figure out where the requested column is stored and how big it is.
|
|
|
|
*/
|
|
|
|
if (payloadSize < (idxWidth * (p2 + 1))) {
|
|
|
|
rc = DBSQL_CORRUPT;
|
|
|
|
goto abort_due_to_error;
|
|
|
|
}
|
|
|
|
if (zRec) {
|
|
|
|
memcpy(aHdr, &zRec[idxWidth * p2], idxWidth * 2);
|
|
|
|
} else if (pC->keyAsData) {
|
|
|
|
__sm_key(pCrsr, idxWidth * p2, idxWidth * 2, (char*)aHdr);
|
|
|
|
} else {
|
|
|
|
__sm_data(pCrsr, idxWidth * p2, idxWidth * 2, (char*)aHdr);
|
|
|
|
}
|
|
|
|
offset = aHdr[0];
|
|
|
|
end = aHdr[idxWidth];
|
|
|
|
if (idxWidth > 1) {
|
|
|
|
offset |= aHdr[1] << 8;
|
|
|
|
end |= aHdr[idxWidth + 1] << 8;
|
|
|
|
if (idxWidth > 2) {
|
|
|
|
offset |= aHdr[2] << 16;
|
|
|
|
end |= aHdr[idxWidth + 2] << 16;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
amt = end - offset;
|
|
|
|
if (amt < 0 || offset < 0 || end > payloadSize) {
|
|
|
|
rc = DBSQL_CORRUPT;
|
|
|
|
goto abort_due_to_error;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 'amt' and 'offset' now hold the offset to the start of data and the
|
|
|
|
* amount of data. Go get the data and put it on the stack.
|
|
|
|
*/
|
|
|
|
pTos->n = amt;
|
|
|
|
if (amt == 0) {
|
|
|
|
pTos->flags = MEM_Null;
|
|
|
|
} else if (zRec) {
|
|
|
|
pTos->flags = MEM_Str | MEM_Ephem;
|
|
|
|
pTos->z = &zRec[offset];
|
|
|
|
} else {
|
|
|
|
if (amt <= NBFS) {
|
|
|
|
pTos->flags = MEM_Str | MEM_Short;
|
|
|
|
pTos->z = pTos->zShort;
|
|
|
|
} else {
|
|
|
|
if (__dbsql_calloc(NULL, 1, amt, &pTos->z) == ENOMEM)
|
|
|
|
goto no_mem;
|
|
|
|
pTos->flags = MEM_Str | MEM_Dyn;
|
|
|
|
}
|
|
|
|
if (pC->keyAsData) {
|
|
|
|
__sm_key(pCrsr, offset, amt, pTos->z);
|
|
|
|
} else {
|
|
|
|
__sm_data(pCrsr, offset, amt, pTos->z);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: Recno P1 * *
|
|
|
|
**
|
|
|
|
** Push onto the stack an integer which is the first 4 bytes of the
|
|
|
|
** the key to the current entry in a sequential scan of the database
|
|
|
|
** file P1. The sequential scan should have been started using the
|
|
|
|
** Next opcode.
|
|
|
|
*/
|
|
|
|
case OP_Recno: {
|
|
|
|
int i = pOp->p1;
|
|
|
|
cursor_t *pC;
|
|
|
|
int v;
|
|
|
|
|
|
|
|
DBSQL_ASSERT(i >= 0 && i < p->nCursor);
|
|
|
|
pC = &p->aCsr[i];
|
|
|
|
__vdbe_cursor_moveto(pC);
|
|
|
|
pTos++;
|
|
|
|
if (pC->recnoIsValid) {
|
|
|
|
v = pC->lastRecno;
|
|
|
|
} else if (pC->pseudoTable) {
|
|
|
|
v = KEY_TO_INT(pC->iKey);
|
|
|
|
} else if (pC->nullRow || pC->pCursor == 0) {
|
|
|
|
pTos->flags = MEM_Null;
|
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
DBSQL_ASSERT(pC->pCursor != 0);
|
2007-10-21 00:33:16 +00:00
|
|
|
__sm_key(pC->pCursor, 0, sizeof(int), (char*)&v);
|
2007-03-10 19:04:07 +00:00
|
|
|
v = KEY_TO_INT(v);
|
|
|
|
}
|
|
|
|
pTos->i = v;
|
|
|
|
pTos->flags = MEM_Int;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: FullKey P1 * *
|
|
|
|
**
|
|
|
|
** Extract the complete key from the record that cursor P1 is currently
|
|
|
|
** pointing to and push the key onto the stack as a string.
|
|
|
|
**
|
|
|
|
** Compare this opcode to Recno. The Recno opcode extracts the first
|
|
|
|
** 4 bytes of the key and pushes those bytes onto the stack as an
|
|
|
|
** integer. This instruction pushes the entire key as a string.
|
|
|
|
**
|
|
|
|
** This opcode may not be used on a pseudo-table.
|
|
|
|
*/
|
|
|
|
case OP_FullKey: {
|
|
|
|
int i = pOp->p1;
|
|
|
|
sm_cursor_t *pCrsr;
|
|
|
|
|
|
|
|
DBSQL_ASSERT(p->aCsr[i].keyAsData);
|
|
|
|
DBSQL_ASSERT(!p->aCsr[i].pseudoTable);
|
|
|
|
DBSQL_ASSERT(i >= 0 && i < p->nCursor);
|
|
|
|
pTos++;
|
|
|
|
if ((pCrsr = p->aCsr[i].pCursor) != 0) {
|
|
|
|
int amt;
|
|
|
|
char *z;
|
|
|
|
|
|
|
|
__vdbe_cursor_moveto(&p->aCsr[i]);
|
|
|
|
__sm_key_size(pCrsr, &amt);
|
|
|
|
if (amt <= 0) {
|
|
|
|
rc = DBSQL_CORRUPT;
|
|
|
|
goto abort_due_to_error;
|
|
|
|
}
|
|
|
|
if (amt > NBFS) {
|
|
|
|
if (__dbsql_calloc(NULL, 1, amt, &z) == ENOMEM)
|
|
|
|
goto no_mem;
|
|
|
|
pTos->flags = MEM_Str | MEM_Dyn;
|
|
|
|
} else {
|
|
|
|
z = pTos->zShort;
|
|
|
|
pTos->flags = MEM_Str | MEM_Short;
|
|
|
|
}
|
|
|
|
__sm_key(pCrsr, 0, amt, z);
|
|
|
|
pTos->z = z;
|
|
|
|
pTos->n = amt;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: NullRow P1 * *
|
|
|
|
**
|
|
|
|
** Move the cursor P1 to a null row. Any OP_Column operations
|
|
|
|
** that occur while the cursor is on the null row will always push
|
|
|
|
** a NULL onto the stack.
|
|
|
|
*/
|
|
|
|
case OP_NullRow: {
|
|
|
|
int i = pOp->p1;
|
|
|
|
|
|
|
|
DBSQL_ASSERT(i >= 0 && i < p->nCursor);
|
|
|
|
p->aCsr[i].nullRow = 1;
|
|
|
|
p->aCsr[i].recnoIsValid = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: Last P1 P2 *
|
|
|
|
**
|
|
|
|
** The next use of the Recno or Column or Next instruction for P1
|
|
|
|
** will refer to the last entry in the database table or index.
|
|
|
|
** If the table or index is empty and P2>0, then jump immediately to P2.
|
|
|
|
** If P2 is 0 or if the table or index is not empty, fall through
|
|
|
|
** to the following instruction.
|
|
|
|
*/
|
|
|
|
case OP_Last: {
|
|
|
|
int i = pOp->p1;
|
|
|
|
cursor_t *pC;
|
|
|
|
sm_cursor_t *pCrsr;
|
|
|
|
|
|
|
|
DBSQL_ASSERT(i >= 0 && i < p->nCursor);
|
|
|
|
pC = &p->aCsr[i];
|
|
|
|
if ((pCrsr = pC->pCursor) != 0) {
|
|
|
|
int res;
|
|
|
|
rc = __sm_last(pCrsr, &res);
|
|
|
|
pC->nullRow = res;
|
|
|
|
pC->deferredMoveto = 0;
|
|
|
|
if (res && pOp->p2 > 0) {
|
|
|
|
pc = pOp->p2 - 1;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
pC->nullRow = 0;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: Rewind P1 P2 *
|
|
|
|
**
|
|
|
|
** The next use of the Recno or Column or Next instruction for P1
|
|
|
|
** will refer to the first entry in the database table or index.
|
|
|
|
** If the table or index is empty and P2>0, then jump immediately to P2.
|
|
|
|
** If P2 is 0 or if the table or index is not empty, fall through
|
|
|
|
** to the following instruction.
|
|
|
|
*/
|
|
|
|
case OP_Rewind: {
|
|
|
|
int i = pOp->p1;
|
|
|
|
cursor_t *pC;
|
|
|
|
sm_cursor_t *pCrsr;
|
|
|
|
|
|
|
|
DBSQL_ASSERT(i >= 0 && i < p->nCursor);
|
|
|
|
pC = &p->aCsr[i];
|
|
|
|
if ((pCrsr = pC->pCursor) != 0) {
|
|
|
|
int res;
|
|
|
|
rc = __sm_first(pCrsr, &res);
|
|
|
|
pC->atFirst = (res == 0);
|
|
|
|
pC->nullRow = res;
|
|
|
|
pC->deferredMoveto = 0;
|
|
|
|
if (res && pOp->p2 > 0) {
|
|
|
|
pc = pOp->p2 - 1;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
pC->nullRow = 0;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: Next P1 P2 *
|
|
|
|
**
|
|
|
|
** Advance cursor P1 so that it points to the next key/data pair in its
|
|
|
|
** table or index. If there are no more key/value pairs then fall through
|
|
|
|
** to the following instruction. But if the cursor advance was successful,
|
|
|
|
** jump immediately to P2.
|
|
|
|
**
|
|
|
|
** See also: Prev
|
|
|
|
*/
|
|
|
|
/* Opcode: Prev P1 P2 *
|
|
|
|
**
|
|
|
|
** Back up cursor P1 so that it points to the previous key/data pair in its
|
|
|
|
** table or index. If there is no previous key/value pairs then fall through
|
|
|
|
** to the following instruction. But if the cursor backup was successful,
|
|
|
|
** jump immediately to P2.
|
|
|
|
*/
|
|
|
|
case OP_Prev: /* FALLTHROUGH */
|
|
|
|
case OP_Next: {
|
|
|
|
cursor_t *pC;
|
|
|
|
sm_cursor_t *pCrsr;
|
|
|
|
|
|
|
|
CHECK_FOR_INTERRUPT;
|
|
|
|
DBSQL_ASSERT(pOp->p1 >= 0 && pOp->p1 < p->nCursor);
|
|
|
|
pC = &p->aCsr[pOp->p1];
|
|
|
|
if ((pCrsr = pC->pCursor) != 0) {
|
|
|
|
int res;
|
|
|
|
if (pC->nullRow) {
|
|
|
|
res = 1;
|
|
|
|
} else {
|
|
|
|
DBSQL_ASSERT(pC->deferredMoveto == 0);
|
|
|
|
rc = pOp->opcode==OP_Next ? __sm_next(pCrsr, &res) :
|
|
|
|
__sm_prev(pCrsr, &res);
|
|
|
|
pC->nullRow = res;
|
|
|
|
}
|
|
|
|
if (res == 0) {
|
|
|
|
pc = pOp->p2 - 1;
|
|
|
|
#ifdef CONFIG_TEST
|
|
|
|
dbsql_search_count++;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
pC->nullRow = 1;
|
|
|
|
}
|
|
|
|
pC->recnoIsValid = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: IdxPut P1 P2 P3
|
|
|
|
**
|
|
|
|
** The top of the stack holds a SQL index key made using the
|
|
|
|
** MakeIdxKey instruction. This opcode writes that key into the
|
|
|
|
** index P1. Data for the entry is nil.
|
|
|
|
**
|
|
|
|
** If P2==1, then the key must be unique. If the key is not unique,
|
|
|
|
** the program aborts with a DBSQL_CONSTRAINT error and the database
|
|
|
|
** is rolled back. If P3 is not null, then it becomes part of the
|
|
|
|
** error message returned with the DBSQL_CONSTRAINT.
|
|
|
|
*/
|
|
|
|
case OP_IdxPut: {
|
|
|
|
int i = pOp->p1;
|
|
|
|
sm_cursor_t *pCrsr;
|
|
|
|
DBSQL_ASSERT(pTos >= p->aStack);
|
|
|
|
DBSQL_ASSERT(i >= 0 && i < p->nCursor);
|
|
|
|
DBSQL_ASSERT(pTos->flags & MEM_Str);
|
|
|
|
if ((pCrsr = p->aCsr[i].pCursor) != 0) {
|
|
|
|
int nKey = pTos->n;
|
|
|
|
const char *zKey = pTos->z;
|
|
|
|
if (pOp->p2) {
|
|
|
|
int res, n;
|
|
|
|
DBSQL_ASSERT(nKey >= 4);
|
|
|
|
rc = __sm_moveto(pCrsr, zKey, (nKey - 4), &res);
|
|
|
|
if (rc != DBSQL_SUCCESS)
|
|
|
|
goto abort_due_to_error;
|
|
|
|
while(res != 0) {
|
|
|
|
int c;
|
|
|
|
__sm_key_size(pCrsr, &n);
|
|
|
|
if (n == nKey &&
|
|
|
|
__sm_key_compare(pCrsr, zKey, (nKey - 4),
|
|
|
|
4, &c) == DBSQL_SUCCESS &&
|
|
|
|
c == 0) {
|
|
|
|
rc = DBSQL_CONSTRAINT;
|
|
|
|
if (pOp->p3 && pOp->p3[0]) {
|
|
|
|
__str_append(&p->zErrMsg,
|
|
|
|
pOp->p3, (char*)0);
|
|
|
|
}
|
|
|
|
goto abort_due_to_error;
|
|
|
|
}
|
|
|
|
if (res < 0) {
|
|
|
|
__sm_next(pCrsr, &res);
|
|
|
|
res = +1;
|
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
rc = __sm_insert(pCrsr, zKey, nKey, "", 0);
|
|
|
|
DBSQL_ASSERT(p->aCsr[i].deferredMoveto == 0);
|
|
|
|
}
|
|
|
|
__entity_release_mem(pTos);
|
|
|
|
pTos--;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: IdxDelete P1 * *
|
|
|
|
**
|
|
|
|
** The top of the stack is an index key built using the MakeIdxKey opcode.
|
|
|
|
** This opcode removes that entry from the index.
|
|
|
|
*/
|
|
|
|
case OP_IdxDelete: {
|
|
|
|
int i = pOp->p1;
|
|
|
|
sm_cursor_t *pCrsr;
|
|
|
|
DBSQL_ASSERT(pTos >= p->aStack);
|
|
|
|
DBSQL_ASSERT(pTos->flags & MEM_Str);
|
|
|
|
DBSQL_ASSERT(i >= 0 && i < p->nCursor);
|
|
|
|
if ((pCrsr = p->aCsr[i].pCursor) != 0) {
|
|
|
|
int rx, res;
|
|
|
|
rx = __sm_moveto(pCrsr, pTos->z, pTos->n, &res);
|
|
|
|
if (rx == DBSQL_SUCCESS && res == 0) {
|
|
|
|
rc = __sm_delete(pCrsr);
|
|
|
|
}
|
|
|
|
DBSQL_ASSERT(p->aCsr[i].deferredMoveto == 0);
|
|
|
|
}
|
|
|
|
__entity_release_mem(pTos);
|
|
|
|
pTos--;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: IdxRecno P1 * *
|
|
|
|
**
|
|
|
|
** Push onto the stack an integer which is the last 4 bytes of the
|
|
|
|
** the key to the current entry in index P1. These 4 bytes should
|
|
|
|
** be the record number of the table entry to which this index entry
|
|
|
|
** points.
|
|
|
|
**
|
|
|
|
** See also: Recno, MakeIdxKey.
|
|
|
|
*/
|
|
|
|
case OP_IdxRecno: {
|
|
|
|
int i = pOp->p1;
|
|
|
|
sm_cursor_t *pCrsr;
|
|
|
|
|
|
|
|
DBSQL_ASSERT(i >= 0 && i < p->nCursor);
|
|
|
|
pTos++;
|
|
|
|
if ((pCrsr = p->aCsr[i].pCursor) != 0) {
|
|
|
|
int v;
|
|
|
|
int sz;
|
|
|
|
DBSQL_ASSERT(p->aCsr[i].deferredMoveto == 0);
|
|
|
|
__sm_key_size(pCrsr, &sz);
|
|
|
|
if (sz < sizeof(u_int32_t)) {
|
|
|
|
pTos->flags = MEM_Null;
|
|
|
|
} else {
|
|
|
|
__sm_key(pCrsr, sz - sizeof(u_int32_t),
|
|
|
|
sizeof(u_int32_t), (char*)&v);
|
|
|
|
v = KEY_TO_INT(v);
|
|
|
|
pTos->i = v;
|
|
|
|
pTos->flags = MEM_Int;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
pTos->flags = MEM_Null;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: IdxGT P1 P2 *
|
|
|
|
**
|
|
|
|
** Compare the top of the stack against the key on the index entry that
|
|
|
|
** cursor P1 is currently pointing to. Ignore the last 4 bytes of the
|
|
|
|
** index entry. If the index entry is greater than the top of the stack
|
|
|
|
** then jump to P2. Otherwise fall through to the next instruction.
|
|
|
|
** In either case, the stack is popped once.
|
|
|
|
*/
|
|
|
|
/* Opcode: IdxGE P1 P2 *
|
|
|
|
**
|
|
|
|
** Compare the top of the stack against the key on the index entry that
|
|
|
|
** cursor P1 is currently pointing to. Ignore the last 4 bytes of the
|
|
|
|
** index entry. If the index entry is greater than or equal to
|
|
|
|
** the top of the stack
|
|
|
|
** then jump to P2. Otherwise fall through to the next instruction.
|
|
|
|
** In either case, the stack is popped once.
|
|
|
|
*/
|
|
|
|
/* Opcode: IdxLT P1 P2 *
|
|
|
|
**
|
|
|
|
** Compare the top of the stack against the key on the index entry that
|
|
|
|
** cursor P1 is currently pointing to. Ignore the last 4 bytes of the
|
|
|
|
** index entry. If the index entry is less than the top of the stack
|
|
|
|
** then jump to P2. Otherwise fall through to the next instruction.
|
|
|
|
** In either case, the stack is popped once.
|
|
|
|
*/
|
|
|
|
case OP_IdxLT: /* FALLTHROUGH */
|
|
|
|
case OP_IdxGT: /* FALLTHROUGH */
|
|
|
|
case OP_IdxGE: {
|
|
|
|
int i= pOp->p1;
|
|
|
|
sm_cursor_t *pCrsr;
|
|
|
|
|
|
|
|
DBSQL_ASSERT(i >= 0 && i < p->nCursor);
|
|
|
|
DBSQL_ASSERT(pTos >= p->aStack);
|
|
|
|
if ((pCrsr = p->aCsr[i].pCursor) != 0) {
|
|
|
|
int res, rc;
|
|
|
|
|
|
|
|
__entity_as_string(pTos);
|
|
|
|
DBSQL_ASSERT(p->aCsr[i].deferredMoveto == 0);
|
|
|
|
rc = __sm_key_compare(pCrsr, pTos->z, pTos->n, 4, &res);
|
|
|
|
if (rc != DBSQL_SUCCESS) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (pOp->opcode == OP_IdxLT) {
|
|
|
|
res = -res;
|
|
|
|
} else if (pOp->opcode == OP_IdxGE) {
|
|
|
|
res++;
|
|
|
|
}
|
|
|
|
if (res > 0) {
|
|
|
|
pc = pOp->p2 - 1 ;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
__entity_release_mem(pTos);
|
|
|
|
pTos--;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: IdxIsNull P1 P2 *
|
|
|
|
**
|
|
|
|
** The top of the stack contains an index entry such as might be generated
|
|
|
|
** by the MakeIdxKey opcode. This routine looks at the first P1 fields of
|
|
|
|
** that key. If any of the first P1 fields are NULL, then a jump is made
|
|
|
|
** to address P2. Otherwise we fall straight through.
|
|
|
|
**
|
|
|
|
** The index entry is always popped from the stack.
|
|
|
|
*/
|
|
|
|
case OP_IdxIsNull: {
|
|
|
|
int i = pOp->p1;
|
|
|
|
int k, n;
|
|
|
|
const char *z;
|
|
|
|
|
|
|
|
DBSQL_ASSERT(pTos >= p->aStack);
|
|
|
|
DBSQL_ASSERT(pTos->flags & MEM_Str);
|
|
|
|
z = pTos->z;
|
|
|
|
n = pTos->n;
|
|
|
|
for (k = 0; k < n && i > 0; i--) {
|
|
|
|
if (z[k] == 'a') {
|
|
|
|
pc = pOp->p2-1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
while(k < n && z[k]) {
|
|
|
|
k++;
|
|
|
|
}
|
|
|
|
k++;
|
|
|
|
}
|
|
|
|
__entity_release_mem(pTos);
|
|
|
|
pTos--;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: Destroy P1 P2 *
|
|
|
|
**
|
|
|
|
** Delete an entire database table or index whose root page in the database
|
|
|
|
** file is given by P1.
|
|
|
|
**
|
|
|
|
** The table being destroyed is in the main database file if P2==0. If
|
|
|
|
** P2==1 then the table to be clear is in the auxiliary database file
|
|
|
|
** that is used to store tables create using CREATE TEMPORARY TABLE.
|
|
|
|
**
|
|
|
|
** See also: Clear
|
|
|
|
*/
|
|
|
|
case OP_Destroy: {
|
|
|
|
rc = __sm_drop_table(db->aDb[pOp->p2].pBt, pOp->p1);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: Clear P1 P2 *
|
|
|
|
**
|
|
|
|
** Delete all contents of the database table or index whose root page
|
|
|
|
** in the database file is given by P1. But, unlike Destroy, do not
|
|
|
|
** remove the table or index from the database file.
|
|
|
|
**
|
|
|
|
** The table being clear is in the main database file if P2==0. If
|
|
|
|
** P2==1 then the table to be clear is in the auxiliary database file
|
|
|
|
** that is used to store tables create using CREATE TEMPORARY TABLE.
|
|
|
|
**
|
|
|
|
** See also: Destroy
|
|
|
|
*/
|
|
|
|
case OP_Clear: {
|
|
|
|
rc = __sm_clear_table(db->aDb[pOp->p2].pBt, pOp->p1);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: CreateTable * P2 P3
|
|
|
|
**
|
|
|
|
** Allocate a new table in the main database file if P2==0 or in the
|
|
|
|
** auxiliary database file if P2==1. Push the page number
|
|
|
|
** for the root page of the new table onto the stack.
|
|
|
|
**
|
|
|
|
** The root page number is also written to a memory location that P3
|
|
|
|
** points to. This is the mechanism is used to write the root page
|
|
|
|
** number into the parser's internal data structures that describe the
|
|
|
|
** new table.
|
|
|
|
**
|
|
|
|
** The difference between a table and an index is this: A table must
|
|
|
|
** have a 4-byte integer key and can have arbitrary data. An index
|
|
|
|
** has an arbitrary key but no data.
|
|
|
|
**
|
|
|
|
** See also: CreateIndex
|
|
|
|
*/
|
|
|
|
/* Opcode: CreateIndex * P2 P3
|
|
|
|
**
|
|
|
|
** Allocate a new index in the main database file if P2==0 or in the
|
|
|
|
** auxiliary database file if P2==1. Push the page number of the
|
|
|
|
** root page of the new index onto the stack.
|
|
|
|
**
|
|
|
|
** See documentation on OP_CreateTable for additional information.
|
|
|
|
*/
|
|
|
|
case OP_CreateIndex: /* FALLTHROUGH */
|
|
|
|
case OP_CreateTable: {
|
|
|
|
int pgno;
|
|
|
|
DBSQL_ASSERT(pOp->p3 != 0 && pOp->p3type == P3_POINTER);
|
|
|
|
DBSQL_ASSERT(pOp->p2 >= 0 && pOp->p2 < db->nDb);
|
|
|
|
DBSQL_ASSERT(db->aDb[pOp->p2].pBt != 0);
|
|
|
|
if (pOp->opcode == OP_CreateTable) {
|
|
|
|
rc = __sm_create_table(db->aDb[pOp->p2].pBt, &pgno);
|
|
|
|
} else {
|
|
|
|
rc = __sm_create_index(db->aDb[pOp->p2].pBt, &pgno);
|
|
|
|
}
|
|
|
|
pTos++;
|
|
|
|
if (rc == DBSQL_SUCCESS) {
|
|
|
|
pTos->i = pgno;
|
|
|
|
pTos->flags = MEM_Int;
|
|
|
|
*(u_int32_t*)pOp->p3 = pgno;
|
|
|
|
pOp->p3 = 0;
|
|
|
|
} else {
|
|
|
|
pTos->flags = MEM_Null;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: ListWrite * * *
|
|
|
|
**
|
|
|
|
** Write the integer on the top of the stack
|
|
|
|
** into the temporary storage list.
|
|
|
|
*/
|
|
|
|
case OP_ListWrite: {
|
|
|
|
keylist_t *pKeylist;
|
|
|
|
DBSQL_ASSERT(pTos >= p->aStack);
|
|
|
|
pKeylist = p->pList;
|
|
|
|
if (pKeylist == 0 || pKeylist->nUsed >= pKeylist->nKey) {
|
|
|
|
if (__dbsql_calloc(NULL, 1, sizeof(keylist_t) +
|
|
|
|
(999 * sizeof(pKeylist->aKey[0])),
|
|
|
|
&pKeylist) == ENOMEM)
|
|
|
|
goto no_mem;
|
|
|
|
pKeylist->nKey = 1000;
|
|
|
|
pKeylist->nRead = 0;
|
|
|
|
pKeylist->nUsed = 0;
|
|
|
|
pKeylist->pNext = p->pList;
|
|
|
|
p->pList = pKeylist;
|
|
|
|
}
|
|
|
|
__entity_to_int(pTos);
|
|
|
|
pKeylist->aKey[pKeylist->nUsed++] = pTos->i;
|
|
|
|
DBSQL_ASSERT(pTos->flags==MEM_Int);
|
|
|
|
pTos--;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: ListRewind * * *
|
|
|
|
**
|
|
|
|
** Rewind the temporary buffer back to the beginning. This is
|
|
|
|
** now a no-op.
|
|
|
|
*/
|
|
|
|
case OP_ListRewind: {
|
|
|
|
/* This is now a no-op */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: ListRead * P2 *
|
|
|
|
**
|
|
|
|
** Attempt to read an integer from the temporary storage buffer
|
|
|
|
** and push it onto the stack. If the storage buffer is empty,
|
|
|
|
** push nothing but instead jump to P2.
|
|
|
|
*/
|
|
|
|
case OP_ListRead: {
|
|
|
|
keylist_t *pKeylist;
|
|
|
|
CHECK_FOR_INTERRUPT;
|
|
|
|
pKeylist = p->pList;
|
|
|
|
if (pKeylist != 0) {
|
|
|
|
DBSQL_ASSERT(pKeylist->nRead >= 0);
|
|
|
|
DBSQL_ASSERT(pKeylist->nRead < pKeylist->nUsed);
|
|
|
|
DBSQL_ASSERT(pKeylist->nRead < pKeylist->nKey);
|
|
|
|
pTos++;
|
|
|
|
pTos->i = pKeylist->aKey[pKeylist->nRead++];
|
|
|
|
pTos->flags = MEM_Int;
|
|
|
|
if (pKeylist->nRead >= pKeylist->nUsed) {
|
|
|
|
p->pList = pKeylist->pNext;
|
|
|
|
__dbsql_free(NULL, pKeylist);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
pc = pOp->p2 - 1;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: ListReset * * *
|
|
|
|
**
|
|
|
|
** Reset the temporary storage buffer so that it holds nothing.
|
|
|
|
*/
|
|
|
|
case OP_ListReset: {
|
|
|
|
if (p->pList) {
|
|
|
|
__vdbe_keylist_free(p->pList);
|
|
|
|
p->pList = 0;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: ListPush * * *
|
|
|
|
**
|
|
|
|
** Save the current vdbe_t list such that it can be restored by a ListPop
|
|
|
|
** opcode. The list is empty after this is executed.
|
|
|
|
*/
|
|
|
|
case OP_ListPush: {
|
|
|
|
p->keylistStackDepth++;
|
|
|
|
DBSQL_ASSERT(p->keylistStackDepth > 0);
|
|
|
|
if (__dbsql_realloc(NULL, sizeof(keylist_t *) * p->keylistStackDepth,
|
|
|
|
&p->keylistStack) == ENOMEM)
|
|
|
|
goto no_mem;
|
|
|
|
p->keylistStack[p->keylistStackDepth - 1] = p->pList;
|
|
|
|
p->pList = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: ListPop * * *
|
|
|
|
**
|
|
|
|
** Restore the vdbe_t list to the state it was in when ListPush was last
|
|
|
|
** executed.
|
|
|
|
*/
|
|
|
|
case OP_ListPop: {
|
|
|
|
DBSQL_ASSERT(p->keylistStackDepth > 0);
|
|
|
|
p->keylistStackDepth--;
|
|
|
|
__vdbe_keylist_free(p->pList);
|
|
|
|
p->pList = p->keylistStack[p->keylistStackDepth];
|
|
|
|
p->keylistStack[p->keylistStackDepth] = 0;
|
|
|
|
if (p->keylistStackDepth == 0) {
|
|
|
|
__dbsql_free(NULL, p->keylistStack);
|
|
|
|
p->keylistStack = 0;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: SortPut * * *
|
|
|
|
**
|
|
|
|
** The TOS is the key and the NOS is the data. Pop both from the stack
|
|
|
|
** and put them on the sorter. The key and data should have been
|
|
|
|
** made using SortMakeKey and SortMakeRec, respectively.
|
|
|
|
*/
|
|
|
|
case OP_SortPut: {
|
|
|
|
mem_t *pNos = &pTos[-1];
|
|
|
|
sorter_t *pSorter;
|
|
|
|
DBSQL_ASSERT(pNos >= p->aStack);
|
|
|
|
if (__entity_to_string(pTos) || __entity_to_string(pNos))
|
|
|
|
goto no_mem;
|
|
|
|
if (__dbsql_calloc(NULL, 1, sizeof(sorter_t), &pSorter) == ENOMEM)
|
|
|
|
goto no_mem;
|
|
|
|
pSorter->pNext = p->pSort;
|
|
|
|
p->pSort = pSorter;
|
|
|
|
DBSQL_ASSERT(pTos->flags & MEM_Dyn);
|
|
|
|
pSorter->nKey = pTos->n;
|
|
|
|
pSorter->zKey = pTos->z;
|
|
|
|
DBSQL_ASSERT(pNos->flags & MEM_Dyn);
|
|
|
|
pSorter->nData = pNos->n;
|
|
|
|
pSorter->pData = pNos->z;
|
|
|
|
pTos -= 2;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: SortMakeRec P1 * *
|
|
|
|
**
|
|
|
|
** The top P1 elements are the arguments to a callback. Form these
|
|
|
|
** elements into a single data entry that can be stored on a sorter
|
|
|
|
** using SortPut and later fed to a callback using SortCallback.
|
|
|
|
*/
|
|
|
|
case OP_SortMakeRec: {
|
|
|
|
char *z;
|
|
|
|
char **azArg;
|
|
|
|
int nByte;
|
|
|
|
int nField;
|
|
|
|
int i;
|
|
|
|
mem_t *pRec;
|
|
|
|
|
|
|
|
nField = pOp->p1;
|
|
|
|
pRec = &pTos[1 - nField];
|
|
|
|
DBSQL_ASSERT( pRec>=p->aStack );
|
|
|
|
nByte = 0;
|
|
|
|
for(i = 0; i < nField; i++, pRec++) {
|
|
|
|
if ((pRec->flags & MEM_Null) == 0) {
|
|
|
|
__entity_as_string(pRec);
|
|
|
|
nByte += pRec->n;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
nByte += sizeof(char*) * (nField + 1);
|
|
|
|
if (__dbsql_calloc(NULL, 1, nByte, &azArg) == ENOMEM)
|
|
|
|
goto no_mem;
|
|
|
|
z = (char*)&azArg[nField + 1];
|
|
|
|
for (pRec = &pTos[1 - nField], i = 0; i < nField; i++, pRec++) {
|
|
|
|
if (pRec->flags & MEM_Null) {
|
|
|
|
azArg[i] = 0;
|
|
|
|
} else {
|
|
|
|
azArg[i] = z;
|
|
|
|
memcpy(z, pRec->z, pRec->n);
|
|
|
|
z += pRec->n;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
__pop_stack(&pTos, nField);
|
|
|
|
pTos++;
|
|
|
|
pTos->n = nByte;
|
|
|
|
pTos->z = (char*)azArg;
|
|
|
|
pTos->flags = MEM_Str | MEM_Dyn;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: SortMakeKey * * P3
|
|
|
|
**
|
|
|
|
** Convert the top few entries of the stack into a sort key. The
|
|
|
|
** number of stack entries consumed is the number of characters in
|
|
|
|
** the string P3. One character from P3 is prepended to each entry.
|
|
|
|
** The first character of P3 is prepended to the element lowest in
|
|
|
|
** the stack and the last character of P3 is prepended to the top of
|
|
|
|
** the stack. All stack entries are separated by a \000 character
|
|
|
|
** in the result. The whole key is terminated by two \000 characters
|
|
|
|
** in a row.
|
|
|
|
**
|
|
|
|
** "N" is substituted in place of the P3 character for NULL values.
|
|
|
|
**
|
|
|
|
** See also the MakeKey and MakeIdxKey opcodes.
|
|
|
|
*/
|
|
|
|
case OP_SortMakeKey: {
|
|
|
|
char *zNewKey;
|
|
|
|
int nByte;
|
|
|
|
int nField;
|
|
|
|
int i, j, k;
|
|
|
|
mem_t *pRec;
|
|
|
|
|
|
|
|
nField = strlen(pOp->p3);
|
|
|
|
pRec = &pTos[1 - nField];
|
|
|
|
nByte = 1;
|
|
|
|
for (i = 0; i < nField; i++, pRec++) {
|
|
|
|
if (pRec->flags & MEM_Null) {
|
|
|
|
nByte += 2;
|
|
|
|
} else {
|
|
|
|
__entity_as_string(pRec);
|
|
|
|
nByte += pRec->n+2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (__dbsql_calloc(NULL, 1, nByte, &zNewKey) == ENOMEM)
|
|
|
|
goto no_mem;
|
|
|
|
j = 0;
|
|
|
|
k = 0;
|
|
|
|
for (pRec = &pTos[1 - nField], i = 0; i < nField; i++, pRec++) {
|
|
|
|
if (pRec->flags & MEM_Null) {
|
|
|
|
zNewKey[j++] = 'N';
|
|
|
|
zNewKey[j++] = 0;
|
|
|
|
k++;
|
|
|
|
} else {
|
|
|
|
zNewKey[j++] = pOp->p3[k++];
|
|
|
|
memcpy(&zNewKey[j], pRec->z, pRec->n - 1);
|
|
|
|
j += pRec->n - 1;
|
|
|
|
zNewKey[j++] = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
zNewKey[j] = 0;
|
|
|
|
DBSQL_ASSERT(j < nByte);
|
|
|
|
__pop_stack(&pTos, nField);
|
|
|
|
pTos++;
|
|
|
|
pTos->n = nByte;
|
|
|
|
pTos->flags = MEM_Str | MEM_Dyn;
|
|
|
|
pTos->z = zNewKey;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: Sort * * *
|
|
|
|
**
|
|
|
|
** Sort all elements on the sorter. The algorithm is a
|
|
|
|
** mergesort.
|
|
|
|
*/
|
|
|
|
case OP_Sort: {
|
|
|
|
int i;
|
|
|
|
sorter_t *pElem;
|
|
|
|
sorter_t *apSorter[NSORT];
|
|
|
|
for(i = 0; i < NSORT; i++) {
|
|
|
|
apSorter[i] = 0;
|
|
|
|
}
|
|
|
|
while(p->pSort) {
|
|
|
|
pElem = p->pSort;
|
|
|
|
p->pSort = pElem->pNext;
|
|
|
|
pElem->pNext = 0;
|
|
|
|
for (i = 0; i < NSORT-1; i++) {
|
|
|
|
if (apSorter[i] == 0) {
|
|
|
|
apSorter[i] = pElem;
|
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
pElem = __sorted_merge(apSorter[i], pElem);
|
|
|
|
apSorter[i] = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (i >= NSORT - 1) {
|
|
|
|
apSorter[NSORT - 1] =
|
|
|
|
__sorted_merge(apSorter[NSORT - 1],pElem);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
pElem = 0;
|
|
|
|
for(i=0; i < NSORT; i++) {
|
|
|
|
pElem = __sorted_merge(apSorter[i], pElem);
|
|
|
|
}
|
|
|
|
p->pSort = pElem;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: SortNext * P2 *
|
|
|
|
**
|
|
|
|
** Push the data for the topmost element in the sorter onto the
|
|
|
|
** stack, then remove the element from the sorter. If the sorter
|
|
|
|
** is empty, push nothing on the stack and instead jump immediately
|
|
|
|
** to instruction P2.
|
|
|
|
*/
|
|
|
|
case OP_SortNext: {
|
|
|
|
sorter_t *pSorter = p->pSort;
|
|
|
|
CHECK_FOR_INTERRUPT;
|
|
|
|
if (pSorter != 0) {
|
|
|
|
p->pSort = pSorter->pNext;
|
|
|
|
pTos++;
|
|
|
|
pTos->z = pSorter->pData;
|
|
|
|
pTos->n = pSorter->nData;
|
|
|
|
pTos->flags = MEM_Str | MEM_Dyn;
|
|
|
|
__dbsql_free(NULL, pSorter->zKey);
|
|
|
|
__dbsql_free(NULL, pSorter);
|
|
|
|
} else {
|
|
|
|
pc = pOp->p2 - 1;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: SortCallback P1 * *
|
|
|
|
**
|
|
|
|
** The top of the stack contains a callback record built using
|
|
|
|
** the SortMakeRec operation with the same P1 value as this
|
|
|
|
** instruction. Pop this record from the stack and invoke the
|
|
|
|
** callback on it.
|
|
|
|
*/
|
|
|
|
case OP_SortCallback: {
|
|
|
|
DBSQL_ASSERT(pTos >= p->aStack);
|
|
|
|
DBSQL_ASSERT(pTos->flags & MEM_Str);
|
|
|
|
if (p->xCallback == 0) {
|
|
|
|
p->pc = pc + 1;
|
|
|
|
p->azResColumn = (char**)pTos->z;
|
|
|
|
p->nResColumn = pOp->p1;
|
|
|
|
p->popStack = 1;
|
|
|
|
p->pTos = pTos;
|
|
|
|
return DBSQL_ROW;
|
|
|
|
} else {
|
|
|
|
if (__safety_off(db))
|
|
|
|
goto abort_due_to_misuse;
|
|
|
|
if (p->xCallback(p->pCbArg, pOp->p1, (char**)pTos->z,
|
|
|
|
p->azColName) != 0) {
|
|
|
|
rc = DBSQL_ABORT;
|
|
|
|
}
|
|
|
|
if (__safety_on(db))
|
|
|
|
goto abort_due_to_misuse;
|
|
|
|
p->nCallback++;
|
|
|
|
}
|
|
|
|
__entity_release_mem(pTos);
|
|
|
|
pTos--;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: SortReset * * *
|
|
|
|
**
|
|
|
|
** Remove any elements that remain on the sorter.
|
|
|
|
*/
|
|
|
|
case OP_SortReset: {
|
|
|
|
__vdbe_sorter_reset(p);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: FileOpen * * P3
|
|
|
|
**
|
|
|
|
** Open the file named by P3 for reading using the FileRead opcode.
|
|
|
|
** If P3 is "stdin" then open standard input for reading.
|
|
|
|
*/
|
|
|
|
case OP_FileOpen: {
|
|
|
|
DBSQL_ASSERT(pOp->p3 != 0);
|
|
|
|
if (p->pFile) {
|
|
|
|
if (p->pFile != stdin)
|
|
|
|
fclose(p->pFile);
|
|
|
|
p->pFile = 0;
|
|
|
|
}
|
|
|
|
if (strcasecmp(pOp->p3, "stdin") == 0) {
|
|
|
|
p->pFile = stdin;
|
|
|
|
} else {
|
|
|
|
p->pFile = fopen(pOp->p3, "r");
|
|
|
|
}
|
|
|
|
if (p->pFile == 0) {
|
|
|
|
__str_append(&p->zErrMsg, "unable to open file: ",
|
|
|
|
pOp->p3, (char*)0);
|
|
|
|
rc = DBSQL_ERROR;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: FileRead P1 P2 P3
|
|
|
|
**
|
|
|
|
** Read a single line of input from the open file (the file opened using
|
|
|
|
** FileOpen). If we reach end-of-file, jump immediately to P2. If
|
|
|
|
** we are able to get another line, split the line apart using P3 as
|
|
|
|
** a delimiter. There should be P1 fields. If the input line contains
|
|
|
|
** more than P1 fields, ignore the excess. If the input line contains
|
|
|
|
** fewer than P1 fields, assume the remaining fields contain NULLs.
|
|
|
|
**
|
|
|
|
** Input ends if a line consists of just "\.". A field containing only
|
|
|
|
** "\N" is a null field. The backslash \ character can be used be used
|
|
|
|
** to escape newlines or the delimiter.
|
|
|
|
*/
|
|
|
|
case OP_FileRead: {
|
|
|
|
int n, eol, nField, i, c, nDelim;
|
|
|
|
char *zDelim, *z;
|
|
|
|
CHECK_FOR_INTERRUPT;
|
|
|
|
if (p->pFile == 0)
|
|
|
|
goto fileread_jump;
|
|
|
|
nField = pOp->p1;
|
|
|
|
if (nField <= 0)
|
|
|
|
goto fileread_jump;
|
|
|
|
if (nField != p->nField || p->azField == 0) {
|
|
|
|
if (__dbsql_realloc(NULL, (sizeof(char*) * nField) + 1,
|
|
|
|
&p->azField) == ENOMEM)
|
|
|
|
goto no_mem;
|
|
|
|
p->nField = nField;
|
|
|
|
}
|
|
|
|
n = 0;
|
|
|
|
eol = 0;
|
|
|
|
while(eol == 0) {
|
|
|
|
if (p->zLine == 0 || n + 200 > p->nLineAlloc) {
|
|
|
|
p->nLineAlloc = (p->nLineAlloc * 2) + 300;
|
|
|
|
if (__dbsql_realloc(NULL, p->nLineAlloc,
|
|
|
|
&p->zLine) == ENOMEM) {
|
|
|
|
p->nLineAlloc = 0;
|
|
|
|
__dbsql_free(NULL, p->zLine);
|
|
|
|
p->zLine = 0;
|
|
|
|
goto no_mem;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (__fgets(&p->zLine[n], p->nLineAlloc-n, p->pFile) == 0) {
|
|
|
|
eol = 1;
|
|
|
|
p->zLine[n] = 0;
|
|
|
|
} else {
|
|
|
|
int c;
|
|
|
|
while((c = p->zLine[n]) != 0) {
|
|
|
|
if (c == '\\') {
|
|
|
|
if (p->zLine[n + 1] == 0)
|
|
|
|
break;
|
|
|
|
n += 2;
|
|
|
|
} else if (c == '\n') {
|
|
|
|
p->zLine[n] = 0;
|
|
|
|
eol = 1;
|
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
n++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (n == 0)
|
|
|
|
goto fileread_jump;
|
|
|
|
z = p->zLine;
|
|
|
|
if (z[0] == '\\' && z[1] == '.' && z[2] == 0) {
|
|
|
|
goto fileread_jump;
|
|
|
|
}
|
|
|
|
zDelim = pOp->p3;
|
|
|
|
if (zDelim == 0)
|
|
|
|
zDelim = "\t";
|
|
|
|
c = zDelim[0];
|
|
|
|
nDelim = strlen(zDelim);
|
|
|
|
p->azField[0] = z;
|
|
|
|
for(i = 1; *z != 0 && i <= nField; i++) {
|
|
|
|
int from, to;
|
|
|
|
from = to = 0;
|
|
|
|
if (z[0] == '\\' && z[1] == 'N' &&
|
|
|
|
(z[2] == 0 || strncmp(&z[2], zDelim, nDelim) == 0)) {
|
|
|
|
if (i <= nField)
|
|
|
|
p->azField[i - 1] = 0;
|
|
|
|
z += 2 + nDelim;
|
|
|
|
if (i < nField)
|
|
|
|
p->azField[i] = z;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
while(z[from]) {
|
|
|
|
if(z[from] == '\\' && z[from + 1] != 0) {
|
|
|
|
int tx = z[from + 1];
|
|
|
|
switch(tx) {
|
|
|
|
case 'b': tx = '\b'; break;
|
|
|
|
case 'f': tx = '\f'; break;
|
|
|
|
case 'n': tx = '\n'; break;
|
|
|
|
case 'r': tx = '\r'; break;
|
|
|
|
case 't': tx = '\t'; break;
|
|
|
|
case 'v': tx = '\v'; break;
|
|
|
|
default: break;
|
|
|
|
}
|
|
|
|
z[to++] = tx;
|
|
|
|
from += 2;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (z[from] == c &&
|
|
|
|
strncmp(&z[from], zDelim, nDelim) == 0)
|
|
|
|
break;
|
|
|
|
z[to++] = z[from++];
|
|
|
|
}
|
|
|
|
if (z[from]) {
|
|
|
|
z[to] = 0;
|
|
|
|
z += from + nDelim;
|
|
|
|
if (i < nField)
|
|
|
|
p->azField[i] = z;
|
|
|
|
} else {
|
|
|
|
z[to] = 0;
|
|
|
|
z = "";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
while(i < nField) {
|
|
|
|
p->azField[i++] = 0;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If we reach end-of-file, or if anything goes wrong, jump here.
|
|
|
|
* This code will cause a jump to P2.
|
|
|
|
*/
|
|
|
|
fileread_jump:
|
|
|
|
pc = pOp->p2 - 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: FileColumn P1 * *
|
|
|
|
**
|
|
|
|
** Push onto the stack the P1-th column of the most recently read line
|
|
|
|
** from the input file.
|
|
|
|
*/
|
|
|
|
case OP_FileColumn: {
|
|
|
|
int i = pOp->p1;
|
|
|
|
char *z;
|
|
|
|
DBSQL_ASSERT(i >= 0 && i < p->nField);
|
|
|
|
if (p->azField) {
|
|
|
|
z = p->azField[i];
|
|
|
|
} else {
|
|
|
|
z = 0;
|
|
|
|
}
|
|
|
|
pTos++;
|
|
|
|
if (z) {
|
|
|
|
pTos->n = strlen(z) + 1;
|
|
|
|
pTos->z = z;
|
|
|
|
pTos->flags = MEM_Str | MEM_Ephem;
|
|
|
|
} else {
|
|
|
|
pTos->flags = MEM_Null;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: MemStore P1 P2 *
|
|
|
|
**
|
|
|
|
** Write the top of the stack into memory location P1.
|
|
|
|
** P1 should be a small integer since space is allocated
|
|
|
|
** for all memory locations between 0 and P1 inclusive.
|
|
|
|
**
|
|
|
|
** After the data is stored in the memory location, the
|
|
|
|
** stack is popped once if P2 is 1. If P2 is zero, then
|
|
|
|
** the original data remains on the stack.
|
|
|
|
*/
|
|
|
|
case OP_MemStore: {
|
|
|
|
int i = pOp->p1;
|
|
|
|
mem_t *pMem;
|
|
|
|
DBSQL_ASSERT(pTos >= p->aStack);
|
|
|
|
if (i >= p->nMem) {
|
|
|
|
int nOld = p->nMem;
|
|
|
|
mem_t *oldMem;
|
|
|
|
p->nMem = i + 5;
|
|
|
|
oldMem = p->aMem;
|
|
|
|
if (__dbsql_realloc(NULL, p->nMem * sizeof(p->aMem[0]),
|
|
|
|
&p->aMem) == ENOMEM)
|
|
|
|
goto no_mem;
|
|
|
|
if (oldMem != p->aMem) { /* realloc moved the memory */
|
|
|
|
int j;
|
|
|
|
for(j = 0; j < nOld; j++) {
|
|
|
|
if (p->aMem[j].flags & MEM_Short) {
|
|
|
|
p->aMem[j].z = p->aMem[j].zShort;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (nOld < p->nMem) {
|
|
|
|
memset(&p->aMem[nOld], 0,
|
|
|
|
sizeof(p->aMem[0]) * (p->nMem - nOld));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (__entity_ephem_to_dyn(pTos) == 1)
|
|
|
|
goto no_mem;
|
|
|
|
pMem = &p->aMem[i];
|
|
|
|
__entity_release_mem(pMem);
|
|
|
|
*pMem = *pTos;
|
|
|
|
if (pMem->flags & MEM_Dyn) {
|
|
|
|
if (pOp->p2) {
|
|
|
|
pTos->flags = MEM_Null;
|
|
|
|
} else {
|
|
|
|
if (__dbsql_malloc(NULL, pMem->n, &pMem->z) == ENOMEM)
|
|
|
|
goto no_mem;
|
|
|
|
memcpy(pMem->z, pTos->z, pMem->n);
|
|
|
|
}
|
|
|
|
} else if (pMem->flags & MEM_Short) {
|
|
|
|
pMem->z = pMem->zShort;
|
|
|
|
}
|
|
|
|
if (pOp->p2) {
|
|
|
|
__entity_release_mem(pTos);
|
|
|
|
pTos--;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: MemLoad P1 * *
|
|
|
|
**
|
|
|
|
** Push a copy of the value in memory location P1 onto the stack.
|
|
|
|
**
|
|
|
|
** If the value is a string, then the value pushed is a pointer to
|
|
|
|
** the string that is stored in the memory location. If the memory
|
|
|
|
** location is subsequently changed (using OP_MemStore) then the
|
|
|
|
** value pushed onto the stack will change too.
|
|
|
|
*/
|
|
|
|
case OP_MemLoad: {
|
|
|
|
int i = pOp->p1;
|
|
|
|
DBSQL_ASSERT(i >= 0 && i < p->nMem);
|
|
|
|
pTos++;
|
|
|
|
memcpy(pTos, &p->aMem[i], sizeof(pTos[0]) - NBFS);;
|
|
|
|
if (pTos->flags & MEM_Str) {
|
|
|
|
pTos->flags |= MEM_Ephem;
|
|
|
|
pTos->flags &= ~(MEM_Dyn | MEM_Static | MEM_Short);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: MemIncr P1 P2 *
|
|
|
|
**
|
|
|
|
** Increment the integer valued memory cell P1 by 1. If P2 is not zero
|
|
|
|
** and the result after the increment is greater than zero, then jump
|
|
|
|
** to P2.
|
|
|
|
**
|
|
|
|
** This instruction throws an error if the memory cell is not initially
|
|
|
|
** an integer.
|
|
|
|
*/
|
|
|
|
case OP_MemIncr: {
|
|
|
|
int i = pOp->p1;
|
|
|
|
mem_t *pMem;
|
|
|
|
DBSQL_ASSERT(i >= 0 && i < p->nMem);
|
|
|
|
pMem = &p->aMem[i];
|
|
|
|
DBSQL_ASSERT(pMem->flags == MEM_Int);
|
|
|
|
pMem->i++;
|
|
|
|
if (pOp->p2 > 0 && pMem->i > 0) {
|
|
|
|
pc = pOp->p2 - 1;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: AggReset * P2 *
|
|
|
|
**
|
|
|
|
** Reset the aggregator so that it no longer contains any data.
|
|
|
|
** Future aggregator elements will contain P2 values each.
|
|
|
|
*/
|
|
|
|
case OP_AggReset: {
|
|
|
|
__vdbe_agg_reset(&p->agg);
|
|
|
|
p->agg.nMem = pOp->p2;
|
|
|
|
if (__dbsql_calloc(NULL, p->agg.nMem, sizeof(p->agg.apFunc[0]),
|
|
|
|
&p->agg.apFunc) == ENOMEM)
|
|
|
|
goto no_mem;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: AggInit * P2 P3
|
|
|
|
**
|
|
|
|
** Initialize the function parameters for an aggregate function.
|
|
|
|
** The aggregate will operate out of aggregate column P2.
|
|
|
|
** P3 is a pointer to the func_def_t structure for the function.
|
|
|
|
*/
|
|
|
|
case OP_AggInit: {
|
|
|
|
int i = pOp->p2;
|
|
|
|
DBSQL_ASSERT(i >= 0 && i < p->agg.nMem);
|
|
|
|
p->agg.apFunc[i] = (func_def_t*)pOp->p3;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: AggFunc * P2 P3
|
|
|
|
**
|
|
|
|
** Execute the step function for an aggregate. The
|
|
|
|
** function has P2 arguments. P3 is a pointer to the func_def_t
|
|
|
|
** structure that specifies the function.
|
|
|
|
**
|
|
|
|
** The top of the stack must be an integer which is the index of
|
|
|
|
** the aggregate column that corresponds to this aggregate function.
|
|
|
|
** Ideally, this index would be another parameter, but there are
|
|
|
|
** no free parameters left. The integer is popped from the stack.
|
|
|
|
*/
|
|
|
|
case OP_AggFunc: {
|
|
|
|
int n = pOp->p2;
|
|
|
|
int i;
|
|
|
|
mem_t *pMem, *pRec;
|
|
|
|
char **azArgv = p->zArgv;
|
|
|
|
dbsql_func_t ctx;
|
|
|
|
|
|
|
|
DBSQL_ASSERT(n >= 0);
|
|
|
|
DBSQL_ASSERT(pTos->flags == MEM_Int);
|
|
|
|
pRec = &pTos[-n];
|
|
|
|
DBSQL_ASSERT(pRec >= p->aStack);
|
|
|
|
for(i = 0; i < n; i++, pRec++) {
|
|
|
|
if (pRec->flags & MEM_Null) {
|
|
|
|
azArgv[i] = 0;
|
|
|
|
} else {
|
|
|
|
__entity_as_string(pRec);
|
|
|
|
azArgv[i] = pRec->z;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
i = pTos->i;
|
|
|
|
DBSQL_ASSERT(i >= 0 && i < p->agg.nMem);
|
|
|
|
ctx.pFunc = (func_def_t*)pOp->p3;
|
|
|
|
pMem = &p->agg.pCurrent->aMem[i];
|
|
|
|
ctx.s.z = pMem->zShort; /* Space used for small aggregate contexts */
|
|
|
|
ctx.pAgg = pMem->z;
|
|
|
|
ctx.cnt = ++pMem->i;
|
|
|
|
ctx.isError = 0;
|
|
|
|
ctx.isStep = 1;
|
|
|
|
(ctx.pFunc->xStep)(&ctx, n, (const char**)azArgv);
|
|
|
|
pMem->z = ctx.pAgg;
|
|
|
|
pMem->flags = MEM_AggCtx;
|
|
|
|
__pop_stack(&pTos, n + 1);
|
|
|
|
if (ctx.isError) {
|
|
|
|
rc = DBSQL_ERROR;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: AggFocus * P2 *
|
|
|
|
**
|
|
|
|
** Pop the top of the stack and use that as an aggregator key. If
|
|
|
|
** an aggregator with that same key already exists, then make the
|
|
|
|
** aggregator the current aggregator and jump to P2. If no aggregator
|
|
|
|
** with the given key exists, create one and make it current but
|
|
|
|
** do not jump.
|
|
|
|
**
|
|
|
|
** The order of aggregator opcodes is important. The order is:
|
|
|
|
** AggReset AggFocus AggNext. In other words, you must execute
|
|
|
|
** AggReset first, then zero or more AggFocus operations, then
|
|
|
|
** zero or more AggNext operations. You must not execute an AggFocus
|
|
|
|
** in between an AggNext and an AggReset.
|
|
|
|
*/
|
|
|
|
case OP_AggFocus: {
|
|
|
|
agg_elem_t *pElem;
|
|
|
|
char *zKey;
|
|
|
|
int nKey;
|
|
|
|
|
|
|
|
DBSQL_ASSERT(pTos >= p->aStack);
|
|
|
|
__entity_as_string(pTos);
|
|
|
|
zKey = pTos->z;
|
|
|
|
nKey = pTos->n;
|
|
|
|
pElem = __hash_find(&p->agg.hash, zKey, nKey);
|
|
|
|
if (pElem) {
|
|
|
|
p->agg.pCurrent = pElem;
|
|
|
|
pc = pOp->p2 - 1;
|
|
|
|
} else {
|
|
|
|
__agg_insert(&p->agg, zKey, nKey);
|
|
|
|
}
|
|
|
|
__entity_release_mem(pTos);
|
|
|
|
pTos--;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: AggSet * P2 *
|
|
|
|
**
|
|
|
|
** Move the top of the stack into the P2-th field of the current
|
|
|
|
** aggregate. String values are duplicated into new memory.
|
|
|
|
*/
|
|
|
|
case OP_AggSet: {
|
|
|
|
agg_elem_t *pFocus = __agg_in_focus(&p->agg);
|
|
|
|
mem_t *pMem;
|
|
|
|
int i = pOp->p2;
|
|
|
|
DBSQL_ASSERT(pTos>=p->aStack);
|
|
|
|
if (pFocus == 0)
|
|
|
|
goto no_mem;
|
|
|
|
DBSQL_ASSERT(i >= 0 && i < p->agg.nMem);
|
|
|
|
if (__entity_ephem_to_dyn(pTos) == 1)
|
|
|
|
goto no_mem;
|
|
|
|
pMem = &pFocus->aMem[i];
|
|
|
|
__entity_release_mem(pMem);
|
|
|
|
*pMem = *pTos;
|
|
|
|
if (pMem->flags & MEM_Dyn) {
|
|
|
|
pTos->flags = MEM_Null;
|
|
|
|
} else if (pMem->flags & MEM_Short) {
|
|
|
|
pMem->z = pMem->zShort;
|
|
|
|
}
|
|
|
|
__entity_release_mem(pTos);
|
|
|
|
pTos--;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: AggGet * P2 *
|
|
|
|
**
|
|
|
|
** Push a new entry onto the stack which is a copy of the P2-th field
|
|
|
|
** of the current aggregate. Strings are not duplicated so
|
|
|
|
** string values will be ephemeral.
|
|
|
|
*/
|
|
|
|
case OP_AggGet: {
|
|
|
|
agg_elem_t *pFocus = __agg_in_focus(&p->agg);
|
|
|
|
mem_t *pMem;
|
|
|
|
int i = pOp->p2;
|
|
|
|
if (pFocus == 0)
|
|
|
|
goto no_mem;
|
|
|
|
DBSQL_ASSERT(i >= 0 && i < p->agg.nMem);
|
|
|
|
pTos++;
|
|
|
|
pMem = &pFocus->aMem[i];
|
|
|
|
*pTos = *pMem;
|
|
|
|
if (pTos->flags & MEM_Str) {
|
|
|
|
pTos->flags &= ~(MEM_Dyn|MEM_Static|MEM_Short);
|
|
|
|
pTos->flags |= MEM_Ephem;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: AggNext * P2 *
|
|
|
|
**
|
|
|
|
** Make the next aggregate value the current aggregate. The prior
|
|
|
|
** aggregate is deleted. If all aggregate values have been consumed,
|
|
|
|
** jump to P2.
|
|
|
|
**
|
|
|
|
** The order of aggregator opcodes is important. The order is:
|
|
|
|
** AggReset AggFocus AggNext. In other words, you must execute
|
|
|
|
** AggReset first, then zero or more AggFocus operations, then
|
|
|
|
** zero or more AggNext operations. You must not execute an AggFocus
|
|
|
|
** in between an AggNext and an AggReset.
|
|
|
|
*/
|
|
|
|
case OP_AggNext: {
|
|
|
|
CHECK_FOR_INTERRUPT;
|
|
|
|
if (p->agg.pSearch == 0) {
|
|
|
|
p->agg.pSearch = __hash_first(&p->agg.hash);
|
|
|
|
} else {
|
|
|
|
p->agg.pSearch = __hash_next(p->agg.pSearch);
|
|
|
|
}
|
|
|
|
if (p->agg.pSearch == 0) {
|
|
|
|
pc = pOp->p2 - 1;
|
|
|
|
} else {
|
|
|
|
int i;
|
|
|
|
dbsql_func_t ctx;
|
|
|
|
mem_t *aMem;
|
|
|
|
p->agg.pCurrent = __hash_data(p->agg.pSearch);
|
|
|
|
aMem = p->agg.pCurrent->aMem;
|
|
|
|
for(i = 0; i < p->agg.nMem; i++){
|
|
|
|
int freeCtx;
|
|
|
|
if (p->agg.apFunc[i] == 0)
|
|
|
|
continue;
|
|
|
|
if (p->agg.apFunc[i]->xFinalize == 0)
|
|
|
|
continue;
|
|
|
|
ctx.s.flags = MEM_Null;
|
|
|
|
ctx.s.z = aMem[i].zShort;
|
|
|
|
ctx.pAgg = (void*)aMem[i].z;
|
|
|
|
freeCtx = aMem[i].z && aMem[i].z!=aMem[i].zShort;
|
|
|
|
ctx.cnt = aMem[i].i;
|
|
|
|
ctx.isStep = 0;
|
|
|
|
ctx.pFunc = p->agg.apFunc[i];
|
|
|
|
(*p->agg.apFunc[i]->xFinalize)(&ctx);
|
|
|
|
if (freeCtx) {
|
|
|
|
__dbsql_free(NULL, aMem[i].z);
|
|
|
|
}
|
|
|
|
aMem[i] = ctx.s;
|
|
|
|
if (aMem[i].flags & MEM_Short) {
|
|
|
|
aMem[i].z = aMem[i].zShort;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: SetInsert P1 * P3
|
|
|
|
**
|
|
|
|
** If Set P1 does not exist then create it. Then insert value
|
|
|
|
** P3 into that set. If P3 is NULL, then insert the top of the
|
|
|
|
** stack into the set.
|
|
|
|
*/
|
|
|
|
case OP_SetInsert: {
|
|
|
|
int i = pOp->p1;
|
|
|
|
if (p->nSet <= i) {
|
|
|
|
int k;
|
|
|
|
if (__dbsql_realloc(NULL, (i+1) * sizeof(p->aSet[0]),
|
|
|
|
&p->aSet) == ENOMEM)
|
|
|
|
goto no_mem;
|
|
|
|
for(k = p->nSet; k <= i; k++) {
|
|
|
|
__hash_init(&p->aSet[k].hash, DBSQL_HASH_BINARY, 1);
|
|
|
|
}
|
|
|
|
p->nSet = i + 1;
|
|
|
|
}
|
|
|
|
if (pOp->p3) {
|
|
|
|
__hash_insert(&p->aSet[i].hash, pOp->p3, strlen(pOp->p3)+1, p);
|
|
|
|
} else {
|
|
|
|
DBSQL_ASSERT(pTos >= p->aStack);
|
|
|
|
__entity_as_string(pTos);
|
|
|
|
__hash_insert(&p->aSet[i].hash, pTos->z, pTos->n, p);
|
|
|
|
__entity_release_mem(pTos);
|
|
|
|
pTos--;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: SetFound P1 P2 *
|
|
|
|
**
|
|
|
|
** Pop the stack once and compare the value popped off with the
|
|
|
|
** contents of set P1. If the element popped exists in set P1,
|
|
|
|
** then jump to P2. Otherwise fall through.
|
|
|
|
*/
|
|
|
|
case OP_SetFound: {
|
|
|
|
int i = pOp->p1;
|
|
|
|
DBSQL_ASSERT(pTos >= p->aStack);
|
|
|
|
__entity_as_string(pTos);
|
|
|
|
if (i >= 0 && i < p->nSet &&
|
|
|
|
__hash_find(&p->aSet[i].hash, pTos->z, pTos->n)) {
|
|
|
|
pc = pOp->p2 - 1;
|
|
|
|
}
|
|
|
|
__entity_release_mem(pTos);
|
|
|
|
pTos--;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: SetNotFound P1 P2 *
|
|
|
|
**
|
|
|
|
** Pop the stack once and compare the value popped off with the
|
|
|
|
** contents of set P1. If the element popped does not exists in
|
|
|
|
** set P1, then jump to P2. Otherwise fall through.
|
|
|
|
*/
|
|
|
|
case OP_SetNotFound: {
|
|
|
|
int i = pOp->p1;
|
|
|
|
DBSQL_ASSERT(pTos>=p->aStack);
|
|
|
|
__entity_as_string(pTos);
|
|
|
|
if (i < 0 || i >= p->nSet ||
|
|
|
|
__hash_find(&p->aSet[i].hash, pTos->z, pTos->n) == 0) {
|
|
|
|
pc = pOp->p2 - 1;
|
|
|
|
}
|
|
|
|
__entity_release_mem(pTos);
|
|
|
|
pTos--;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: SetFirst P1 P2 *
|
|
|
|
**
|
|
|
|
** Read the first element from set P1 and push it onto the stack. If the
|
|
|
|
** set is empty, push nothing and jump immediately to P2. This opcode is
|
|
|
|
** used in combination with OP_SetNext to loop over all elements of a set.
|
|
|
|
*/
|
|
|
|
/* Opcode: SetNext P1 P2 *
|
|
|
|
**
|
|
|
|
** Read the next element from set P1 and push it onto the stack. If there
|
|
|
|
** are no more elements in the set, do not do the push and fall through.
|
|
|
|
** Otherwise, jump to P2 after pushing the next set element.
|
|
|
|
*/
|
|
|
|
case OP_SetFirst: /* FALLTHROUGH */
|
|
|
|
case OP_SetNext: {
|
|
|
|
set_t *pSet;
|
|
|
|
CHECK_FOR_INTERRUPT;
|
|
|
|
if (pOp->p1 < 0 || pOp->p1 >= p->nSet) {
|
|
|
|
if (pOp->opcode == OP_SetFirst)
|
|
|
|
pc = pOp->p2 - 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
pSet = &p->aSet[pOp->p1];
|
|
|
|
if (pOp->opcode == OP_SetFirst) {
|
|
|
|
pSet->prev = __hash_first(&pSet->hash);
|
|
|
|
if (pSet->prev == 0) {
|
|
|
|
pc = pOp->p2 - 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
DBSQL_ASSERT(pSet->prev);
|
|
|
|
pSet->prev = __hash_next(pSet->prev);
|
|
|
|
if (pSet->prev == 0) {
|
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
pc = pOp->p2 - 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
pTos++;
|
|
|
|
pTos->z = __hash_key(pSet->prev);
|
|
|
|
pTos->n = __hash_keysize(pSet->prev);
|
|
|
|
pTos->flags = MEM_Str | MEM_Ephem;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Opcode: Vacuum * * *
|
|
|
|
**
|
|
|
|
** Vacuum the entire database. This opcode will cause other virtual
|
|
|
|
** machines to be created and run. It may not be called from within
|
|
|
|
** a transaction.
|
|
|
|
*/
|
|
|
|
case OP_Vacuum: {
|
|
|
|
if (__safety_off(db))
|
|
|
|
goto abort_due_to_misuse;
|
|
|
|
rc = __execute_vacuum(&p->zErrMsg, db);
|
|
|
|
if (__safety_on(db))
|
|
|
|
goto abort_due_to_misuse;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Any other opcode is illegal...
|
|
|
|
*/
|
|
|
|
default: {
|
|
|
|
snprintf(zBuf, sizeof(zBuf), "%d", pOp->opcode);
|
|
|
|
__str_append(&p->zErrMsg, "unknown opcode ", zBuf, (char*)0);
|
|
|
|
rc = DBSQL_INTERNAL;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************************
|
|
|
|
** The cases of the switch statement above this line should all be indented
|
|
|
|
** by 6 spaces. But the left-most 6 spaces have been removed to improve the
|
|
|
|
** readability. From this point on down, the normal indentation rules are
|
|
|
|
** restored.
|
|
|
|
*****************************************************************************/
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef VDBE_PROFILE
|
|
|
|
{
|
|
|
|
long long elapse = __os_hwtime() - start;
|
|
|
|
pOp->cycles += elapse;
|
|
|
|
pOp->cnt++;
|
|
|
|
#if 0
|
|
|
|
fprintf(stdout, "%10lld ", elapse);
|
|
|
|
__vdbe_print_op(stdout, origPc, &p->aOp[origPc]);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The following code adds nothing to the actual functionality
|
|
|
|
* of the program. It is only here for testing and debugging.
|
|
|
|
* On the other hand, it does burn CPU cycles every time through
|
|
|
|
* the evaluator loop. So we can leave it out when NDEBUG is defined.
|
|
|
|
*/
|
|
|
|
#ifndef NDEBUG
|
|
|
|
/* Sanity checking on the top element of the stack. */
|
|
|
|
if (pTos >= p->aStack) {
|
|
|
|
DBSQL_ASSERT(pTos->flags != 0); /* Must define some type */
|
|
|
|
if (pTos->flags & MEM_Str) {
|
|
|
|
int x = pTos->flags &
|
|
|
|
(MEM_Static | MEM_Dyn | MEM_Ephem | MEM_Short);
|
|
|
|
DBSQL_ASSERT(x != 0); /* Strings must define
|
|
|
|
a string subtype. */
|
|
|
|
DBSQL_ASSERT((x & (x-1)) == 0); /* Only one string subtype
|
|
|
|
can be defined. */
|
|
|
|
DBSQL_ASSERT(pTos->z != 0); /* Strings must have a
|
|
|
|
value. */
|
|
|
|
/*
|
|
|
|
* mem_t.z points to mem_t.zShort iff the subtype is
|
|
|
|
* MEM_Short
|
|
|
|
*/
|
|
|
|
DBSQL_ASSERT((pTos->flags & MEM_Short) == 0 ||
|
|
|
|
pTos->z==pTos->zShort);
|
|
|
|
DBSQL_ASSERT((pTos->flags & MEM_Short) != 0 ||
|
|
|
|
pTos->z!=pTos->zShort);
|
|
|
|
} else {
|
|
|
|
/*
|
|
|
|
* Cannot define a string subtype for non-string objects.
|
|
|
|
*/
|
|
|
|
DBSQL_ASSERT((pTos->flags &
|
|
|
|
(MEM_Static | MEM_Dyn | MEM_Ephem | MEM_Short)) == 0);
|
|
|
|
}
|
|
|
|
/* MEM_Null excludes all other types. */
|
|
|
|
DBSQL_ASSERT(pTos->flags == MEM_Null || (pTos->flags&MEM_Null) == 0);
|
|
|
|
}
|
|
|
|
if (pc < -1 || pc >= p->nOp) {
|
|
|
|
__str_append(&p->zErrMsg, "jump destination out of range",
|
|
|
|
(char*)0);
|
|
|
|
rc = DBSQL_INTERNAL;
|
|
|
|
}
|
|
|
|
if (p->trace && pTos >= p->aStack) {
|
|
|
|
int i;
|
|
|
|
fprintf(p->trace, "Stack:");
|
|
|
|
for(i = 0; i > -5 && &pTos[i] >= p->aStack; i--) {
|
|
|
|
if (pTos[i].flags & MEM_Null) {
|
|
|
|
fprintf(p->trace, " NULL");
|
|
|
|
} else if ((pTos[i].flags &
|
|
|
|
(MEM_Int | MEM_Str)) == (MEM_Int | MEM_Str)) {
|
|
|
|
fprintf(p->trace, " si:%d", pTos[i].i);
|
|
|
|
} else if (pTos[i].flags & MEM_Int) {
|
|
|
|
fprintf(p->trace, " i:%d", pTos[i].i);
|
|
|
|
} else if (pTos[i].flags & MEM_Real) {
|
|
|
|
fprintf(p->trace, " r:%g", pTos[i].r);
|
|
|
|
} else if (pTos[i].flags & MEM_Str) {
|
|
|
|
int j, k;
|
|
|
|
char zBuf[100];
|
|
|
|
zBuf[0] = ' ';
|
|
|
|
if (pTos[i].flags & MEM_Dyn) {
|
|
|
|
zBuf[1] = 'z';
|
|
|
|
DBSQL_ASSERT((pTos[i].flags &
|
|
|
|
(MEM_Static | MEM_Ephem)) == 0);
|
|
|
|
} else if (pTos[i].flags & MEM_Static) {
|
|
|
|
zBuf[1] = 't';
|
|
|
|
DBSQL_ASSERT( (pTos[i].flags &
|
|
|
|
(MEM_Dyn | MEM_Ephem)) == 0);
|
|
|
|
} else if (pTos[i].flags & MEM_Ephem) {
|
|
|
|
zBuf[1] = 'e';
|
|
|
|
DBSQL_ASSERT((pTos[i].flags &
|
|
|
|
(MEM_Static | MEM_Dyn)) == 0);
|
|
|
|
} else {
|
|
|
|
zBuf[1] = 's';
|
|
|
|
}
|
|
|
|
zBuf[2] = '[';
|
|
|
|
k = 3;
|
|
|
|
for(j = 0; j < 20 && j < pTos[i].n; j++) {
|
|
|
|
int c = pTos[i].z[j];
|
|
|
|
if (c == 0 && j == pTos[i].n - 1)
|
|
|
|
break;
|
|
|
|
if (isprint(c) && !isspace(c)) {
|
|
|
|
zBuf[k++] = c;
|
|
|
|
} else {
|
|
|
|
zBuf[k++] = '.';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
zBuf[k++] = ']';
|
|
|
|
zBuf[k++] = 0;
|
|
|
|
fprintf(p->trace, "%s", zBuf);
|
|
|
|
} else {
|
|
|
|
fprintf(p->trace, " ???");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (rc != 0)
|
|
|
|
fprintf(p->trace," rc=%d",rc);
|
|
|
|
fprintf(p->trace, "\n");
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
} /* The end of the for(;;) loop the loops through opcodes */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If we reach this point, it means that execution is finished.
|
|
|
|
*/
|
|
|
|
vdbe_halt:
|
|
|
|
if (rc) {
|
|
|
|
p->rc = rc;
|
|
|
|
rc = DBSQL_ERROR;
|
|
|
|
} else {
|
|
|
|
rc = DBSQL_DONE;
|
|
|
|
}
|
|
|
|
p->magic = VDBE_MAGIC_HALT;
|
|
|
|
p->pTos = pTos;
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Jump to here if a __dbsql_malloc() fails. It's hard to get a
|
|
|
|
* __dbsql_malloc() to fail on a modern VM computer, so this code
|
|
|
|
* is untested.
|
|
|
|
*/
|
|
|
|
no_mem:
|
|
|
|
__str_append(&p->zErrMsg, "out of memory", (char*)0); /* TODO rm */
|
|
|
|
rc = DBSQL_NOMEM;
|
|
|
|
goto vdbe_halt;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Jump to here for an DBSQL_MISUSE error.
|
|
|
|
*/
|
|
|
|
abort_due_to_misuse:
|
|
|
|
rc = DBSQL_MISUSE;
|
|
|
|
/* Fall thru into abort_due_to_error. */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Jump to here for any other kind of fatal error. The "rc" variable
|
|
|
|
* should hold the error number.
|
|
|
|
*/
|
|
|
|
abort_due_to_error:
|
|
|
|
if (p->zErrMsg == 0) {
|
|
|
|
__str_append(&p->zErrMsg, dbsql_strerror(rc), (char*)0);
|
|
|
|
}
|
|
|
|
goto vdbe_halt;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Jump to here if the dbsql_interrupt() API sets the interrupt
|
|
|
|
* flag.
|
|
|
|
*/
|
|
|
|
abort_due_to_interrupt:
|
|
|
|
DBSQL_ASSERT(db->flags & DBSQL_Interrupt);
|
|
|
|
db->flags &= ~DBSQL_Interrupt;
|
|
|
|
if (db->magic != DBSQL_STATUS_BUSY) {
|
|
|
|
rc = DBSQL_MISUSE;
|
|
|
|
} else {
|
|
|
|
rc = DBSQL_INTERRUPTED;
|
|
|
|
}
|
|
|
|
__str_append(&p->zErrMsg, dbsql_strerror(rc), (char*)0);
|
|
|
|
goto vdbe_halt;
|
|
|
|
}
|