SQLite Forum

Error message from table-valued function xConnect is ignored
Login

Error message from table-valued function xConnect is ignored

(1) By David Matson (dmatson) on 2021-07-08 01:08:44 [source]

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":

  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

#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;
}

(2) By Dan Kennedy (dan) on 2021-07-09 10:55:19 in reply to 1 [link] [source]

Thanks for reporting this. Should now be fixed here:

https://sqlite.org/src/info/bbbbeb59a6a14b94

Dan.

(3) By Max (Maxulite) on 2021-07-09 11:49:10 in reply to 2 [link] [source]

It looks like it's the same issue as I reported recently (https://sqlite.org/forum/forumpost/db63efd0d1). At least now when switching from 3.36.00 to the check-in version I see my message instead of "no such table ...". Thanks!