Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement progress handler interface #458

Open
wants to merge 23 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
6c4e155
Add the progress_handler interface
fractaledmind Jan 7, 2024
046bd67
Add tests for the progress_handler interface
fractaledmind Jan 7, 2024
11a94dc
Merge branch 'main' into progress-handler
fractaledmind Jan 7, 2024
50f1ef0
Remove extra line
fractaledmind Jan 7, 2024
c6bf834
Bump the frequency for the opcode arg test
fractaledmind Jan 7, 2024
3681b2a
Bump the frequency for the opcode arg test and make assertion more re…
fractaledmind Jan 7, 2024
c0d5144
Only define the ruby progress_handler method if the OMIT compilation …
fractaledmind Jan 7, 2024
02e6f0a
Properly protect the progress_handler depending on whether or not the…
fractaledmind Jan 9, 2024
da216c9
Skip progress_handler tests if the method is not defined
fractaledmind Jan 9, 2024
35a003a
WIP: make progress_handler GC-compaction safe
fractaledmind Jan 9, 2024
af1da42
Improve the compaction-safe version of the progress_handler
fractaledmind Jan 9, 2024
ce03826
Remove allocation bit
fractaledmind Jan 9, 2024
2aa7eae
Merge remote-tracking branch 'upstream/main' into progress-handler
fractaledmind Jan 9, 2024
22679e4
Merge branch 'main' into progress-handler
fractaledmind Jan 10, 2024
824627d
Merge branch 'main' into progress-handler
fractaledmind Jan 18, 2024
535d184
Default to progress handler every 1000 VM instructions
fractaledmind Jan 18, 2024
e12ef7d
Merge branch 'main' into progress-handler
fractaledmind Jan 26, 2024
4eb2870
Fix conditional check for defining progress_handler method
fractaledmind Jan 26, 2024
e7debf0
Add test showing how progress_handler can be used to increase concurr…
fractaledmind Jan 26, 2024
0fdd551
Fix Standard issue
fractaledmind Jan 26, 2024
dcd3b08
Fix tests now that the default number of vm steps for the progress_ha…
fractaledmind Jan 26, 2024
e5a18b9
Loosen test assertion for progress_handler releasing GVL
fractaledmind Jan 26, 2024
361a7e2
Loosen test assertion for progress_handler releasing GVL even more
fractaledmind Jan 26, 2024
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
48 changes: 48 additions & 0 deletions ext/sqlite3/database.c
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,51 @@ busy_handler(int argc, VALUE *argv, VALUE self)
return self;
}

static int
rb_sqlite3_progress_handler(void *ctx)
{
VALUE self = (VALUE)(ctx);
VALUE handle = rb_iv_get(self, "@progress_handler");
VALUE result = rb_funcall(handle, rb_intern("call"), 0);

if (Qfalse == result) return 1;

return 0;
}

/* call-seq:
* progress_handler([n]) { ... }
* progress_handler([n,] Class.new { def call; end }.new)
*
* Register a progress handler with this database instance.
* This handler will be invoked periodically during a long-running query or operation.
* If the handler returns +false+, the operation will be interrupted; otherwise, it continues.
* The parameter 'n' specifies the number of SQLite virtual machine instructions between invocations.
* If 'n' is not provided, the default value is 1.
*/
static VALUE
progress_handler(int argc, VALUE *argv, VALUE self)
{
sqlite3RubyPtr ctx;
VALUE block, n_value;

TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx);
REQUIRE_OPEN_DB(ctx);

rb_scan_args(argc, argv, "02", &n_value, &block);

int n = NIL_P(n_value) ? 1 : NUM2INT(n_value);
fractaledmind marked this conversation as resolved.
Show resolved Hide resolved
if(NIL_P(block) && rb_block_given_p()) block = rb_block_proc();

rb_iv_set(self, "@progress_handler", block);

sqlite3_progress_handler(
ctx->db, n, NIL_P(block) ? NULL : rb_sqlite3_progress_handler, (void *)self);
fractaledmind marked this conversation as resolved.
Show resolved Hide resolved

return self;
}


/* call-seq: last_insert_row_id
*
* Obtains the unique row ID of the last row to be inserted by this Database
Expand Down Expand Up @@ -866,6 +911,9 @@ init_sqlite3_database(void)
rb_define_method(cSqlite3Database, "changes", changes, 0);
rb_define_method(cSqlite3Database, "authorizer=", set_authorizer, 1);
rb_define_method(cSqlite3Database, "busy_handler", busy_handler, -1);
#ifndef SQLITE_OMIT_PROGRESS_CALLBACK
rb_define_method(cSqlite3Database, "progress_handler", progress_handler, -1);
#endif
rb_define_method(cSqlite3Database, "busy_timeout=", set_busy_timeout, 1);
rb_define_method(cSqlite3Database, "extended_result_codes=", set_extended_result_codes, 1);
rb_define_method(cSqlite3Database, "transaction_active?", transaction_active_p, 0);
Expand Down
1 change: 1 addition & 0 deletions lib/sqlite3/database.rb
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ def initialize file, options = {}, zvfs = nil
@authorizer = nil
@encoding = nil
@busy_handler = nil
@progress_handler = nil
@collations = {}
@functions = {}
@results_as_hash = options[:results_as_hash]
Expand Down
54 changes: 53 additions & 1 deletion test/test_integration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,7 @@ def test_transaction_active

def test_transaction_implicit_rollback
assert [email protected]_active?
@db.transaction
@db.transaction
@db.execute('create table bar (x CHECK(1 = 0))')
assert @db.transaction_active?
assert_raises( SQLite3::ConstraintException ) do
Expand Down Expand Up @@ -504,4 +504,56 @@ def test_bind_array_parameter
[ 1, "foo" ] )
assert_equal "foo", result
end

def test_progress_handler_used
progress_calls = []
@db.progress_handler do
progress_calls << nil
true
end
@db.execute "create table test1(a, b)"

assert_operator 1, :<, progress_calls.size
end

def test_progress_handler_opcode_arg
progress_calls = []
handler = Proc.new do
progress_calls << nil
true
end
@db.progress_handler(1, handler)
@db.execute "create table test1(a, b)"
first_count = progress_calls.size

progress_calls = []
@db.progress_handler(10, handler)
@db.execute "create table test2(a, b)"
second_count = progress_calls.size

assert_operator first_count, :>=, second_count
end

def test_progress_handler_interrupts_operation
@db.progress_handler do
false
end

assert_raises(SQLite3::InterruptException) do
@db.execute "create table test1(a, b)"
end
end

def test_clear_handler
progress_calls = []
@db.progress_handler do
progress_calls << nil
true
end
@db.progress_handler(nil)

@db.execute "create table test1(a, b)"

assert_equal 0, progress_calls.size
end
end