wcdb icon indicating copy to clipboard operation
wcdb copied to clipboard

错误处理

Open 0x1306a94 opened this issue 2 years ago • 6 comments

The language of WCDB

Cpp

The version of WCDB

v2.0.4

The platform of WCDB

macOS

The installation of WCDB

Git clone

What's the issue?

  • 通过 database.prepareInsert 执行数据插入,发生错误时比如非空字段插入了空,通过 database.getError() 或者 insert.getError() 拿到的错误都是OK
  • 示例代码
// message_schema_hpp
#include <WCDBCpp/WCDBCpp.h>

#include <string>

struct MessageSchema {
    MessageSchema() {}
    ~MessageSchema() {}

    std::uint64_t messageId;
    std::string remoteMessageId;
    std::uint64_t timestamp;
    std::uint64_t deliveredTimestamp;
    std::uint64_t readTimestamp;
    std::string conversationId;
    std::int8_t conversationType;
    std::string senderUid;
    std::int8_t status;
    std::int8_t isSelf;
    std::int16_t contentType;
    WCDB::Data contentData{WCDB::Data::null()};
    WCDB::Data localCustomData{WCDB::Data::null()};
    std::int32_t localCustomInt;
    WCDB::Data cloudCustomData{WCDB::Data::null()};
    std::string searchable;

    constexpr static const char tableName[] = "t_message";
    WCDB_CPP_ORM_DECLARATION(MessageSchema);
};

// message_schema.cpp
#include "message_schema.hpp"

WCDB_CPP_ORM_IMPLEMENTATION_BEGIN(MessageSchema)

WCDB_CPP_SYNTHESIZE_COLUMN(messageId, "message_id")
WCDB_CPP_SYNTHESIZE_COLUMN(remoteMessageId, "remote_message_id")
WCDB_CPP_SYNTHESIZE(timestamp)
WCDB_CPP_SYNTHESIZE_COLUMN(deliveredTimestamp, "delivered_timestamp")
WCDB_CPP_SYNTHESIZE_COLUMN(readTimestamp, "read_timestamp")
WCDB_CPP_SYNTHESIZE_COLUMN(conversationId, "conversation_id")
WCDB_CPP_SYNTHESIZE_COLUMN(conversationType, "conversation_type")
WCDB_CPP_SYNTHESIZE_COLUMN(senderUid, "sender_uid")
WCDB_CPP_SYNTHESIZE(status)
WCDB_CPP_SYNTHESIZE_COLUMN(isSelf, "is_self")
WCDB_CPP_SYNTHESIZE_COLUMN(contentType, "content_type")
WCDB_CPP_SYNTHESIZE_COLUMN(contentData, "content_data")
WCDB_CPP_SYNTHESIZE_COLUMN(localCustomData, "local_custom_data")
WCDB_CPP_SYNTHESIZE_COLUMN(localCustomInt, "local_custom_int")
WCDB_CPP_SYNTHESIZE_COLUMN(cloudCustomData, "cloud_custom_data")
WCDB_CPP_SYNTHESIZE(searchable)

WCDB_CPP_DEFAULT(deliveredTimestamp, 0);
WCDB_CPP_DEFAULT(readTimestamp, 0);

WCDB_CPP_PRIMARY_ASC_AUTO_INCREMENT(messageId)

WCDB_CPP_NOT_NULL(timestamp)
WCDB_CPP_NOT_NULL(conversationId)
WCDB_CPP_NOT_NULL(conversationType)
WCDB_CPP_NOT_NULL(senderUid)
WCDB_CPP_NOT_NULL(status)

// 组合唯一索引
WCDB_CPP_UNIQUE_INDEX("_uniqueIndex", conversationType)
WCDB_CPP_UNIQUE_INDEX("_uniqueIndex", conversationId)
WCDB_CPP_UNIQUE_INDEX("_uniqueIndex", timestamp)
WCDB_CPP_UNIQUE_INDEX("_uniqueIndex", senderUid)
WCDB_CPP_UNIQUE_INDEX("_uniqueIndex", remoteMessageId)

WCDB_CPP_INDEX("_timestampIndex", timestamp)
WCDB_CPP_INDEX("_remoteMessageIdIndex", remoteMessageId)
WCDB_CPP_INDEX("_senderUidIndex", senderUid)
WCDB_CPP_INDEX("_statusIndex", status)

WCDB_CPP_ORM_IMPLEMENTATION_END

