/* * Code for testing the xprintf() function. This code is used for testing * only and will not be included when the library is built without * CONFIG_TEST set. */ #include "dbsql_config.h" #ifndef NO_SYSTEM_INCLUDES #include #include #endif #include "dbsql_int.h" #include "tcl.h" /* __testset_1 --------------------------------------------------------------*/ #ifdef DB_WIN32 #define PTR_FMT "%x" #else #define PTR_FMT "%p" #endif /* * get_dbsql_from_ptr -- * Decode a pointer encoded in a string to an pointer to a structure. */ static int get_dbsql_from_ptr(interp, args, dbsqlp) Tcl_Interp *interp; const char *args; DBSQL **dbsqlp; { if (sscanf(args, PTR_FMT, (void**)dbsqlp) != 1 && (args[0] != '0' || args[1] != 'x' || sscanf(&args[2], PTR_FMT, (void**)dbsqlp) != 1)) { Tcl_AppendResult(interp, "\"", args, "\" is not a valid pointer value", 0); return TCL_ERROR; } return TCL_OK; } /* * get_sqlvm_from_ptr -- * Decode a pointer to an sqlvm_t object. */ static int get_sqlvm_from_ptr(interp, args, sqlvmp) Tcl_Interp *interp; const char *args; dbsql_stmt_t **sqlvmp; { if (sscanf(args, PTR_FMT, (void**)sqlvmp) != 1) { Tcl_AppendResult(interp, "\"", args, "\" is not a valid pointer value", 0); return TCL_ERROR; } return TCL_OK; } /* * __encode_as_ptr -- * Generate a text representation of a pointer that can be understood * by the get_dbsql_from_ptr and get_sqlvm_from_ptr routines above. * * The problem is, on some machines (Solaris) if you do a printf with * "%p" you cannot turn around and do a scanf with the same "%p" and * get your pointer back. You have to prepend a "0x" before it will * work. But this behavior varies from machine to machine. This * work around tests the string after it is generated to see if it can be * understood by scanf, and if not, try prepending an "0x" to see if * that helps. If nothing works, a fatal error is generated. */ static int __encode_as_ptr(Tcl_Interp* interp, char* ptr, void* p) { void *p2; sprintf(ptr, PTR_FMT, p); if( sscanf(ptr, PTR_FMT, &p2) != 1 || p2 != p) { sprintf(ptr, "0x" PTR_FMT, p); if (sscanf(ptr, PTR_FMT, &p2) != 1 || p2 != p) { Tcl_AppendResult(interp, "unable to convert a pointer to a string " "in the file " __FILE__ " in function __encode_as_ptr(). Please " "report this problem to support@dbsql.org as a new " "report. Please provide detailed information about how " "you compiled DBSQL (include your config.log file) and what " "hardware and operating system you are using.", 0); return TCL_ERROR; } } return TCL_OK; } /* * test_dbsql_env_create -- * Usage: dbsql_create name * Returns: The name of an open database. */ static int test_dbsql_env_create(void* notused, Tcl_Interp* interp, int argc, char* *argv) { DBSQL *db; char *err_msgs; char ptr[100]; int rc; dbsql_ctx_t *dbctx = (dbsql_ctx_t*)_dbctx; if (argc != 2) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " FILENAME\"", 0); return TCL_ERROR; } if ((rc = dbsql_create_env(&db, argv[1], NULL, 0, DBSQL_THREAD)) != DBSQL_SUCCESS) { Tcl_AppendResult(interp, dbsql_strerror(rc), 0); return TCL_ERROR; } if (__encode_as_ptr(interp, ptr, db)) return TCL_ERROR; Tcl_AppendResult(interp, ptr, 0); return TCL_OK; } /* * exec_printf_cb -- * The callback routine for DBSQL->exec_printf(). */ static int exec_printf_cb(void* arg, int argc, char* *argv, char* *name) { Tcl_DString *str; int i; str = (Tcl_DString*)arg; if (Tcl_DStringLength(str) == 0) { for(i = 0; i < argc; i++) { Tcl_DStringAppendElement(str, name[i] ? name[i] : "NULL"); } } for(i = 0; i < argc; i++) { Tcl_DStringAppendElement(str, argv[i] ? argv[i] : "NULL"); } return 0; } /* * test_exec_printf -- * Usage: dbsql_exec_printf DBSQL FORMAT STRING * * Invoke the dbsql_exec_printf() interface using the open database * DB. The SQL is the string FORMAT. The format string should contain * one %s or %q. STRING is the value inserted into %s or %q. */ static int test_exec_printf(void* _dbctx, Tcl_Interp* interp, int argc, char* *argv) { DBSQL *dbp; Tcl_DString str; int rc; char *err; char buf[30]; dbsql_ctx_t *dbctx = (dbsql_ctx_t*)_dbctx; err = 0; if (argc != 4) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " DB FORMAT STRING", 0); return TCL_ERROR; } if (get_dbsql_from_ptr(interp, argv[1], &dbp)) return TCL_ERROR; Tcl_DStringInit(&str); rc = dbctx->dbp->printf(dbctx->dbp, argv[2], exec_printf_cb, &str, &err, argv[3]); sprintf(buf, "%d", rc); Tcl_AppendElement(interp, buf); Tcl_AppendElement(interp, rc == DBSQL_SUCCESS ? Tcl_DStringValue(&str) : err); Tcl_DStringFree(&str); if (err) free(err); return TCL_OK; } /* * test_xprintf -- * Usage: xprintf SEPARATOR ARG0 ARG1 ... * * Test the %z format of xprintf(). Use multiple xprintf() calls to * concatenate arg0 through argn using separator as the separator. * Return the result. */ static int test_xprintf(void* _dbctx, Tcl_Interp* interp, int argc, char* *argv) { int i; char *result; dbsql_ctx_t *dbctx = (dbsql_ctx_t*)_dbctx; result = 0; for(i = 2; i < argc; i++) { result = xvprintf(dbctx->dbp, "%z%s%s", result, argv[1], argv[i]); } Tcl_AppendResult(interp, result, 0); __dbsql_free(dbctx->dbp, result); return TCL_OK; } /* * test_get_table_printf -- * Usage: dbsql_get_table_printf DB FORMAT STRING * * Invoke the dbsql_get_table_printf() interface using the open database * DB. The SQL is the string FORMAT. The format string should contain * one %s or %q. STRING is the value inserted into %s or %q. */ static int test_get_table_printf(void* notused, Tcl_Interp* interp, int argc, char* *argv) { DBSQL *db; Tcl_DString str; int rc; char *err = 0; int nrow, ncol; char **result; int i; char buf[30]; dbsql_ctx_t *dbctx = (dbsql_ctx_t*)_dbctx; if (argc != 4) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " DB FORMAT STRING", 0); return TCL_ERROR; } if (get_dbsql_from_ptr(interp, argv[1], &db)) return TCL_ERROR; Tcl_DStringInit(&str); rc = dbsql_get_table_printf(db, argv[2], &result, &nrow, &ncol, &err, argv[3]); sprintf(buf, "%d", rc); Tcl_AppendElement(interp, buf); if (rc == DBSQL_SUCCESS) { sprintf(buf, "%d", nrow); Tcl_AppendElement(interp, buf); sprintf(buf, "%d", ncol); Tcl_AppendElement(interp, buf); for (i = 0; i < (nrow + 1) * ncol; i++) { Tcl_AppendElement(interp, result[i] ? result[i] : "NULL"); } } else { Tcl_AppendElement(interp, err); } dbsql_free_table(result); if (err) free(err); return TCL_OK; } /* * test_last_rowid -- * Usage: dbsql_last_inserted_rowid DB * * Returns the integer ROWID of the most recent insert. */ static int test_last_rowid(void* notused, Tcl_Interp* interp, int argc, char* *argv) { DBSQL *db; char buf[30]; dbsql_ctx_t *dbctx = (dbsql_ctx_t*)_dbctx; if (argc != 2) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " DB\"", 0); return TCL_ERROR; } if (get_dbsql_from_ptr(interp, argv[1], &db)) return TCL_ERROR; sprintf(buf, "%d", dbsql_last_inserted_rowid(db)); Tcl_AppendResult(interp, buf, 0); return DBSQL_SUCCESS; } /* * test_close -- * Usage: dbsql_close DB * * Closes the database. */ static int test_close(void* _dbctx, Tcl_Interp* interp, int argc, char* *argv) { DBSQL *db; dbsql_ctx_t *dbctx = (dbsql_ctx_t*)_dbctx; if (argc != 2) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " FILENAME\"", 0); return TCL_ERROR; } if (get_dbsql_from_ptr(interp, argv[1], &db)) return TCL_ERROR; dbsql_close(db); return TCL_OK; } /* * ifnull_func -- * Implementation of the x_coalesce() function. * Return the first argument non-NULL argument. */ static void ifnull_func(context, argc, argv) dbsql_func_t *context; int argc; const char **argv; { int i; for(i = 0; i < argc; i++) { if (argv[i]) { dbsql_set_result_string(context, argv[i], -1); break; } } } /* * A structure into which to accumulate text. */ struct dstr { int size; /* Space allocated */ int len; /* Space used */ char *str; /* The string */ }; /* * dstr_append -- * Append text to a dstr. */ static void dstr_append(p, z, divider) struct dstr *p; const char *z; int divider; { int n = strlen(z); if (p->len + n + 2 > p->size) { char *zNew; p->size = p->size * 2 + n + 200; if (__dbsql_realloc(NULL, p->size, &p->str) == ENOMEM) return; } if (divider && p->len > 0) { p->str[p->len++] = divider; } memcpy(&p->str[p->len], z, n + 1); p->len += n; } /* * exec_func_callback -- * Invoked for each callback from dbsql_exec */ static int exec_func_callback(void* data, int argc, char* *argv, char* *notused) { struct dstr *p; int i; COMPQUIET(notused, NULL); p = (struct dstr*)data; for(i = 0; i < argc; i++) { if (argv[i] == 0) { dstr_append(p, "NULL", ' '); } else { dstr_append(p, argv[i], ' '); } } return 0; } /* * exec_func -- * Implementation of the x_dbsql_exec() function. This function takes * a single argument and attempts to execute that argument as SQL code. * This is illegal and should set the DBSQL_MISUSE flag on the database. * This routine simulates the effect of having two threads attempt to * use the same database at the same time. */ static void exec_func(context, argc, argv) dbsql_func_t *context; int argc; const char **argv; { struct dstr x; memset(&x, 0, sizeof(x)); dbsql_exec((DBSQL*)dbsql_user_data(context), argv[0], exec_func_callback, &x, 0); dbsql_set_result_string(context, x.str, x.len); __dbsql_free(NULL, x.str); } /* * test_create_function -- * Usage: sqlite_test_create_function DB * * Call the dbsql_create_function API on the given database in order * to create a function named "x_coalesce". This function does the same * thing as the "coalesce" function. This function also registers an * SQL function named "x_dbsql_exec" that invokes dbsql_exec(). * Invoking dbsql_exec() in this way is illegal recursion and should * raise an DBSQL_MISUSE error. The effect is similar to trying to use * the same database connection from * two threads at the same time. * * The original motivation for this routine was to be able to call the * dbsql_create_function function while a query is in progress in order * to test the DBSQL_MISUSE detection logic. */ static int test_create_function(void* _dbctx, Tcl_Interp* interp, int argc, char* *argv) { DBSQL *db; extern void Md5_Register(DBSQL*); dbsql_ctx_t *dbctx = (dbsql_ctx_t*)_dbctx; if (argc != 2) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " FILENAME\"", 0); return TCL_ERROR; } if (get_dbsql_from_ptr(interp, argv[1], &db)) return TCL_ERROR; dbsql_create_function(db, "x_coalesce", -1, ifnull_func, 0); dbsql_create_function(db, "x_dbsql_exec", 1, exec_func, db); return TCL_OK; } /* * Routines to implement the x_count() aggregate function. */ typedef struct cnt { int n; } cnt_t; /* * count_step -- * */ static void count_step(context, argc, argv) dbsql_func_t *context; int argc; const char **argv; { cnt_t *p; p = dbsql_aggregate_context(context, sizeof(*p)); if ((argc == 0 || argv[0]) && p) { p->n++; } } /* * count_finalize -- * */ static void count_finalize(dbsql_func_t* context) { cnt_t *p; p = dbsql_aggregate_context(context, sizeof(*p)); dbsql_set_result_int(context, p ? p->n : 0); } /* * test_create_aggregate -- * Usage: sqlite_test_create_aggregate DB * * Call the dbsql_create_function API on the given database in order * to create a function named "x_count". This function does the same * thing as the "md5sum" function. * * The original motivation for this routine was to be able to call the * dbsql_create_aggregate function while a query is in progress in order * to test the DBSQL_MISUSE detection logic. */ static int test_create_aggregate(void* notused, Tcl_Interp* interp, int argc, char* *argv) { DBSQL *db; COMPQUIET(notused, NULL); if (argc != 2) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " FILENAME\"", 0); return TCL_ERROR; } if (get_dbsql_from_ptr(interp, argv[1], &db)) return TCL_ERROR; dbsql_create_aggregate(db, "x_count", 0, count_step, count_finalize, 0); dbsql_create_aggregate(db, "x_count", 1, count_step, count_finalize, 0); return TCL_OK; } #if 0 TODO /* * dbsql_mprintf_int -- * Usage: dbsql_mprintf_int FORMAT INTEGER INTEGER INTEGER * * Call mprintf with three integer arguments. */ static int dbsql_mprintf_int(void* _dbctx, Tcl_Interp* interp, int argc, char* *argv) { int a[3], i; char *buf; if (argc != 5) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " FORMAT INT INT INT\"", 0); return TCL_ERROR; } for(i = 2; i < 5; i++) { if (Tcl_GetInt(interp, argv[i], &a[i-2])) return TCL_ERROR; } buf = __mprintf(argv[1], a[0], a[1], a[2]); Tcl_AppendResult(interp, buf, 0); __dbsql_free(NULL, buf); return TCL_OK; } /* * dbsql_mprintf_str -- * Usage: dbsql_mprintf_str FORMAT INTEGER INTEGER STRING * * Call mprintf with two integer arguments and one string argument. */ static int dbsql_mprintf_str(void* _dbctx, Tcl_Interp* interp, int argc, char* *argv) { int a[3], i; char *buf; COMPQUIET(notused, NULL); if (argc < 4 || argc > 5) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " FORMAT INT INT ?STRING?\"", 0); return TCL_ERROR; } for (i = 2; i < 4; i++) { if (Tcl_GetInt(interp, argv[i], &a[i-2])) return TCL_ERROR; } buf = __mprintf(argv[1], a[0], a[1], argc > 4 ? argv[4] : NULL); Tcl_AppendResult(interp, buf, 0); dbsql_free(buf); return TCL_OK; } /* * dbsql_mprintf_double -- * Usage: dbsql_mprintf_str FORMAT INTEGER INTEGER DOUBLE * * Call mprintf with two integer arguments and one double argument */ static int dbsql_mprintf_double(void* _dbctx, Tcl_Interp* interp, int argc, char* *argv) { int a[3], i; double r; char *buf; COMPQUIET(notused, NULL); if (argc != 5) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " FORMAT INT INT STRING\"", 0); return TCL_ERROR; } for (i = 2; i < 4; i++) { if (Tcl_GetInt(interp, argv[i], &a[i-2])) return TCL_ERROR; } if (Tcl_GetDouble(interp, argv[4], &r)) return TCL_ERROR; buf = __mprintf(argv[1], a[0], a[1], r); Tcl_AppendResult(interp, buf, 0); dbsql_free(buf); return TCL_OK; } #endif /* * dbsql_malloc_fail -- * Usage: dbsql_malloc_fail N * * Rig __dbsql_calloc() to fail on the N-th call. Turn off this mechanism * and reset the sqlite_malloc_failed variable is N==0. */ #ifdef MEMORY_DEBUG static int dbsql_malloc_fail(void* _dbctx, Tcl_Interp* interp, int argc, char* *argv) { int n; COMPQUIET(notused, NULL); if (argc != 2) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " N\"", 0); return TCL_ERROR; } if (Tcl_GetInt(interp, argv[1], &n)) return TCL_ERROR; sqlite_iMallocFail = n; sqlite_malloc_failed = 0; return TCL_OK; } #endif /* * dbsql_malloc_stat -- * Usage: dbsql_malloc_stat * * Return the number of prior calls to __dbsql_calloc() * and __dbsql_free(). */ #ifdef MEMORY_DEBUG static int dbsql_malloc_stat(void* _dbctx, Tcl_Interp* interp, int argc, char* *argv) { char buf[200]; COMPQUIET(notused, NULL); sprintf(buf, "%d %d %d", sqlite_nMalloc, sqlite_nFree, sqlite_iMallocFail); Tcl_AppendResult(interp, buf, 0); return TCL_OK; } #endif /* * dbsql_abort -- * Usage: dbsql_abort * * Shutdown the process immediately. This is not a clean shutdown. * This command is used to test the recoverability of a database in * the event of a program crash. */ static int dbsql_abort(void* _dbctx, Tcl_Interp* interp, int argc, char* *argv) { COMPQUIET(notused, NULL); DBSQL_ASSERT(interp == 0); /* This will always fail. */ return TCL_OK; } /* * test_func -- * The following routine is a user-defined SQL function whose purpose * is to test the dbsql_set_result_string() API. */ static void test_func(context, argc, argv) dbsql_func_t *context; int argc; const char **argv; { while(argc >= 2) { if (argv[0] == 0) { dbsql_set_result_error(context, "first argument to test function may " "not be NULL", -1); } else if (strcasecmp(argv[0], "string") == 0) { dbsql_set_result_string(context, argv[1], -1); } else if (argv[1] == 0) { dbsql_set_result_error(context, "2nd argument may not be NULL if the " "first argument is not \"string\"", -1); } else if (strcasecmp(argv[0], "int") == 0) { dbsql_set_result_int(context, atoi(argv[1])); } else if (strcasecmp(argv[0], "double") == 0) { dbsql_set_result_double(context, __dbsql_atof(argv[1])); } else { dbsql_set_result_error(context, "first argument should be one of: " "string int double", -1); } argc -= 2; argv += 2; } } /* * test_register_func -- * Usage: dbsql_register_test_function DB NAME * * Register the test SQL function on the database DB under the name NAME. */ static int test_register_func(void* _dbctx, Tcl_Interp* interp, int argc, char* *argv) { DBSQL *db; int rc; COMPQUIET(notused, NULL); if (argc != 3) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " DB FUNCTION-NAME", 0); return TCL_ERROR; } if (get_dbsql_from_ptr(interp, argv[1], &db)) return TCL_ERROR; rc = dbsql_create_function(db, argv[2], -1, test_func, 0); if (rc != 0) { Tcl_AppendResult(interp, dbsql_strerr(rc), 0); return TCL_ERROR; } return TCL_OK; } /* ** This SQLite callback records the datatype of all columns. ** ** The pArg argument is really a pointer to a TCL interpreter. The ** column names are inserted as the result of this interpreter. ** ** This routine returns non-zero which causes the query to abort. */ static int rememberDataTypes(void *pArg, int nCol, char **argv, char **colv){ int i; Tcl_Interp *interp = (Tcl_Interp*)pArg; Tcl_Obj *pList, *pElem; if( colv[nCol+1]==0 ){ return 1; } pList = Tcl_NewObj(); for(i=0; i5 ){ Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " VM NVAR VALUEVAR COLNAMEVAR", 0); return TCL_ERROR; } if( get_sqlvm_from_ptr(interp, argv[1], &vm) ) return TCL_ERROR; rc = dbsql_step(vm, argc>=3?&N:0, argc>=4?&azValue:0, argc==5?&azColName:0); if( argc>=3 ){ sprintf(zBuf, "%d", N); Tcl_SetVar(interp, argv[2], zBuf, 0); } if( argc>=4 ){ Tcl_SetVar(interp, argv[3], "", 0); if( azValue ){ for(i=0; i