mirror of
https://github.com/ElementsProject/lightning.git
synced 2024-11-19 18:11:28 +01:00
95f20a3978
Put an assertion inside db.c, and run every command we do (in testing) through a `check` variant. I inserted a deliberate bug (made addpsbtoutput call wallet_get_newindex() before returning when running `check`, and indeed, backtrace as expected. Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
169 lines
4.5 KiB
C
169 lines
4.5 KiB
C
#include "config.h"
|
|
#include <ccan/tal/tal.h>
|
|
#include <db/bindings.h>
|
|
#include <db/common.h>
|
|
#include <db/exec.h>
|
|
#include <db/utils.h>
|
|
|
|
/**
|
|
* db_get_version - Determine the current DB schema version
|
|
*
|
|
* Will attempt to determine the current schema version of the
|
|
* database @db by querying the `version` table. If the table does not
|
|
* exist it'll return schema version -1, so that migration 0 is
|
|
* applied, which should create the `version` table.
|
|
*/
|
|
int db_get_version(struct db *db)
|
|
{
|
|
int res = -1;
|
|
struct db_stmt *stmt = db_prepare_v2(db, SQL("SELECT version FROM version LIMIT 1"));
|
|
|
|
/*
|
|
* Tentatively execute a query, but allow failures. Some databases
|
|
* like postgres will terminate the DB transaction if there is an
|
|
* error during the execution of a query, e.g., trying to access a
|
|
* table that doesn't exist yet, so we need to terminate and restart
|
|
* the DB transaction.
|
|
*/
|
|
if (!db_query_prepared_canfail(stmt)) {
|
|
db_commit_transaction(stmt->db);
|
|
db_begin_transaction(stmt->db);
|
|
tal_free(stmt);
|
|
return res;
|
|
}
|
|
|
|
if (db_step(stmt))
|
|
res = db_col_int(stmt, "version");
|
|
|
|
tal_free(stmt);
|
|
return res;
|
|
}
|
|
|
|
u32 db_data_version_get(struct db *db)
|
|
{
|
|
struct db_stmt *stmt;
|
|
u32 version;
|
|
stmt = db_prepare_v2(db, SQL("SELECT intval FROM vars WHERE name = 'data_version'"));
|
|
/* postgres will act upset if the table doesn't exist yet. */
|
|
if (!db_query_prepared_canfail(stmt)) {
|
|
tal_free(stmt);
|
|
return 0;
|
|
}
|
|
/* This fails on uninitialized db, so "0" */
|
|
if (db_step(stmt))
|
|
version = db_col_int(stmt, "intval");
|
|
else
|
|
version = 0;
|
|
tal_free(stmt);
|
|
return version;
|
|
}
|
|
|
|
void db_set_intvar(struct db *db, const char *varname, s64 val)
|
|
{
|
|
size_t changes;
|
|
struct db_stmt *stmt = db_prepare_v2(db, SQL("UPDATE vars SET intval=? WHERE name=?;"));
|
|
db_bind_int(stmt, val);
|
|
db_bind_text(stmt, varname);
|
|
db_exec_prepared_v2(stmt);
|
|
changes = db_count_changes(stmt);
|
|
tal_free(stmt);
|
|
|
|
if (changes == 0) {
|
|
stmt = db_prepare_v2(db, SQL("INSERT INTO vars (name, intval) VALUES (?, ?);"));
|
|
db_bind_text(stmt, varname);
|
|
db_bind_int(stmt, val);
|
|
db_exec_prepared_v2(stmt);
|
|
tal_free(stmt);
|
|
}
|
|
}
|
|
|
|
s64 db_get_intvar(struct db *db, const char *varname, s64 defval)
|
|
{
|
|
s64 res = defval;
|
|
struct db_stmt *stmt = db_prepare_v2(
|
|
db, SQL("SELECT intval FROM vars WHERE name= ? LIMIT 1"));
|
|
db_bind_text(stmt, varname);
|
|
if (db_query_prepared_canfail(stmt) && db_step(stmt))
|
|
res = db_col_int(stmt, "intval");
|
|
|
|
tal_free(stmt);
|
|
return res;
|
|
}
|
|
|
|
/* Leak tracking. */
|
|
|
|
/* By making the update conditional on the current value we expect we
|
|
* are implementing an optimistic lock: if the update results in
|
|
* changes on the DB we know that the data_version did not change
|
|
* under our feet and no other transaction ran in the meantime.
|
|
*
|
|
* Notice that this update effectively locks the row, so that other
|
|
* operations attempting to change this outside the transaction will
|
|
* wait for this transaction to complete. The external change will
|
|
* ultimately fail the changes test below, it'll just delay its abort
|
|
* until our transaction is committed.
|
|
*/
|
|
static void db_data_version_incr(struct db *db)
|
|
{
|
|
struct db_stmt *stmt = db_prepare_v2(
|
|
db, SQL("UPDATE vars "
|
|
"SET intval = intval + 1 "
|
|
"WHERE name = 'data_version'"
|
|
" AND intval = ?"));
|
|
db_bind_int(stmt, db->data_version);
|
|
db_exec_prepared_v2(stmt);
|
|
if (db_count_changes(stmt) != 1)
|
|
db_fatal(stmt->db, "Optimistic lock on the database failed. There"
|
|
" may be a concurrent access to the database."
|
|
" Aborting since concurrent access is unsafe.");
|
|
tal_free(stmt);
|
|
db->data_version++;
|
|
}
|
|
|
|
void db_begin_transaction_(struct db *db, const char *location)
|
|
{
|
|
bool ok;
|
|
if (db->in_transaction)
|
|
db_fatal(db, "Already in transaction from %s", db->in_transaction);
|
|
|
|
/* No writes yet. */
|
|
db->dirty = false;
|
|
|
|
db_prepare_for_changes(db);
|
|
ok = db->config->begin_tx_fn(db);
|
|
if (!ok)
|
|
db_fatal(db, "Failed to start DB transaction: %s", db->error);
|
|
|
|
db->in_transaction = location;
|
|
}
|
|
|
|
bool db_in_transaction(struct db *db)
|
|
{
|
|
return db->in_transaction;
|
|
}
|
|
|
|
void db_set_readonly(struct db *db, bool readonly)
|
|
{
|
|
db->readonly = readonly;
|
|
}
|
|
|
|
void db_commit_transaction(struct db *db)
|
|
{
|
|
bool ok;
|
|
assert(db->in_transaction);
|
|
db_assert_no_outstanding_statements(db);
|
|
|
|
/* Increment before reporting changes to an eventual plugin. */
|
|
if (db->dirty)
|
|
db_data_version_incr(db);
|
|
|
|
db_report_changes(db, NULL, 0);
|
|
ok = db->config->commit_tx_fn(db);
|
|
|
|
if (!ok)
|
|
db_fatal(db, "Failed to commit DB transaction: %s", db->error);
|
|
|
|
db->in_transaction = NULL;
|
|
db->dirty = false;
|
|
}
|