When developing a table-valued function, I've tried to provide a custom error from xConnect when the table can't be created for some reason (such as due to a programmer syntax error in the SQL passed to sqlite3_declare_vtab). xConnect has an explicit char** pzErr parameter, but it is ignored in this case. Specifically, vtabCallConstructor copies this error message and returns it up to sqlite3VtabEponymousTableInit, which again copies in correctly. But when it returns to sqlite3LocateTable, that function overwrites the custom error message with "no such table": ```c if( p==0 ){ const char *zMsg = flags & LOCATE_VIEW ? "no such view" : "no such table"; if( zDbase ){ sqlite3ErrorMsg(pParse, "%s: %s.%s", zMsg, zDbase, zName); }else{ sqlite3ErrorMsg(pParse, "%s: %s", zMsg, zName); } ``` ... Could SQLite preserve the error message in this case? Below is source with a repro. Thanks, David ```c #include <assert.h> #include <stdio.h> #include <string.h> #include "sqlite3.h" static int testVTabConnect(sqlite3* connection, void* pAux, int argc, const char* const* argv, sqlite3_vtab** ppVtab, char** pzErr) { (void)pAux; (void)argc; (void)argv; sqlite3_vtab* table = (sqlite3_vtab*)sqlite3_malloc(sizeof(*table)); if (table == NULL) { return SQLITE_NOMEM; } memset(table, 0, sizeof(*table)); int rc = sqlite3_declare_vtab(connection, "CREATE TABLE x(Value INTEGER PRIMARY KEY) BAD SYNTAX HERE;"); if (rc != SQLITE_OK) { sqlite3_free(table); const char* connectionError = sqlite3_errmsg(connection); size_t errorLength = strlen(connectionError); char* errorMessageCopy = (char*)sqlite3_malloc64((sqlite3_uint64)errorLength + 1); if (errorMessageCopy == NULL) { return SQLITE_NOMEM; } strncpy_s(errorMessageCopy, errorLength + 1, connectionError, errorLength); *pzErr = errorMessageCopy; return rc; } *ppVtab = table; return SQLITE_OK; } static int testVTabDisconnect(sqlite3_vtab* pVtab) { sqlite3_free(pVtab); return SQLITE_OK; } static int testVTabBestIndex(sqlite3_vtab* tab, sqlite3_index_info* pIdxInfo) { (void)tab; pIdxInfo->idxNum = 0; pIdxInfo->idxStr = NULL; pIdxInfo->estimatedCost = 1; pIdxInfo->estimatedRows = 1; return SQLITE_OK; } struct test_vtab_cursor { sqlite3_vtab_cursor base; // This base struct must come first. sqlite_int64 rowid; }; typedef struct test_vtab_cursor test_vtab_cursor; static int testVTabOpen(sqlite3_vtab* p, sqlite3_vtab_cursor** ppCursor) { (void)p; test_vtab_cursor* cursor = (test_vtab_cursor*)sqlite3_malloc(sizeof(*cursor)); if (cursor == NULL) { return SQLITE_NOMEM; } memset(cursor, 0, sizeof(*cursor)); *ppCursor = &cursor->base; return SQLITE_OK; } static int testVTabClose(sqlite3_vtab_cursor* cur) { test_vtab_cursor* cursor = (test_vtab_cursor*)cur; sqlite3_free(cursor); return SQLITE_OK; } static int testVTabFilter(sqlite3_vtab_cursor* pVtabCursor, int idxNum, const char* idxStr, int argc, sqlite3_value** argv) { assert(idxNum == 0); (void)idxNum; assert(idxStr == NULL); (void)idxStr; assert(argc == 0); (void)argc; (void)argv; test_vtab_cursor* cursor = (test_vtab_cursor*)pVtabCursor; cursor->rowid = 0; return SQLITE_OK; } static int testVTabNext(sqlite3_vtab_cursor* cur) { test_vtab_cursor* cursor = (test_vtab_cursor*)cur; ++(cursor->rowid); return SQLITE_OK; } static int testVTabEof(sqlite3_vtab_cursor* cur) { test_vtab_cursor* cursor = (test_vtab_cursor*)cur; return cursor->rowid < 1 ? 0 : 1; } static int testVTabColumn(sqlite3_vtab_cursor* cur, sqlite3_context* ctx, int i) { assert(i == 0); (void)i; test_vtab_cursor* cursor = (test_vtab_cursor*)cur; sqlite3_result_int64(ctx, cursor->rowid); return SQLITE_OK; } static int testVTabRowid(sqlite3_vtab_cursor* cur, sqlite_int64* pRowid) { test_vtab_cursor* cursor = (test_vtab_cursor*)cur; *pRowid = cursor->rowid; return SQLITE_OK; } static sqlite3_module testVTabModule = { 3, // iVersion NULL, // xCreate testVTabConnect, // xConnect testVTabBestIndex, // xBestIndex testVTabDisconnect, //xDisconnect NULL, // xDestroy testVTabOpen, //int xOpen testVTabClose, //int xClose testVTabFilter, //int xFilter testVTabNext, // int xNext testVTabEof, // int xEof testVTabColumn, // xColumn testVTabRowid, // xRowid NULL, // xUpdate NULL, // xBegin NULL, // xSync NULL, // xCommit NULL, // xRollback NULL, // xFindFunction NULL, // xRename NULL, // xSavepoint NULL, // xRelease NULL, // xRollbackTo NULL, // xShadowName }; int main() { int rc = 0; int rcCleanup = 0; int rcCurrentCleanup = 0; sqlite3* db = NULL; sqlite3_stmt* stmt = NULL; rc = sqlite3_open("D:\\test.db", &db); if (rc) { goto cleanup; } rc = sqlite3_create_module_v2(db, "testvtab", &testVTabModule, NULL, NULL); if (rc) { goto cleanup; } rc = sqlite3_prepare_v2(db, "SELECT count(*) FROM testvtab();", -1, &stmt, NULL); if (rc) { goto cleanup; } rc = sqlite3_step(stmt); if (rc != SQLITE_ROW) { goto cleanup; } printf("%i\n", sqlite3_column_int(stmt, 0)); rc = sqlite3_step(stmt); if (rc != SQLITE_DONE) { goto cleanup; } rc = SQLITE_OK; cleanup: if (rc && db) { fprintf(stderr, "%s\n", sqlite3_errmsg(db)); } if (stmt) { rcCurrentCleanup = sqlite3_finalize(stmt); if (rcCurrentCleanup && !rcCleanup) { rcCleanup = rcCurrentCleanup; } } if (db) { rcCurrentCleanup = sqlite3_close_v2(db); if (rcCurrentCleanup && !rcCleanup) { rcCleanup = rcCurrentCleanup; } } if (rcCleanup) { return rcCleanup; } return rc; } ```