Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions src/abi/ace_exports.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5939,6 +5939,15 @@ UNSIGNED32 AdsAppendRecord(ADSHANDLE hTable) {
if (!r) return fail(r.error());
return ok();
}
#if defined(OPENADS_WITH_SQLITE)
if (auto* st = get_sqlite_table(hTable)) {
if (st->conn == nullptr)
return fail(openads::AE_INVALID_CONNECTION_HANDLE, "");
auto r = st->conn->append_blank(st);
if (!r) return fail(r.error());
return ok();
}
#endif
#if defined(OPENADS_WITH_FIREBIRD)
if (auto* ft = get_firebird_table(hTable)) {
if (ft->conn == nullptr)
Expand Down Expand Up @@ -6002,6 +6011,15 @@ UNSIGNED32 AdsWriteRecord(ADSHANDLE hTable) {
if (!r) return fail(r.error());
return ok();
}
#if defined(OPENADS_WITH_SQLITE)
if (auto* st = get_sqlite_table(hTable)) {
if (st->conn == nullptr)
return fail(openads::AE_INVALID_CONNECTION_HANDLE, "");
auto r = st->conn->flush_record(st);
if (!r) return fail(r.error());
return ok();
}
#endif
#if defined(OPENADS_WITH_FIREBIRD)
if (auto* ft = get_firebird_table(hTable)) {
if (ft->conn == nullptr)
Expand Down Expand Up @@ -6102,6 +6120,15 @@ UNSIGNED32 AdsDeleteRecord(ADSHANDLE hTable) {
if (!r) return fail(r.error());
return ok();
}
#if defined(OPENADS_WITH_SQLITE)
if (auto* st = get_sqlite_table(hTable)) {
if (st->conn == nullptr)
return fail(openads::AE_INVALID_CONNECTION_HANDLE, "");
auto r = st->conn->delete_record(st);
if (!r) return fail(r.error());
return ok();
}
#endif
#if defined(OPENADS_WITH_FIREBIRD)
if (auto* ft = get_firebird_table(hTable)) {
if (ft->conn == nullptr)
Expand Down Expand Up @@ -6251,6 +6278,20 @@ UNSIGNED32 AdsSetString(ADSHANDLE hTable, UNSIGNED8* pucField,
if (!r) return fail(r.error());
return ok();
}
#if defined(OPENADS_WITH_SQLITE)
if (auto* st = get_sqlite_table(hTable)) {
if (pucField == nullptr) return fail(openads::AE_INTERNAL_ERROR, "");
if (st->conn == nullptr)
return fail(openads::AE_INVALID_CONNECTION_HANDLE, "");
std::string fname(reinterpret_cast<const char*>(pucField));
std::string val;
if (pucValue != nullptr && ulLen > 0)
val.assign(reinterpret_cast<const char*>(pucValue), ulLen);
auto r = st->conn->set_field(st, fname, val);
if (!r) return fail(r.error());
return ok();
}
#endif
#if defined(OPENADS_WITH_FIREBIRD)
if (auto* ft = get_firebird_table(hTable)) {
if (pucField == nullptr) return fail(openads::AE_INTERNAL_ERROR, "");
Expand Down
185 changes: 185 additions & 0 deletions src/sql_backend/sqlite_connection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -650,4 +650,189 @@ SqliteConnection::run_sql(const std::string& sql) {
#endif
}

#if defined(OPENADS_WITH_SQLITE)
namespace {
std::string quote_ident_sqlite(const std::string& name) {
std::string out = "\"";
for (char c : name) { if (c == '"') out += '"'; out += c; }
out += '"';
return out;
}
} // namespace
#endif

util::Result<void> SqliteConnection::append_blank(SqliteTable* tbl) {
#if defined(OPENADS_WITH_SQLITE)
if (!valid() || tbl == nullptr) {
return util::Error{5001, 0, "invalid sqlite append", ""};
}
if (!tbl->fields_cached) {
if (auto d = describe_table_impl(impl_->db, tbl); !d) return d.error();
}
tbl->staging_row.assign(tbl->fields.size(), std::string{});
tbl->staging_nulls.assign(tbl->fields.size(), true);
tbl->pending_append = true;
tbl->row_dirty = true;
tbl->row_valid = true;
tbl->positioned = true;
return util::Result<void>{};
#else
(void)tbl;
return util::Error{5004, 0, "sqlite backend disabled", ""};
#endif
}

util::Result<void> SqliteConnection::set_field(
SqliteTable* tbl, const std::string& field_name, const std::string& value) {
#if defined(OPENADS_WITH_SQLITE)
if (!valid() || tbl == nullptr) {
return util::Error{5001, 0, "invalid sqlite set_field", ""};
}
if (!tbl->row_valid && !tbl->pending_append) {
return util::Error{5026, 0, "no current record", ""};
}
if (!tbl->fields_cached) return util::Error{5001, 0, "schema not cached", ""};
const std::size_t idx = field_index_ci(*tbl, field_name);
if (idx == static_cast<std::size_t>(-1)) {
return util::Error{5063, 0, "column not found", field_name};
}
if (!tbl->row_dirty && !tbl->pending_append) {
tbl->staging_row = tbl->current_row;
tbl->staging_nulls = tbl->current_nulls;
}
if (tbl->staging_row.size() < tbl->fields.size()) {
tbl->staging_row.resize(tbl->fields.size());
tbl->staging_nulls.resize(tbl->fields.size(), true);
}
tbl->staging_row[idx] = value;
tbl->staging_nulls[idx] = false;
tbl->row_dirty = true;
return util::Result<void>{};
#else
(void)tbl; (void)field_name; (void)value;
return util::Error{5004, 0, "sqlite backend disabled", ""};
#endif
}

util::Result<void> SqliteConnection::flush_record(SqliteTable* tbl) {
#if defined(OPENADS_WITH_SQLITE)
if (!valid() || tbl == nullptr) {
return util::Error{5001, 0, "invalid sqlite flush", ""};
}
if (!tbl->row_dirty && !tbl->pending_append) return util::Result<void>{};
if (!tbl->fields_cached) return util::Error{5001, 0, "schema not cached", ""};
sqlite3* db = impl_->db;

if (tbl->pending_append) {
std::string cols, marks;
std::vector<std::size_t> bound;
for (std::size_t i = 0; i < tbl->fields.size(); ++i) {
if (i < tbl->staging_nulls.size() && tbl->staging_nulls[i]) continue;
if (!bound.empty()) { cols += ", "; marks += ", "; }
cols += quote_ident_sqlite(tbl->fields[i].name);
marks += "?";
bound.push_back(i);
}
if (bound.empty()) {
return util::Error{5001, 0, "insert has no columns", tbl->name};
}
const std::string sql = "INSERT INTO " + quote_ident_sqlite(tbl->name) +
" (" + cols + ") VALUES (" + marks + ")";
sqlite3_stmt* stmt = nullptr;
if (sqlite3_prepare_v2(db, sql.c_str(), static_cast<int>(sql.size()),
&stmt, nullptr) != SQLITE_OK) {
return sqlite_error(db, "prepare insert");
}
for (std::size_t k = 0; k < bound.size(); ++k) {
const std::string& v = tbl->staging_row[bound[k]];
sqlite3_bind_text(stmt, static_cast<int>(k + 1), v.c_str(),
static_cast<int>(v.size()), SQLITE_TRANSIENT);
}
const int rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
if (rc != SQLITE_DONE) return sqlite_error(db, "exec insert");

const std::int64_t new_rowid = sqlite3_last_insert_rowid(db);
tbl->pending_append = false;
tbl->row_dirty = false;
if (auto r = load_rowids(db, tbl); !r) return r.error();
return position_at_rowid(db, tbl, new_rowid);
}

// UPDATE the current row, keyed by its rowid.
if (!tbl->positioned || tbl->pos >= tbl->rowids.size()) {
return util::Error{5026, 0, "no current record", ""};
}
const std::int64_t rowid = tbl->rowids[tbl->pos];
std::string set_clause;
std::vector<std::size_t> bound;
for (std::size_t i = 0; i < tbl->fields.size(); ++i) {
if (i >= tbl->staging_row.size()) continue;
if (!bound.empty()) set_clause += ", ";
set_clause += quote_ident_sqlite(tbl->fields[i].name) + " = ?";
bound.push_back(i);
}
if (bound.empty()) { tbl->row_dirty = false; return util::Result<void>{}; }
const std::string sql = "UPDATE " + quote_ident_sqlite(tbl->name) +
" SET " + set_clause + " WHERE rowid = ?";
sqlite3_stmt* stmt = nullptr;
if (sqlite3_prepare_v2(db, sql.c_str(), static_cast<int>(sql.size()),
&stmt, nullptr) != SQLITE_OK) {
return sqlite_error(db, "prepare update");
}
int p = 1;
for (std::size_t i : bound) {
if (i < tbl->staging_nulls.size() && tbl->staging_nulls[i]) {
sqlite3_bind_null(stmt, p++);
} else {
const std::string& v = tbl->staging_row[i];
sqlite3_bind_text(stmt, p++, v.c_str(),
static_cast<int>(v.size()), SQLITE_TRANSIENT);
}
}
sqlite3_bind_int64(stmt, p, rowid);
const int rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
if (rc != SQLITE_DONE) return sqlite_error(db, "exec update");

tbl->row_dirty = false;
return position_at_rowid(db, tbl, rowid);
#else
(void)tbl;
return util::Error{5004, 0, "sqlite backend disabled", ""};
#endif
}

util::Result<void> SqliteConnection::delete_record(SqliteTable* tbl) {
#if defined(OPENADS_WITH_SQLITE)
if (!valid() || tbl == nullptr) {
return util::Error{5001, 0, "invalid sqlite delete", ""};
}
if (tbl->pending_append || !tbl->positioned ||
tbl->pos >= tbl->rowids.size()) {
return util::Error{5026, 0, "no current record", ""};
}
sqlite3* db = impl_->db;
const std::int64_t rowid = tbl->rowids[tbl->pos];
const std::string sql = "DELETE FROM " + quote_ident_sqlite(tbl->name) +
" WHERE rowid = ?";
sqlite3_stmt* stmt = nullptr;
if (sqlite3_prepare_v2(db, sql.c_str(), static_cast<int>(sql.size()),
&stmt, nullptr) != SQLITE_OK) {
return sqlite_error(db, "prepare delete");
}
sqlite3_bind_int64(stmt, 1, rowid);
const int rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
if (rc != SQLITE_DONE) return sqlite_error(db, "exec delete");

tbl->row_dirty = false;
tbl->pending_append = false;
return load_rowids(db, tbl);
Comment on lines +829 to +831

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

After deleting the current record, the cursor state is left in an inconsistent and potentially dangerous state:

  1. Stale Data Read: tbl->row_valid remains true and tbl->current_row is not cleared, meaning subsequent read operations will return stale data from the deleted record.
  2. Out-of-Bounds Access: Since the record is deleted, the list of rowids returned by load_rowids will shrink by 1. If the deleted record was the last record in the table, tbl->pos will now be equal to tbl->rowids.size(). Because tbl->row_valid is still true, any subsequent operation attempting to access tbl->rowids[tbl->pos] will result in an out-of-bounds access and potential crash.

Setting tbl->row_valid = false and checking if tbl->pos is out of bounds to clear tbl->positioned resolves these issues.

    tbl->row_dirty      = false;
    tbl->pending_append = false;
    tbl->row_valid      = false;
    if (auto r = load_rowids(db, tbl); !r) return r.error();
    if (tbl->pos >= tbl->rowids.size()) {
        tbl->positioned = false;
    }
    return util::Result<void>{};

#else
(void)tbl;
return util::Error{5004, 0, "sqlite backend disabled", ""};
#endif
}

} // namespace openads::sql_backend
11 changes: 11 additions & 0 deletions src/sql_backend/sqlite_connection.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,17 @@ class SqliteConnection {
util::Result<void> goto_bottom(SqliteTable* tbl);
util::Result<void> skip(SqliteTable* tbl, std::int32_t step);

// Navigational write (mirrors MariaConnection/FirebirdConnection):
// append_blank stages a blank row, set_field stages one column,
// flush_record emits an INSERT (pending_append) or a rowid-keyed UPDATE,
// delete_record a rowid-keyed DELETE. SQLite rowid is the implicit key.
util::Result<void> append_blank(SqliteTable* tbl);
util::Result<void> set_field(SqliteTable* tbl,
const std::string& field_name,
const std::string& value);
util::Result<void> flush_record(SqliteTable* tbl);
util::Result<void> delete_record(SqliteTable* tbl);

// Tier-2 push-down: install (where non-empty) or clear (where empty) a SQL
// WHERE fragment and reload the rowid list so navigation walks only the
// matching rows. `where` must be a trusted, already-translated SQL boolean
Expand Down
8 changes: 8 additions & 0 deletions src/sql_backend/sqlite_table.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@ struct SqliteTable {

bool last_seek_found = false;

// Write staging (mirrors MariaTable/FirebirdTable): append_blank/set_field
// stage column values; flush_record emits an INSERT (pending_append) or a
// rowid-keyed UPDATE; delete_record a rowid-keyed DELETE.
std::vector<std::string> staging_row;
std::vector<bool> staging_nulls;
bool pending_append = false;
bool row_dirty = false;

// Result-set cursor mode (AdsExecuteSQLDirect SELECT passthrough): rows are
// materialized in memory instead of fetched per-rowid from a base table, so
// navigation serves `current_row` straight from `result_rows[pos]`.
Expand Down
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ add_executable(openads_unit_tests
unit/abi_aggregate_sqlite_test.cpp
unit/abi_plus_sqlite_seek_test.cpp
unit/abi_plus_sqlite_passthrough_test.cpp
unit/abi_plus_sqlite_write_test.cpp
unit/abi_plus_sqlcipher_read_test.cpp
unit/lsn_map_test.cpp
unit/index_expr_test.cpp
Expand Down
Loading
Loading