1393 lines
32 KiB
C
1393 lines
32 KiB
C
/*-
|
|
* DBSQL - A SQL database engine.
|
|
*
|
|
* Copyright (C) 2007 The DBSQL Group, Inc. - All rights reserved.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* 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_method.c 7 2007-02-03 13:34:17Z gburd $
|
|
*/
|
|
|
|
/*
|
|
* 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;
|
|
}
|