geary/src/engine/db/db-statement.vala
Michael Gratton 230127237a Add Db.Statement.get_expanded_sql
Useful for debugging.
2019-08-06 21:42:19 +10:00

278 lines
8.7 KiB
Vala

/* Copyright 2016 Software Freedom Conservancy Inc.
*
* This software is licensed under the GNU Lesser General Public License
* (version 2.1 or later). See the COPYING file in this distribution.
*/
private extern string? sqlite3_expanded_sql(Sqlite.Statement stmt);
public class Geary.Db.Statement : Geary.Db.Context {
public string sql {
get { return this.stmt.sql(); }
}
public Connection connection { get; private set; }
internal Sqlite.Statement stmt;
private Gee.HashMap<string, int>? column_map = null;
/**
* Fired when the Statement is executed the first time (after creation or after a reset).
*/
public signal void executed();
/**
* Fired when the Statement is reset.
*/
public signal void resetted();
/**
* Fired when the Statement's bindings are cleared.
*/
public signal void bindings_cleared();
private Gee.HashSet<Memory.Buffer> held_buffers = new Gee.HashSet<Memory.Buffer>();
internal Statement(Connection connection, string sql) throws DatabaseError {
this.connection = connection;
throw_on_error("Statement.ctor", connection.db.prepare_v2(sql, -1, out stmt, null), sql);
}
/** Returns SQL for the statement with bound parameters expanded. */
public string? get_expanded_sql() {
// Replace all this with `Sqlite.Statement.expanded_sql` is
// readily available. See:
// https://gitlab.gnome.org/GNOME/vala/merge_requests/74
string* sqlite = sqlite3_expanded_sql(this.stmt);
string? sql = sqlite;
Sqlite.Memory.free((void*) sqlite);
return sql;
}
/**
* Reset the Statement for reuse, optionally clearing all bindings as well. If bindings are
* not cleared, valued bound previously will be maintained.
*
* See http://www.sqlite.org/c3ref/reset.html and http://www.sqlite.org/c3ref/clear_bindings.html
*/
public Statement reset(ResetScope reset_scope) throws DatabaseError {
if (reset_scope == ResetScope.CLEAR_BINDINGS)
throw_on_error("Statement.clear_bindings", stmt.clear_bindings());
throw_on_error("Statement.reset", stmt.reset());
// fire signals after Statement has been altered -- this prevents reentrancy while the
// Statement is in a halfway state
if (reset_scope == ResetScope.CLEAR_BINDINGS)
bindings_cleared();
resetted();
return this;
}
/**
* Returns the number of columns the Statement will return in a Result.
*/
public int get_column_count() {
return stmt.column_count();
}
/**
* Returns the column name for column at the zero-based index.
*
* The name may be used with Result.int_for() (and other *_for() variants).
*/
public unowned string? get_column_name(int index) {
return stmt.column_name(index);
}
/**
* Returns the zero-based column index matching the column name. Column names are
* case-insensitive.
*
* Returns -1 if column name is unknown.
*/
public int get_column_index(string name) {
// prepare column map only if names requested
if (column_map == null) {
column_map = new Gee.HashMap<string, int>(Geary.String.stri_hash, Geary.String.stri_equal);
int cols = stmt.column_count();
for (int ctr = 0; ctr < cols; ctr++) {
string? column_name = stmt.column_name(ctr);
if (!String.is_empty(column_name))
column_map.set(column_name, ctr);
}
}
return column_map.has_key(name) ? column_map.get(name) : -1;
}
/**
* Executes the Statement and returns a Result object. The Result starts pointing at the first
* row in the result set. If empty, Result.finished will be true.
*/
public Result exec(Cancellable? cancellable = null) throws Error {
Result results = new Result(this, cancellable);
executed();
return results;
}
/**
* Executes the Statement and returns the last inserted rowid. If this Statement is not
* an INSERT, it will return the rowid of the last prior INSERT.
*
* See Connection.last_insert_rowid.
*/
public int64 exec_insert(Cancellable? cancellable = null) throws Error {
new Result(this, cancellable);
int64 rowid = connection.last_insert_rowid;
// fire signal after safely retrieving the rowid
executed();
return rowid;
}
/**
* Executes the Statement and returns the number of rows modified by the operation. This
* Statement should be an INSERT, UPDATE, or DELETE, otherwise this will return the number
* of modified rows from the last INSERT, UPDATE, or DELETE.
*
* See Connection.last_modified_rows.
*/
public int exec_get_modified(Cancellable? cancellable = null) throws Error {
new Result(this, cancellable);
int modified = connection.last_modified_rows;
// fire signal after safely retrieving the count
executed();
return modified;
}
/**
* index is zero-based.
*/
public Statement bind_double(int index, double d) throws DatabaseError {
throw_on_error("Statement.bind_double", stmt.bind_double(index + 1, d));
return this;
}
/**
* index is zero-based.
*/
public Statement bind_int(int index, int i) throws DatabaseError {
throw_on_error("Statement.bind_int", stmt.bind_int(index + 1, i));
return this;
}
/**
* index is zero-based.
*/
public Statement bind_uint(int index, uint u) throws DatabaseError {
return bind_int64(index, (int64) u);
}
/**
* index is zero-based.
*/
public Statement bind_long(int index, long l) throws DatabaseError {
return bind_int64(index, (int64) l);
}
/**
* index is zero-based.
*/
public Statement bind_int64(int index, int64 i64) throws DatabaseError {
throw_on_error("Statement.bind_int64", stmt.bind_int64(index + 1, i64));
return this;
}
/**
* Binds a bool to the column. A bool is stored as an integer, false == 0, true == 1. Note
* that fetching a bool via Result is more lenient; see Result.bool_at() and Result.bool_from().
*
* index is zero-based.
*/
public Statement bind_bool(int index, bool b) throws DatabaseError {
return bind_int(index, b ? 1 : 0);
}
/**
* index is zero-based.
*
* This will bind the value to the column as an int64 unless it's INVALID_ROWID, in which case
* the column is bound as NULL. WARNING: This does *not* work in WHERE clauses. For WHERE, you
* must use "field IS NULL".
*/
public Statement bind_rowid(int index, int64 rowid) throws DatabaseError {
return (rowid != Db.INVALID_ROWID) ? bind_int64(index, rowid) : bind_null(index);
}
/**
* index is zero-based.
*
* WARNING: This does *not* work in WHERE clauses. For WHERE, you must use "field IS NULL".
*/
public Statement bind_null(int index) throws DatabaseError {
throw_on_error("Statement.bind_null", stmt.bind_null(index + 1));
return this;
}
/**
* index is zero-based.
*/
public Statement bind_string(int index, string? s) throws DatabaseError {
throw_on_error("Statement.bind_string", stmt.bind_text(index + 1, s));
return this;
}
/**
* Binds the string representation of a {@link Memory.Buffer} to the replacement value
* in the {@link Statement}.
*
* If buffer supports {@link Memory.UnownedStringBuffer}, the unowned string will be used
* to avoid a memory copy. However, this means the Statement will hold a reference to the
* buffer until the Statement is destroyed.
*
* index is zero-based.
*/
public Statement bind_string_buffer(int index, Memory.Buffer? buffer) throws DatabaseError {
if (buffer == null)
return bind_string(index, null);
Memory.UnownedStringBuffer? unowned_buffer = buffer as Memory.UnownedStringBuffer;
if (unowned_buffer == null) {
throw_on_error("Statement.bind_string_buffer", stmt.bind_text(index + 1, buffer.to_string()));
return this;
}
// hold on to buffer for lifetime of Statement, SQLite's callback isn't enough for us to
// selectively unref each Buffer as it's done with it
held_buffers.add(unowned_buffer);
// note use of _bind_text, which is for static and other strings with their own memory
// management
stmt._bind_text(index + 1, unowned_buffer.to_unowned_string());
return this;
}
public override Statement? get_statement() {
return this;
}
}