Memory and Errors¶
This page covers memory ownership, error handling, and thread safety. These are critical for using libwlite correctly from any language.
Memory ownership¶
The rule¶
Objects returned by _create or _load functions belong to the caller. Borrowed objects (returned by functions that do not allocate) are owned by their parent.
Owned objects¶
These are allocated by libwlite and must be freed by the caller:
| Object | Free function | Notes |
|---|---|---|
wlite_db |
wlite_close(db) |
Closes the SQLite connection |
wlite_model |
wlite_model_free(model) |
Frees all tables, fields, indexes |
wlite_stmt |
wlite_stmt_finalize(stmt) |
Finalizes the prepared statement |
wlite_tx |
wlite_tx_free(tx) |
Frees the transaction handle |
WlPlan |
wl_plan_free(plan) |
Frees the migration plan |
wlite_error |
wlite_error_free(err) |
Frees the error object |
Borrowed objects¶
These are owned by their parent and must NOT be freed separately:
| Object | Parent | Lifetime |
|---|---|---|
wlite_table |
wlite_model |
Valid until model is freed |
wlite_field |
wlite_model |
Valid until model is freed |
wlite_index |
wlite_model |
Valid until model is freed |
| Column text pointer | wlite_stmt |
Valid until next step or finalize |
Example: correct ownership¶
wlite_model *model = NULL;
wlite_db *db = NULL;
wlite_stmt *stmt = NULL;
// Allocate
wlite_model_load_file("schema.wlite", &model); // caller owns model
wlite_open("app.db", &db); // caller owns db
wlite_prepare(db, "SELECT * FROM users", &stmt); // caller owns stmt
// Use borrowed objects
const wlite_table *table = wlite_model_table(model, "users");
const wlite_field *field = wlite_table_field(table, "name");
// Do NOT free table or field
// Free in reverse order
wlite_stmt_finalize(stmt); // free stmt
wlite_close(db); // free db
wlite_model_free(model); // free model (also frees all tables and fields)
Example: incorrect ownership (double free)¶
wlite_model *model = NULL;
wlite_model_load_file("schema.wlite", &model);
const wlite_table *table = wlite_model_table(model, "users");
// WRONG: do not free borrowed objects
// wlite_table_free(table); // BUG: double free
wlite_model_free(model); // this already frees the table
Example: use after free¶
wlite_model *model = NULL;
wlite_model_load_file("schema.wlite", &model);
const wlite_table *table = wlite_model_table(model, "users");
wlite_model_free(model);
// WRONG: table is no longer valid
// const char *name = wlite_table_name(table); // BUG: use after free
Error handling¶
Return codes¶
All libwlite functions return wlite_result. The value WLITE_OK (0) indicates success. Any other value indicates an error.
wlite_result r = wlite_open("app.db", &db);
if (r != WLITE_OK) {
fprintf(stderr, "Error: %s\n", wlite_strerror(r));
return 1;
}
Error codes¶
| Code | Value | Meaning |
|---|---|---|
WLITE_OK |
0 | Success |
WLITE_ERROR |
1 | General or unexpected error |
WLITE_INVALID_ARGUMENT |
2 | Null pointer or invalid parameter |
WLITE_OUT_OF_MEMORY |
3 | Memory allocation failed |
WLITE_IO_ERROR |
4 | I/O error (disk full, permission denied, file not found) |
WLITE_PARSE_ERROR |
5 | Schema parse error (malformed .wlite source) |
WLITE_MODEL_ERROR |
6 | Schema model error (invalid table, missing field, etc.) |
WLITE_SQLITE_ERROR |
7 | SQLite returned an error |
WLITE_CONSTRAINT_ERROR |
8 | UNIQUE, CHECK, or FOREIGN KEY constraint violation |
WLITE_NOT_FOUND |
9 | Requested table, column, or resource not found |
WLITE_BUSY |
10 | Database is locked by another connection |
WLITE_TRANSACTION_ERROR |
11 | Transaction failed or is in an invalid state |
Error messages¶
Use wlite_strerror to convert an error code to a human-readable string:
wlite_result r = wlite_open("missing.db", &db);
if (r != WLITE_OK) {
fprintf(stderr, "Error %d: %s\n", r, wlite_strerror(r));
}
Error objects¶
For detailed error information, use wl_schema_verify or wl_plan_migration which accept an wlite_error** parameter:
wlite_error *err = NULL;
WlDiff *diff = NULL;
wlite_result r = wl_schema_verify(db, expected, &diff, &err);
if (r != WLITE_OK) {
fprintf(stderr, "Verify failed: %s\n", wlite_strerror(r));
if (err) {
fprintf(stderr, "Code: %d\n", err->code);
fprintf(stderr, "Message: %s\n", err->message);
fprintf(stderr, "Subsystem: %s\n", err->subsystem);
fprintf(stderr, "Object: %s\n", err->object);
fprintf(stderr, "SQLite code: %d\n", err->sqlite_code);
fprintf(stderr, "Line: %d\n", err->line);
wlite_error_free(err);
}
}
Checking return values¶
Always check the return value of allocation functions:
wlite_model *model = NULL;
wlite_result r = wlite_model_load_file("schema.wlite", &model);
if (r != WLITE_OK) {
// model is NULL, do not use it
return 1;
}
// model is valid, use it
Do not check return values of void functions or functions that cannot fail:
// These cannot fail, no need to check
wlite_close(db);
wlite_model_free(model);
wlite_stmt_finalize(stmt);
Panic on error¶
For simple programs where any error is fatal:
#define CHECK(r) do { \
if ((r) != WLITE_OK) { \
fprintf(stderr, "Fatal: %s at %s:%d\n", wlite_strerror(r), __FILE__, __LINE__); \
abort(); \
} \
} while(0)
CHECK(wlite_open("app.db", &db));
CHECK(wlite_migrate(db, model));
Thread safety¶
Models¶
Models are immutable after loading. They can be shared across threads safely. Use wlite_model_load_file once, then pass the model pointer to any thread.
// Thread-safe: sharing a model
wlite_model *model = NULL;
wlite_model_load_file("schema.wlite", &model);
// Thread 1
wlite_db *db1 = NULL;
wlite_open("db1.db", &db1);
wlite_migrate(db1, model); // OK: model is immutable
// Thread 2
wlite_db *db2 = NULL;
wlite_open("db2.db", &db2);
wlite_migrate(db2, model); // OK: model is immutable
Database connections¶
Database connections are NOT thread-safe. Each thread must have its own connection. Do not share a wlite_db across threads.
// WRONG: sharing a connection across threads
// Thread 1: wlite_prepare(db, "SELECT ...", &stmt1);
// Thread 2: wlite_prepare(db, "INSERT ...", &stmt2); // DATA RACE
// CORRECT: each thread gets its own connection
// Thread 1: wlite_open("app.db", &db1); wlite_prepare(db1, ...);
// Thread 2: wlite_open("app.db", &db2); wlite_prepare(db2, ...);
Statements¶
Statements belong to a database connection. Do not share a wlite_stmt across threads.
Transactions¶
Transactions belong to a database connection. Do not share a wlite_tx across threads.
Summary¶
| Object | Thread-safe? | Notes |
|---|---|---|
wlite_model |
Yes | Immutable after loading |
wlite_db |
No | One connection per thread |
wlite_stmt |
No | Belongs to a connection |
wlite_tx |
No | Belongs to a connection |
wlite_table |
Yes | Borrowed from model, immutable |
wlite_field |
Yes | Borrowed from model, immutable |
Cleanup patterns¶
RAII in C¶
For C++ callers, libwlite objects can be wrapped in RAII types:
struct ModelDeleter {
void operator()(wlite_model *m) { wlite_model_free(m); }
};
std::unique_ptr<wlite_model, ModelDeleter> model;
{
wlite_model *raw = nullptr;
wlite_model_load_file("schema.wlite", &raw);
model.reset(raw);
}
// model is freed automatically when it goes out of scope
Cleanup on error¶
When multiple objects are allocated, free them in reverse order of allocation. If an error occurs mid-way, free only the objects that were successfully allocated.
wlite_model *model = NULL;
wlite_db *db = NULL;
wlite_result r = wlite_model_load_file("schema.wlite", &model);
if (r != WLITE_OK) return r;
r = wlite_open("app.db", &db);
if (r != WLITE_OK) {
wlite_model_free(model);
return r;
}
// Both allocated successfully
// ... use them ...
wlite_close(db);
wlite_model_free(model);
goto-based cleanup¶
A common C pattern uses goto for centralized cleanup. This avoids duplicating free calls in every error branch:
wlite_model *model = NULL;
wlite_db *db = NULL;
wlite_stmt *stmt = NULL;
wlite_result r;
r = wlite_model_load_file("schema.wlite", &model);
if (r != WLITE_OK) goto done;
r = wlite_open("app.db", &db);
if (r != WLITE_OK) goto done;
r = wlite_prepare(db, "SELECT * FROM users", &stmt);
if (r != WLITE_OK) goto done;
// ... use stmt ...
done:
if (stmt) wlite_stmt_finalize(stmt);
if (db) wlite_close(db);
if (model) wlite_model_free(model);
return r;
This pattern scales well when many resources must be cleaned up and keeps the error-handling path in one place.
Summary¶
libwlite follows simple ownership rules: allocated objects must be freed, borrowed objects must not. Error codes are returned by every function. Models are thread-safe, connections are not. The memory model is designed to be predictable and easy to integrate with any language's memory management.