dbsql/src/vdbe_method.c
2009-08-31 20:28:38 -04:00

1395 lines
32 KiB
C

/*-
* DBSQL - A SQL database engine.
*
* Copyright (C) 2007-2008 The DBSQL Group, Inc. - All rights reserved.
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* There are special exceptions to the terms and conditions of the GPL as it
* is applied to this software. View the full text of the exception in file
* LICENSE_EXCEPTIONS in the directory of this software distribution.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*/
/*
* This file contains code used for creating, destroying, and populating
* a VDBE (also known as "dbsql_stmt_t").
*/
#include "dbsql_config.h"
#ifndef NO_SYSTEM_INCLUDES
#include <ctype.h>
#endif
#include "dbsql_int.h"
#include "inc/vdbe_int.h"
/*
* When debugging the code generator in a symbolic debugger, one can
* set the dbsql_vdbe_add_op_trace to 1 and all opcodes will be printed
* as they are added to the instruction stream.
*/
#ifndef NDEBUG /* TODO: if DEBUG and DIAGNOSTIC (?) */
int dbsql_vdbe_add_op_trace = 0;
#endif
/*
* __vdbe_create --
* Create a new virtual database engine.
*
* PUBLIC: vdbe_t *__vdbe_create __P((DBSQL *));
*/
vdbe_t *
__vdbe_create(dbp)
DBSQL *dbp;
{
vdbe_t *vm;
if (__dbsql_calloc(dbp, 1, sizeof(vdbe_t), &vm) == ENOMEM)
return 0;
vm->db = dbp;
if (dbp->pVdbe) {
dbp->pVdbe->pPrev = vm;
}
vm->pNext = dbp->pVdbe;
vm->pPrev = 0;
dbp->pVdbe = vm;
vm->magic = VDBE_MAGIC_INIT;
return vm;
}
/*
* __vdbe_trace --
* Turn tracing on or off.
*
* PUBLIC: void __vdbe_trace __P((vdbe_t *, FILE *));
*/
void
__vdbe_trace(vm, trace)
vdbe_t *vm;
FILE *trace;
{
vm->trace = trace;
}
/*
* __vdbe_add_op --
* Add a new instruction to the list of instructions current in the
* VDBE. Return the address of the new instruction.
*
* Parameters:
*
* p Pointer to the VDBE
*
* op The opcode for this instruction
*
* p1, p2 First two of the three possible operands.
*
* Use the __vdbe_resolve_label() function to fix an address and
* the __vdbe_change_p3() function to change the value of the P3
* operand.
*
* PUBLIC: int __vdbe_add_op __P((vdbe_t *, int, int, int));
*/
int
__vdbe_add_op(vm, op, p1, p2)
vdbe_t *vm;
int op;
int p1;
int p2;
{
int i;
i = vm->nOp;
vm->nOp++;
DBSQL_ASSERT(vm->magic == VDBE_MAGIC_INIT);
if (i >= vm->nOpAlloc) {
int old_size = vm->nOpAlloc;
vdbe_op_t *new;
vm->nOpAlloc = (vm->nOpAlloc * 2) + 100;
if (__dbsql_realloc(NULL, (vm->nOpAlloc * sizeof(vdbe_op_t)),
&vm->aOp) == ENOMEM) {
vm->nOpAlloc = old_size;
return 0;
}
memset(&vm->aOp[old_size], 0,
(vm->nOpAlloc - old_size) * sizeof(vdbe_op_t));
}
vm->aOp[i].opcode = op;
vm->aOp[i].p1 = p1;
if (p2 < 0 && (-1 - p2) < vm->nLabel && vm->aLabel[-1 -p2] >= 0) {
p2 = vm->aLabel[-1 - p2];
}
vm->aOp[i].p2 = p2;
vm->aOp[i].p3 = 0;
vm->aOp[i].p3type = P3_NOTUSED;
#ifndef NDEBUG
if (dbsql_vdbe_add_op_trace)
__vdbe_print_op(0, i, &vm->aOp[i]);
#endif
return i;
}
/*
* __vdbe_make_label --
* Create a new symbolic label for an instruction that has yet to be
* coded. The symbolic label is really just a negative number. The
* label can be used as the P2 value of an operation. Later, when
* the label is resolved to a specific address, the VDBE will scan
* through its operation list and change all values of P2 which match
* the label into the resolved address.
*
* The VDBE knows that a P2 value is a label because labels are
* always negative and P2 values are suppose to be non-negative.
* Hence, a negative P2 value is a label that has yet to be resolved.
*
* PUBLIC: int __vdbe_make_label __P((vdbe_t *));
*/
int
__vdbe_make_label(vm)
vdbe_t *vm;
{
int i;
i = vm->nLabel++;
DBSQL_ASSERT(vm->magic == VDBE_MAGIC_INIT);
if (i >= vm->nLabelAlloc) {
int *new;
vm->nLabelAlloc = (vm->nLabelAlloc * 2) + 10;
if (__dbsql_realloc(NULL, vm->nLabelAlloc * sizeof(vm->aLabel[0]),
&vm->aLabel) == ENOMEM) {
__dbsql_free(NULL, vm->aLabel);
}
}
if (vm->aLabel == 0) {
vm->nLabel = 0;
vm->nLabelAlloc = 0;
return 0;
}
vm->aLabel[i] = -1;
return -1 - i;
}
/*
* __vdbe_resolve_label --
* Resolve label "x" to be the address of the next instruction to
* be inserted. The parameter "x" must have been obtained from
* a prior call to __vdbe_make_label().
*
* PUBLIC: void __vdbe_resolve_label __P((vdbe_t *, int));
*/
void
__vdbe_resolve_label(vm, x)
vdbe_t *vm;
int x;
{
int j;
DBSQL_ASSERT(vm->magic == VDBE_MAGIC_INIT);
if (x < 0 && (-x) <= vm->nLabel && vm->aOp) {
if (vm->aLabel[-1 - x] == vm->nOp)
return;
DBSQL_ASSERT(vm->aLabel[-1 - x] < 0);
vm->aLabel[-1 - x] = vm->nOp;
for (j = 0; j < vm->nOp; j++) {
if (vm->aOp[j].p2 == x)
vm->aOp[j].p2 = vm->nOp;
}
}
}
/*
* __vdbe_current_addr --
* Return the address of the next instruction to be inserted.
*
* PUBLIC: int __vdbe_current_addr __P((vdbe_t *));
*/
int
__vdbe_current_addr(vm)
vdbe_t *vm;
{
DBSQL_ASSERT(vm->magic == VDBE_MAGIC_INIT);
return vm->nOp;
}
/*
* __vdbe_add_op_list --
* Add a whole list of operations to the operation stack. Return the
* address of the first operation added.
*
* PUBLIC: int __vdbe_add_op_list __P((vdbe_t *, int, const vdbe_op_t *));
*/
int
__vdbe_add_op_list(vm, num_op, op)
vdbe_t *vm;
int num_op;
const vdbe_op_t *op;
{
int addr;
DBSQL_ASSERT(vm->magic == VDBE_MAGIC_INIT);
if (vm->nOp + num_op >= vm->nOpAlloc) {
int old_size = vm->nOpAlloc;
vdbe_op_t *new;
vm->nOpAlloc = (vm->nOpAlloc * 2) + num_op + 10;
if (__dbsql_realloc(NULL, vm->nOpAlloc * sizeof(vdbe_op_t),
&vm->aOp) == ENOMEM) {
vm->nOpAlloc = old_size;
return 0;
}
memset(&vm->aOp[old_size], 0,
(vm->nOpAlloc - old_size) * sizeof(vdbe_op_t));
}
addr = vm->nOp;
if (num_op > 0) {
int i;
for (i = 0; i < num_op; i++) {
int p2 = op[i].p2;
vm->aOp[i + addr] = op[i];
if (p2 < 0)
vm->aOp[i + addr].p2 = addr + ADDR(p2);
vm->aOp[i + addr].p3type = op[i].p3 ?
P3_STATIC : P3_NOTUSED;
#ifndef NDEBUG
if (dbsql_vdbe_add_op_trace) {
__vdbe_print_op(0, i + addr, &vm->aOp[i+addr]);
}
#endif
}
vm->nOp += num_op;
}
return addr;
}
/*
* __vdbe_change_p1 --
* Change the value of the P1 operand for a specific instruction.
* This routine is useful when a large program is loaded from a
* static array using __vdbe_add_op_list but we want to make a
* few minor changes to the program.
*
* PUBLIC: void __vdbe_change_p1 __P((vdbe_t *, int, int));
*/
void
__vdbe_change_p1(vm, addr, val)
vdbe_t *vm;
int addr;
int val;
{
DBSQL_ASSERT(vm->magic == VDBE_MAGIC_INIT);
if (vm && addr >= 0 && vm->nOp > addr && vm->aOp) {
vm->aOp[addr].p1 = val;
}
}
/*
* __vdbe_change_p2 --
* Change the value of the P2 operand for a specific instruction.
* This routine is useful for setting a jump destination.
*
* PUBLIC: void __vdbe_change_p2 __P((vdbe_t *, int, int));
*/
void
__vdbe_change_p2(vm, addr, val)
vdbe_t *vm;
int addr;
int val;
{
DBSQL_ASSERT(val >= 0);
DBSQL_ASSERT(vm->magic == VDBE_MAGIC_INIT);
if (vm && addr >= 0 && vm->nOp > addr && vm->aOp) {
vm->aOp[addr].p2 = val;
}
}
/*
* __vdbe_change_p3 --
* Change the value of the P3 operand for a specific instruction.
* This routine is useful when a large program is loaded from a
* static array using __vdbe_add_op_list but we want to make a
* few minor changes to the program.
*
* If n>=0 then the P3 operand is dynamic, meaning that a copy of
* the string is made into memory obtained from __dbsql_calloc().
* A value of n==0 means copy bytes of 'p3' up to and including the
* first null byte. If n>0 then copy n+1 bytes of zP3.
*
* If n==P3_STATIC it means that zP3 is a pointer to a constant static
* string and we can just copy the pointer. n==P3_POINTER means 'p3' is
* a pointer to some object other than a string.
*
* If addr<0 then change P3 on the most recently inserted instruction.
*
* PUBLIC: void __vdbe_change_p3 __P((vdbe_t *, int, const char *, int));
*/
void
__vdbe_change_p3(vm, addr, p3, n)
vdbe_t *vm;
int addr;
const char *p3;
int n;
{
vdbe_op_t *op;
DBSQL_ASSERT(vm->magic == VDBE_MAGIC_INIT);
if (vm == 0 || vm->aOp == 0)
return;
if (addr < 0 || addr >= vm->nOp) {
addr = vm->nOp - 1;
if (addr < 0)
return;
}
op = &vm->aOp[addr];
if (op->p3 && op->p3type == P3_DYNAMIC) {
__dbsql_free(NULL, op->p3);
op->p3 = 0;
}
if (p3 == 0) {
op->p3 = 0;
op->p3type = P3_NOTUSED;
} else if (n < 0) {
op->p3 = (char*)p3;
op->p3type = n;
} else {
__str_nappend(&op->p3, p3, n, NULL);
op->p3type = P3_DYNAMIC;
}
}
/*
* __vdbe_dequote_p3 --
* If the P3 operand to the specified instruction appears
* to be a quoted string token, then this procedure removes
* the quotes.
*
* The quoting operator can be either a grave ascent (ASCII 0x27)
* or a double quote character (ASCII 0x22). Two quotes in a row
* resolve to be a single actual quote character within the string.
*
* PUBLIC: void __vdbe_dequote_p3 __P((vdbe_t *, int));
*/
void
__vdbe_dequote_p3(vm, addr)
vdbe_t *vm;
int addr;
{
vdbe_op_t *op;
DBSQL_ASSERT(vm->magic == VDBE_MAGIC_INIT);
if (vm->aOp == 0)
return;
if (addr < 0 || addr >= vm->nOp) {
addr = vm->nOp - 1;
if (addr < 0)
return;
}
op = &vm->aOp[addr];
if (op->p3 == 0 || op->p3[0] == 0)
return;
if (op->p3type == P3_POINTER)
return;
if (op->p3type != P3_DYNAMIC) {
__dbsql_strdup(NULL, op->p3, &op->p3);
op->p3type = P3_DYNAMIC;
}
__str_unquote(op->p3);
}
/*
* __vdbe_compress_space --
* On the P3 argument of the given instruction, change all
* strings of whitespace characters into a single space and
* delete leading and trailing whitespace.
*
* PUBLIC: void __vdbe_compress_space __P((vdbe_t *, int));
*/
void
__vdbe_compress_space(vm, addr)
vdbe_t *vm;
int addr;
{
unsigned char *z;
int i, j;
vdbe_op_t *op;
DBSQL_ASSERT(vm->magic == VDBE_MAGIC_INIT);
if (vm->aOp == 0 || addr < 0 || addr >= vm->nOp)
return;
op = &vm->aOp[addr];
if (op->p3type == P3_POINTER) {
return;
}
if (op->p3type != P3_DYNAMIC) {
__dbsql_strdup(NULL, op->p3, &op->p3);
op->p3type = P3_DYNAMIC;
}
z = (unsigned char*)op->p3;
if (z == 0)
return;
i = j = 0;
while(isspace(z[i])) {
i++;
}
while(z[i]) {
if (isspace(z[i])) {
z[j++] = ' ';
while(isspace(z[++i])) {}
} else {
z[j++] = z[i++];
}
}
while(j > 0 && isspace(z[j - 1])) {
j--;
}
z[j] = 0;
}
/*
* __vdbe_find_op --
* Search for the current program for the given opcode and P2
* value. Return the address plus 1 if found and 0 if not found.
*
* PUBLIC: int __vdbe_find_op __P((vdbe_t *, int, int));
*/
int
__vdbe_find_op(vm, op, p2)
vdbe_t *vm;
int op;
int p2;
{
int i;
DBSQL_ASSERT(vm->magic == VDBE_MAGIC_INIT);
for (i = 0; i < vm->nOp; i++) {
if (vm->aOp[i].opcode == op && vm->aOp[i].p2 == p2)
return i + 1;
}
return 0;
}
/*
* __vdbe_get_op --
* Return the opcode for a given address.
*
* PUBLIC: vdbe_op_t *__vdbe_get_op __P((vdbe_t *, int));
*/
vdbe_op_t *
__vdbe_get_op(vm, addr)
vdbe_t *vm;
int addr;
{
DBSQL_ASSERT(vm->magic == VDBE_MAGIC_INIT);
DBSQL_ASSERT(addr >= 0 && addr < vm->nOp);
return &vm->aOp[addr];
}
/*
* dbsql_set_result_string --
* The following group or routines are employed by installable functions
* to return their results.
*
* The dbsql_set_result_string() routine can be used to return a string
* value or to return a NULL. To return a NULL, pass in NULL for
* 'result'.
* A copy is made of the string before this routine returns so it is safe
* to pass in an ephemeral string.
*
* dbsql_set_result_error() works like dbsql_set_result_string() except
* that it signals a fatal error. The string argument, if any, is the
* error message. If the argument is NULL a generic substitute error
* message is used.
*
* The dbsql_set_result_int() and dbsql_set_result_double() set the
* return value of the user function to an integer or a double.
*
* These routines are defined here in vdbe_aux.c because they depend on
* knowing the internals of the dbsql_func_t structure which is only
* defined in this source file.
*
* EXTERN: char *dbsql_set_result_string __P((dbsql_func_t *, const char *,
* EXTERN: int));
*/
char *
dbsql_set_result_string(p, result, n)
dbsql_func_t *p;
const char *result;
int n;
{
DBSQL_ASSERT(!p->isStep);
if (p->s.flags & MEM_Dyn) {
__dbsql_free(NULL, p->s.z);
}
if (result == 0) {
p->s.flags = MEM_Null;
n = 0;
p->s.z = 0;
p->s.n = 0;
} else {
if (n < 0)
n = strlen(result);
if (n < NBFS - 1) {
memcpy(p->s.zShort, result, n);
p->s.zShort[n] = 0;
p->s.flags = MEM_Str | MEM_Short;
p->s.z = p->s.zShort;
} else {
if (__dbsql_calloc(NULL, 1, n + 1, &p->s.z) != ENOMEM) {
memcpy(p->s.z, result, n);
p->s.z[n] = 0;
}
p->s.flags = MEM_Str | MEM_Dyn;
}
p->s.n = n + 1;
}
return p->s.z;
}
/*
* dbsql_set_result_null --
*
* EXTERN: void dbsql_set_result_null __P((dbsql_func_t *));
*/
void
dbsql_set_result_null(p)
dbsql_func_t *p;
{
DBSQL_ASSERT(!p->isStep);
if (p->s.flags & MEM_Dyn) {
__dbsql_free(NULL, p->s.z);
}
p->s.flags = MEM_Null;
p->s.z = 0;
p->s.n = 0;
}
/*
* dbsql_set_result_int --
*
* EXTERN: void dbsql_set_result_int __P((dbsql_func_t *, int));
*/
void
dbsql_set_result_int(p, result)
dbsql_func_t *p;
int result;
{
DBSQL_ASSERT(!p->isStep);
if (p->s.flags & MEM_Dyn) {
__dbsql_free(NULL, p->s.z);
}
p->s.i = result;
p->s.flags = MEM_Int;
}
/*
* dbsql_set_result_int64 --
*
* EXTERN: void dbsql_set_result_int64 __P((dbsql_func_t *, int64_t));
*/
void
dbsql_set_result_int64(p, result) /*TODO*/
dbsql_func_t *p;
int64_t result;
{
DBSQL_ASSERT(0);
}
/*
* dbsql_set_result_double --
*
* EXTERN: void dbsql_set_result_double __P((dbsql_func_t *, double));
*/
void
dbsql_set_result_double(p, result)
dbsql_func_t *p;
double result;
{
DBSQL_ASSERT(!p->isStep);
if (p->s.flags & MEM_Dyn) {
__dbsql_free(NULL, p->s.z);
}
p->s.r = result;
p->s.flags = MEM_Real;
}
/*
* dbsql_set_result_error --
*
* EXTERN: void dbsql_set_result_error __P((dbsql_func_t *, const char *,
* EXTERN: int));
*/
void
dbsql_set_result_error(p, msg, n)
dbsql_func_t *p;
const char *msg;
int n;
{
DBSQL_ASSERT(!p->isStep);
dbsql_set_result_string(p, msg, n);
p->isError = 1;
}
/*
* dbsql_set_result_blob --
*
* EXTERN: void dbsql_set_result_blob __P((dbsql_func_t *, const void *,
* EXTERN: size_t, void(*)(void*)));
*/
void
dbsql_set_result_blob(p, result, size, finalize) /*TODO*/
dbsql_func_t *p;
const void *result;
size_t size;
void(*finalize)(void*);
{
DBSQL_ASSERT(0);
}
/*
* dbsql_set_result_varchar --
*
* EXTERN: void dbsql_set_result_varchar __P((dbsql_func_t *, const char *,
* EXTERN: size_t, void(*)(void*)));
*/
void
dbsql_set_result_varchar(p, result, size, finalize) /*TODO*/
dbsql_func_t *p;
const char *result;
size_t size;
void(*finalize)(void*);
{
DBSQL_ASSERT(0);
}
/*
* dbsql_user_data --
* Extract the user data from a dbsql_func_t structure and return a
* pointer to it.
*
* EXTERN: void *dbsql_user_data __P((dbsql_func_t *));
*/
void *
dbsql_user_data(p)
dbsql_func_t *p;
{
DBSQL_ASSERT(p && p->pFunc);
return p->pFunc->pUserData;
}
/*
* dbsql_aggregate_context --
* Allocate or return the aggregate context for a user function. A new
* context is allocated on the first call. Subsequent calls return the
* same context that was returned on prior calls.
*
* This routine is defined here in vdbe.c because it depends on knowing
* the internals of the dbsql_func_t structure which is only defined in
* this source file.
*
* EXTERN: void *dbsql_aggregate_context __P((dbsql_func_t *, int));
*/
void *
dbsql_aggregate_context(p, num_bytes)
dbsql_func_t *p;
int num_bytes;
{
DBSQL_ASSERT(p && p->pFunc && p->pFunc->xStep);
if (p->pAgg == 0) {
if (num_bytes <= NBFS) {
p->pAgg = (void *)p->s.z;
memset(p->pAgg, 0, num_bytes);
} else {
__dbsql_calloc(NULL, 1, num_bytes, &p->pAgg);
}
}
return p->pAgg;
}
/*
* dbsql_aggregate_count --
* Return the number of times the Step function of a aggregate has been
* called.
*
* This routine is defined here in vdbe.c because it depends on knowing
* the internals of the dbsql_func_t structure which is only defined in
* this source file.
*
* EXTERN: int dbsql_aggregate_count __P((dbsql_func_t *));
*/
int
dbsql_aggregate_count(p)
dbsql_func_t *p;
{
DBSQL_ASSERT(p && p->pFunc && p->pFunc->xStep);
return p->cnt;
}
#if !defined(NDEBUG) || defined(VDBE_PROFILE)
/*
* __vdbe_print_op --
* Print a single opcode. This routine is used for debugging only.
*
* PUBLIC: void __vdbe_print_op __P((FILE *, int, vdbe_op_t *));
*/
void
__vdbe_print_op(out, pc, op)
FILE *out;
int pc;
vdbe_op_t *op;
{
char *p3;
char ptr[40];
if (op->p3type == P3_POINTER) {
sprintf(ptr, "ptr(%p)", op->p3);
p3 = ptr;
} else {
p3 = op->p3;
}
if (out == 0)
out = stdout;
fprintf(out,"%4d %-12s %4d %4d %s\n",
pc, __opcode_names[op->opcode],
op->p1, op->p2, p3 ? p3 : "");
fflush(out);
}
#endif
/*
* __vdbe_list --
* Give a listing of the program in the virtual machine.
*
* The interface is the same as __vdbe_exec(). But instead of
* running the code, it invokes the callback once for each instruction.
* This feature is used to implement "EXPLAIN".
*
* PUBLIC: int __vdbe_list __P((vdbe_t *));
*/
int
__vdbe_list(vm)
vdbe_t *vm;
{
int i;
DBSQL *db = vm->db;
static char *column_names[] = {
"addr", "opcode", "p1", "p2", "p3",
"int", "text", "int", "int", "text",
0
};
DBSQL_ASSERT(vm->popStack == 0);
DBSQL_ASSERT(vm->explain);
vm->azColName = column_names;
vm->azResColumn = vm->zArgv;
for (i = 0; i < 5; i++) {
vm->zArgv[i] = vm->aStack[i].zShort;
}
vm->rc = DBSQL_SUCCESS;
for (i = vm->pc; vm->rc == DBSQL_SUCCESS && i < vm->nOp; i++) {
if (db->flags & DBSQL_Interrupt) {
db->flags &= ~DBSQL_Interrupt;
if (db->magic != DBSQL_STATUS_BUSY) {
vm->rc = DBSQL_MISUSE;
} else {
vm->rc = DBSQL_INTERRUPTED;
}
__str_append(&vm->zErrMsg, dbsql_strerror(vm->rc),
(char*)0);
break;
}
sprintf(vm->zArgv[0], "%d", i);
sprintf(vm->zArgv[2], "%d", vm->aOp[i].p1);
sprintf(vm->zArgv[3], "%d", vm->aOp[i].p2);
if (vm->aOp[i].p3type == P3_POINTER) {
sprintf(vm->aStack[4].zShort, "ptr(%p)",
vm->aOp[i].p3);
vm->zArgv[4] = vm->aStack[4].zShort;
} else {
vm->zArgv[4] = vm->aOp[i].p3;
}
vm->zArgv[1] = __opcode_names[vm->aOp[i].opcode];
if (vm->xCallback == 0) {
vm->pc = i + 1;
vm->azResColumn = vm->zArgv;
vm->nResColumn = 5;
return DBSQL_ROW;
}
if (__safety_off(db)) {
vm->rc = DBSQL_MISUSE;
break;
}
if (vm->xCallback(vm->pCbArg, 5, vm->zArgv, vm->azColName)) {
vm->rc = DBSQL_ABORT;
}
if (__safety_on(db)) {
vm->rc = DBSQL_MISUSE;
}
}
return vm->rc == DBSQL_SUCCESS ? DBSQL_DONE : DBSQL_ERROR;
}
/*
* __vdbe_make_ready --
* Prepare a virtual machine for execution. This involves things such
* as allocating stack space and initializing the program counter.
* After the VDBE has be prepped, it can be executed by one or more
* calls to __vdbe_exec().
*
* The behavior of __vdbe_exec() is influenced by the parameters to
* this routine. If 'callback' is NULL, then __vdbe_exec() will return
* with DBSQL_ROW whenever there is a row of the result set ready
* to be delivered. vm->azResColumn will point to the row and
* vm->nResColumn gives the number of columns in the row. If 'callback'
* is not NULL, then the 'callback()' routine is invoked to process each
* row in the result set.
*
* PUBLIC: void __vdbe_make_ready __P((vdbe_t *, int, dbsql_callback, void *,
* PUBLIC: int));
*
* vm The VDBE
* num_var Number of '?' seen in the SQL statement
* callback Result callback
* callback_arg 1st argument to callback()
* explain_p True if the EXPLAIN keywords is present
*/
void
__vdbe_make_ready(vm, num_var, callback, callback_arg, explain_p)
vdbe_t *vm;
int num_var;
dbsql_callback callback;
void *callback_arg;
int explain_p;
{
int n;
DBSQL_ASSERT(vm != 0);
DBSQL_ASSERT(vm->magic == VDBE_MAGIC_INIT);
/*
* Add a HALT instruction to the very end of the program.
*/
if (vm->nOp == 0 ||
(vm->aOp && vm->aOp[vm->nOp - 1].opcode != OP_Halt)) {
__vdbe_add_op(vm, OP_Halt, 0, 0);
}
/*
* No instruction ever pushes more than a single element onto the
* stack. And the stack never grows on successive executions of the
* same loop. So the total number of instructions is an upper bound
* on the maximum stack depth required.
*
* Allocation all the stack space we will ever need.
*/
if (vm->aStack == 0) {
vm->nVar = num_var;
DBSQL_ASSERT(num_var >= 0);
n = explain_p ? 10 : vm->nOp;
__dbsql_calloc(NULL, 1,
/* aStack and zArgv */
(n * (sizeof(vm->aStack[0]) + (2 * sizeof(char*))) +
/* azVar, anVar, abVar */
(vm->nVar * (sizeof(char*)) + sizeof(int) + 1)),
&vm->aStack);
vm->zArgv = (char**)&vm->aStack[n];
vm->azColName = (char**)&vm->zArgv[n];
vm->azVar = (char**)&vm->azColName[n];
vm->anVar = (int*)&vm->azVar[vm->nVar];
vm->abVar = (u_int8_t*)&vm->anVar[vm->nVar];
}
__hash_init(&vm->agg.hash, DBSQL_HASH_BINARY, 0);
vm->agg.pSearch = 0;
#ifdef MEMORY_DEBUG
if (__os_file_exists("vdbe_trace")){
vm->trace = stdout;
}
#endif
vm->pTos = &vm->aStack[-1];
vm->pc = 0;
vm->rc = DBSQL_SUCCESS;
vm->uniqueCnt = 0;
vm->returnDepth = 0;
vm->errorAction = OE_Abort;
vm->undoTransOnError = 0;
vm->xCallback = callback;
vm->pCbArg = callback_arg;
vm->popStack = 0;
vm->explain |= explain_p;
vm->magic = VDBE_MAGIC_RUN;
#ifdef VDBE_PROFILE
{
int i;
for (i = 0; i < vm->nOp; i++) {
vm->aOp[i].cnt = 0;
vm->aOp[i].cycles = 0;
}
}
#endif
}
/*
* __vdbe_sorter_reset --
* Remove any elements that remain on the sorter for the VDBE given.
*
* PUBLIC: void __vdbe_sorter_reset __P((vdbe_t *));
*/
void
__vdbe_sorter_reset(vm)
vdbe_t *vm;
{
while(vm->pSort) {
sorter_t *s = vm->pSort;
vm->pSort = s->pNext;
__dbsql_free(NULL, s->zKey);
__dbsql_free(NULL, s->pData);
__dbsql_free(NULL, s);
}
}
/*
* __vdbe_agg_reset --
* Reset an Agg structure. Delete all its contents.
*
* For installable aggregate functions, if the step function has been
* called, make sure the finalizer function has also been called. The
* finalizer might need to free memory that was allocated as part of its
* private context. If the finalizer has not been called yet, call it
* now.
*
* PUBLIC: void __vdbe_agg_reset __P((agg_t *));
*/
void
__vdbe_agg_reset(agg)
agg_t *agg;
{
int i;
hash_ele_t *p;
for (p = __hash_first(&agg->hash); p; p = __hash_next(p)) {
agg_elem_t *elem = __hash_data(p);
DBSQL_ASSERT(agg->apFunc != 0);
for (i = 0; i < agg->nMem; i++) {
mem_t *mem = &elem->aMem[i];
if (agg->apFunc[i] && (mem->flags & MEM_AggCtx) != 0) {
dbsql_func_t ctx;
ctx.pFunc = agg->apFunc[i];
ctx.s.flags = MEM_Null;
ctx.pAgg = mem->z;
ctx.cnt = mem->i;
ctx.isStep = 0;
ctx.isError = 0;
(*agg->apFunc[i]->xFinalize)(&ctx);
if (mem->z != 0 && mem->z != mem->zShort) {
__dbsql_free(NULL, mem->z);
}
} else if (mem->flags & MEM_Dyn) {
__dbsql_free(NULL, mem->z);
}
}
__dbsql_free(NULL, elem);
}
__hash_clear(&agg->hash);
__dbsql_free(NULL, agg->apFunc);
agg->apFunc = 0;
agg->pCurrent = 0;
agg->pSearch = 0;
agg->nMem = 0;
}
/*
* __vdbe_keylist_free --
* Delete a keylist
*
* PUBLIC: void __vdbe_keylist_free __P((keylist_t *));
*/
void
__vdbe_keylist_free(p)
keylist_t *p;
{
while(p) {
keylist_t *next = p->pNext;
__dbsql_free(NULL, p);
p = next;
}
}
/*
* __vdbe_cleanup_cursor --
* Close a cursor and release all the resources that cursor happens
* to hold.
*
* PUBLIC: void __vdbe_cleanup_cursor __P((cursor_t *));
*/
void
__vdbe_cleanup_cursor(cx)
cursor_t *cx;
{
if (cx->pCursor) {
__sm_close_cursor(cx->pCursor);
}
if (cx->pBt) {
__sm_close_db(cx->pBt);
}
__dbsql_free(NULL, cx->pData);
memset(cx, 0, sizeof(cursor_t));
}
/*
* __close_all_cursors --
* Close all cursors
*
* STATIC: static void __close_all_cursors __P((vdbe_t *));
*/
static void
__close_all_cursors(vm)
vdbe_t *vm;
{
int i;
for (i = 0; i < vm->nCursor; i++) {
__vdbe_cleanup_cursor(&vm->aCsr[i]);
}
__dbsql_free(NULL, vm->aCsr);
vm->aCsr = 0;
vm->nCursor = 0;
}
/*
* __cleanup --
* Clean up the VM after execution.
*
* This routine will automatically close any cursors, lists, and/or
* sorters that were left open. It also deletes the values of
* variables in the azVariable[] array.
*
* STATIC: static void __cleanup __P((vdbe_t *));
*/
static void
__cleanup(vm)
vdbe_t *vm;
{
int i;
if (vm->aStack) {
mem_t *tos = vm->pTos;
while(tos >= vm->aStack) {
if (tos->flags & MEM_Dyn) {
__dbsql_free(NULL, tos->z);
}
tos--;
}
vm->pTos = tos;
}
__close_all_cursors(vm);
if (vm->aMem) {
for(i = 0; i < vm->nMem; i++) {
if (vm->aMem[i].flags & MEM_Dyn) {
__dbsql_free(NULL, vm->aMem[i].z);
}
}
}
__dbsql_free(NULL, vm->aMem);
vm->aMem = 0;
vm->nMem = 0;
if (vm->pList) {
__vdbe_keylist_free(vm->pList);
vm->pList = 0;
}
__vdbe_sorter_reset(vm);
if (vm->pFile) {
if (vm->pFile != stdin)
fclose(vm->pFile);
vm->pFile = 0;
}
if (vm->azField) {
__dbsql_free(NULL, vm->azField);
vm->azField = 0;
}
vm->nField = 0;
if (vm->zLine) {
__dbsql_free(NULL, vm->zLine);
vm->zLine = 0;
}
vm->nLineAlloc = 0;
__vdbe_agg_reset(&vm->agg);
if (vm->aSet) {
for(i = 0; i < vm->nSet; i++) {
__hash_clear(&vm->aSet[i].hash);
}
}
__dbsql_free(NULL, vm->aSet);
vm->aSet = 0;
vm->nSet = 0;
if (vm->keylistStack) {
int ii;
for(ii = 0; ii < vm->keylistStackDepth; ii++) {
__vdbe_keylist_free(vm->keylistStack[ii]);
}
__dbsql_free(NULL, vm->keylistStack);
vm->keylistStackDepth = 0;
vm->keylistStack = 0;
}
__dbsql_free(NULL, vm->zErrMsg);
vm->zErrMsg = 0;
}
/*
* __vdbe_reset --
* Clean up a VDBE after execution but do not delete the VDBE just yet.
* Write any error messages into *pzErrMsg. Return the result code.
*
* After this routine is run, the VDBE should be ready to be executed
* again.
* PUBLIC: int __vdbe_reset __P((vdbe_t *, char **));
*/
int
__vdbe_reset(vm, err_msgs)
vdbe_t *vm;
char **err_msgs;
{
int i;
DBSQL *db = vm->db;
if (vm->magic != VDBE_MAGIC_RUN && vm->magic != VDBE_MAGIC_HALT) {
__str_append(err_msgs, dbsql_strerror(DBSQL_MISUSE), (char*)0);
return DBSQL_MISUSE;
}
if (vm->zErrMsg) {
if (err_msgs && *err_msgs == 0) {
*err_msgs = vm->zErrMsg;
} else {
__dbsql_free(NULL, vm->zErrMsg);
}
vm->zErrMsg = 0;
}
__cleanup(vm);
if (vm->rc != DBSQL_SUCCESS) {
switch(vm->errorAction) {
case OE_Abort:
/* FALLTHROUG */
case OE_Rollback:
/* TODO __sm_abort_txn(db); */
db->flags &= ~DBSQL_InTrans;
db->onError = OE_Default;
break;
default:
if (vm->undoTransOnError) {
/* TODO __sm_abort_txn(db); */
db->flags &= ~DBSQL_InTrans;
db->onError = OE_Default;
}
break;
}
__rollback_internal_changes(db);
}
DBSQL_ASSERT(vm->pTos < &vm->aStack[vm->pc]);
#ifdef VDBE_PROFILE
{
FILE *out = fopen("vdbe_profile.out", "a");
if (out) {
int i;
fprintf(out, "---- ");
for (i = 0; i < p->nOp; i++) {
fprintf(out, "%02x", vm->aOp[i].opcode);
}
fprintf(out, "\n");
for(i = 0; i < vm->nOp; i++) {
fprintf(out, "%6d %10lld %8lld ",
vm->aOp[i].cnt,
vm->aOp[i].cycles,
(vm->aOp[i].cnt > 0) ?
vm->aOp[i].cycles / vm->aOp[i].cnt :0);
__vdbe_print_op(out, i, &vm->aOp[i]);
}
fclose(out);
}
}
#endif
vm->magic = VDBE_MAGIC_INIT;
return vm->rc;
}
/*
* __vdbe_finalize --
* Clean up and delete a VDBE after execution. Return an integer which is
* the result code. Write any error message text into *err_msgs.
*
* PUBLIC: int __vdbe_finalize __P((vdbe_t *, char **));
*/
int
__vdbe_finalize(vm, err_msgs)
vdbe_t *vm;
char **err_msgs;
{
int rc;
DBSQL *db;
if (vm->magic != VDBE_MAGIC_RUN && vm->magic != VDBE_MAGIC_HALT) {
__str_append(err_msgs, dbsql_strerror(DBSQL_MISUSE), (char*)0);
return DBSQL_MISUSE;
}
db = vm->db;
rc = __vdbe_reset(vm, err_msgs);
__vdbe_delete(vm);
if (db->want_to_close && db->pVdbe == 0) {
db->close(db);
}
return rc;
}
/*
* __api_bind --
* Set the values of all variables. Variable $1 in the original SQL will
* be the string azValue[0]. $2 will have the value azValue[1]. And
* so forth. If a value is out of range (for example $3 when nValue==2)
* then its value will be NULL.
* This routine overrides any prior call.
*
* PUBLIC: int __api_bind __P((dbsql_stmt_t *, int, const char *, int,
* PUBLIC: int));
*/
int
__api_bind(p, i, val, len, copy)
dbsql_stmt_t *p;
int i;
const char *val;
int len;
int copy;
{
vdbe_t *vm = (vdbe_t*)p;
if (vm->magic != VDBE_MAGIC_RUN || vm->pc != 0) {
return DBSQL_MISUSE;
}
if (i < 1 || i > vm->nVar) {
return DBSQL_RANGE;
}
i--;
if (vm->abVar[i]) {
__dbsql_free(NULL, vm->azVar[i]);
}
if (val == 0) {
copy = 0;
len = 0;
}
if (len < 0) {
len = strlen(val) + 1;
}
if (copy) {
if (__dbsql_calloc(NULL, 1, len, &vm->azVar[i]) != ENOMEM)
memcpy(vm->azVar[i], val, len);
} else {
vm->azVar[i] = (char*)val;
}
vm->abVar[i] = copy;
vm->anVar[i] = len;
return DBSQL_SUCCESS;
}
/*
* __vdbe_delete --
* Delete an entire VDBE.
*
* PUBLIC: void __vdbe_delete __P((vdbe_t *));
*/
void
__vdbe_delete(vm)
vdbe_t *vm;
{
int i;
if (vm == 0)
return;
__cleanup(vm);
if (vm->pPrev) {
vm->pPrev->pNext = vm->pNext;
} else {
DBSQL_ASSERT(vm->db->pVdbe == vm );
vm->db->pVdbe = vm->pNext;
}
if (vm->pNext) {
vm->pNext->pPrev = vm->pPrev;
}
vm->pPrev = vm->pNext = 0;
if (vm->nOpAlloc == 0) {
vm->aOp = 0;
vm->nOp = 0;
}
for (i = 0; i < vm->nOp; i++) {
if (vm->aOp[i].p3type == P3_DYNAMIC) {
__dbsql_free(NULL, vm->aOp[i].p3);
}
}
for(i = 0; i < vm->nVar; i++) {
if (vm->abVar[i])
__dbsql_free(NULL, vm->azVar[i]);
}
__dbsql_free(NULL, vm->aOp);
__dbsql_free(NULL, vm->aLabel);
__dbsql_free(NULL, vm->aStack);
vm->magic = VDBE_MAGIC_DEAD;
__dbsql_free(NULL, vm);
}
/*
* __vdbe_byte_swap --
* Convert an integer in between the native integer format and
* the bigEndian format used as the record number for tables.
*
* The bigEndian format (most significant byte first) is used for
* record numbers so that records will sort into the correct order
* even though memcmp() is used to compare the keys. On machines
* whose native integer format is little endian (ex: i486) the
* order of bytes is reversed. On native big-endian machines
* (ex: Alpha, Sparc, Motorola) the byte order is the same.
*
* This function is its own inverse. In other words
*
* X == byteSwap(byteSwap(X))
*
* PUBLIC: int __vdbe_byte_swap __P((int));
*/
int
__vdbe_byte_swap(x)
int x;
{
union {
char buf[sizeof(int)];
int i;
} ux;
ux.buf[3] = x&0xff;
ux.buf[2] = (x>>8)&0xff;
ux.buf[1] = (x>>16)&0xff;
ux.buf[0] = (x>>24)&0xff;
return ux.i;
}
/*
* __vdbe_cursor_moveto --
* If a MoveTo operation is pending on the given cursor, then do that
* MoveTo now. Return an error code. If no MoveTo is pending, this
* routine does nothing and returns DBSQL_SUCCESS.
*
* PUBLIC: int __vdbe_cursor_moveto __P((cursor_t *));
*/
int
__vdbe_cursor_moveto(p)
cursor_t *p;
{
if (p->deferredMoveto) {
int res;
#ifdef CONFIG_TEST
extern int dbsql_search_count;
#endif
__sm_moveto(p->pCursor, (char*)&p->movetoTarget,
sizeof(int), &res);
p->lastRecno = KEY_TO_INT(p->movetoTarget);
p->recnoIsValid = (res == 0);
if (res < 0) {
__sm_next(p->pCursor, &res);
}
#ifdef CONFIG_TEST
dbsql_search_count++;
#endif
p->deferredMoveto = 0;
}
return DBSQL_SUCCESS;
}