// main.cpp
#include <chrono>
#include <iostream>

#include "message_schema.hpp"

std::uint64_t now_ms() {
    auto duration = std::chrono::system_clock::now().time_since_epoch();
    return static_cast<std::uint64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(duration).count());
}

int main(int argc, const char *argv[]) {

    std::string db_path = DB_FILE_PATH;
    WCDB::Database database(db_path);
    database.traceSQL([](long tag,
                         const WCDB::UnsafeStringView &path,
                         const WCDB::UnsafeStringView &sql,
                         const void *handleIdentifier) {
        std::cerr << "sql: " << sql.data() << std::endl;
    });

    thread_local WCDB::Error lastError;
    database.traceError([&](const WCDB::Error &error) {
        lastError = error;
        std::cerr << "error: " << error.getDescription().data() << std::endl;
    });

    database.createTable<MessageSchema>(MessageSchema::tableName);

    MessageSchema object;
    object.isAutoIncrement = true;
    object.conversationType = 1;
    object.conversationId = "1231313";
    object.timestamp = 1697776099925;  //now_ms();
    object.senderUid = "41050";
    object.remoteMessageId = "jfhafjakfjafk";


    // 这种方式获取的Error 是OK
    auto insert = database.prepareInsert<MessageSchema>()
                      .intoTable(MessageSchema::tableName)
                      .onFields({WCDB_FIELD(MessageSchema::conversationType), WCDB_FIELD(MessageSchema::conversationId), WCDB_FIELD(MessageSchema::timestamp), WCDB_FIELD(MessageSchema::senderUid), WCDB_FIELD(MessageSchema::remoteMessageId)})
                      .values(object);
    bool res = insert.execute();
    auto error = database.getError();

    // 这种方式获取的Error 是符合预期的
//    auto insert = WCDB::StatementInsert().insertIntoTable(MessageSchema::tableName).columns({WCDB_FIELD(MessageSchema::conversationType), WCDB_FIELD(MessageSchema::conversationId), WCDB_FIELD(MessageSchema::timestamp), WCDB_FIELD(MessageSchema::senderUid), WCDB_FIELD(MessageSchema::remoteMessageId)}).values({object.conversationType, object.conversationId, object.timestamp, object.senderUid, object.remoteMessageId});
//    auto res = database.execute(insert);
//    auto error = database.getError();

    std::cerr << "error code: " << (int)error.code() << " ext_code: " << (int)error.getExtCode() << std::endl;
    return 0;
}

0x1306a94 avatar Oct 20 '23 06:10 0x1306a94

Get

getError is actually quite cumbersome to use. We recommend using static Database::globalTraceError() or Database::traceError()

Qiuwen-chen avatar Oct 24 '23 01:10 Qiuwen-chen

@Qiuwen-chen I think the design of static Database::globalTraceError() or Database::traceError() is more geared towards error log statistics. The actual business scenario requires different processing based on the current specific error type. For example, when inserting non-null constraint data, the user will be clearly informed of a more specific error instead of simply telling the user that the insertion failed.

0x1306a94 avatar Oct 24 '23 02:10 0x1306a94

traceError is called back synchronously when an error occurs.

You can output the error returned by traceError to a log file or console, and then output data-related logs at the location where the API is called. Then you can easily associate the error with the operation.

This is how we use it now.

Qiuwen-chen avatar Oct 24 '23 02:10 Qiuwen-chen

In this way, you won't miss any database errors.

Qiuwen-chen avatar Oct 24 '23 02:10 Qiuwen-chen

@Qiuwen-chen

I understand what you mean, and this is really convenient for logging errors. But as I said above, in some scenarios it is inconvenient to get the specific error information of the current execution. At this time, it is very convenient to use getError, and the corresponding code is also more readable. I read the source code. The callback of Database::traceError() is executed before the current call returns. This way, the error can be captured in the callback and then used after the call returns. However, this code is very bad.

0x1306a94 avatar Oct 24 '23 03:10 0x1306a94

In most cases, you cannot handle errors at runtime. If you are expecting a not null constraint conflict, you can directly detect whether the value is null before the Insert operation, instead of handling it through a database error, which is less efficient.

The error mechanism is mainly used to deal with unexpected situations.

Qiuwen-chen avatar Oct 24 '23 03:10 Qiuwen-chen

Fixed in the latest version.

Qiuwen-chen avatar Mar 08 '24 15:03 Qiuwen-chen