Skip to content

Commit 3ebee19

Browse files
committed
Merge branch 'progress-handler' into statement-timeout
2 parents e3f95ca + c0d5144 commit 3ebee19

File tree

3 files changed

+102
-1
lines changed

3 files changed

+102
-1
lines changed

ext/sqlite3/database.c

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,51 @@ busy_handler(int argc, VALUE *argv, VALUE self)
251251
return self;
252252
}
253253

254+
static int
255+
rb_sqlite3_progress_handler(void *ctx)
256+
{
257+
VALUE self = (VALUE)(ctx);
258+
VALUE handle = rb_iv_get(self, "@progress_handler");
259+
VALUE result = rb_funcall(handle, rb_intern("call"), 0);
260+
261+
if (Qfalse == result) return 1;
262+
263+
return 0;
264+
}
265+
266+
/* call-seq:
267+
* progress_handler([n]) { ... }
268+
* progress_handler([n,] Class.new { def call; end }.new)
269+
*
270+
* Register a progress handler with this database instance.
271+
* This handler will be invoked periodically during a long-running query or operation.
272+
* If the handler returns +false+, the operation will be interrupted; otherwise, it continues.
273+
* The parameter 'n' specifies the number of SQLite virtual machine instructions between invocations.
274+
* If 'n' is not provided, the default value is 1.
275+
*/
276+
static VALUE
277+
progress_handler(int argc, VALUE *argv, VALUE self)
278+
{
279+
sqlite3RubyPtr ctx;
280+
VALUE block, n_value;
281+
282+
TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx);
283+
REQUIRE_OPEN_DB(ctx);
284+
285+
rb_scan_args(argc, argv, "02", &n_value, &block);
286+
287+
int n = NIL_P(n_value) ? 1 : NUM2INT(n_value);
288+
if(NIL_P(block) && rb_block_given_p()) block = rb_block_proc();
289+
290+
rb_iv_set(self, "@progress_handler", block);
291+
292+
sqlite3_progress_handler(
293+
ctx->db, n, NIL_P(block) ? NULL : rb_sqlite3_progress_handler, (void *)self);
294+
295+
return self;
296+
}
297+
298+
254299
/* call-seq: last_insert_row_id
255300
*
256301
* Obtains the unique row ID of the last row to be inserted by this Database
@@ -866,6 +911,9 @@ init_sqlite3_database(void)
866911
rb_define_method(cSqlite3Database, "changes", changes, 0);
867912
rb_define_method(cSqlite3Database, "authorizer=", set_authorizer, 1);
868913
rb_define_method(cSqlite3Database, "busy_handler", busy_handler, -1);
914+
#ifndef SQLITE_OMIT_PROGRESS_CALLBACK
915+
rb_define_method(cSqlite3Database, "progress_handler", progress_handler, -1);
916+
#endif
869917
rb_define_method(cSqlite3Database, "busy_timeout=", set_busy_timeout, 1);
870918
rb_define_method(cSqlite3Database, "extended_result_codes=", set_extended_result_codes, 1);
871919
rb_define_method(cSqlite3Database, "transaction_active?", transaction_active_p, 0);

lib/sqlite3/database.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ def initialize file, options = {}, zvfs = nil
124124
@authorizer = nil
125125
@encoding = nil
126126
@busy_handler = nil
127+
@progress_handler = nil
127128
@collations = {}
128129
@functions = {}
129130
@results_as_hash = options[:results_as_hash]

test/test_integration.rb

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -470,7 +470,7 @@ def test_transaction_active
470470

471471
def test_transaction_implicit_rollback
472472
assert !@db.transaction_active?
473-
@db.transaction
473+
@db.transaction
474474
@db.execute('create table bar (x CHECK(1 = 0))')
475475
assert @db.transaction_active?
476476
assert_raises( SQLite3::ConstraintException ) do
@@ -504,4 +504,56 @@ def test_bind_array_parameter
504504
[ 1, "foo" ] )
505505
assert_equal "foo", result
506506
end
507+
508+
def test_progress_handler_used
509+
progress_calls = []
510+
@db.progress_handler do
511+
progress_calls << nil
512+
true
513+
end
514+
@db.execute "create table test1(a, b)"
515+
516+
assert_operator 1, :<, progress_calls.size
517+
end
518+
519+
def test_progress_handler_opcode_arg
520+
progress_calls = []
521+
handler = Proc.new do
522+
progress_calls << nil
523+
true
524+
end
525+
@db.progress_handler(1, handler)
526+
@db.execute "create table test1(a, b)"
527+
first_count = progress_calls.size
528+
529+
progress_calls = []
530+
@db.progress_handler(10, handler)
531+
@db.execute "create table test2(a, b)"
532+
second_count = progress_calls.size
533+
534+
assert_operator first_count, :>=, second_count
535+
end
536+
537+
def test_progress_handler_interrupts_operation
538+
@db.progress_handler do
539+
false
540+
end
541+
542+
assert_raises(SQLite3::InterruptException) do
543+
@db.execute "create table test1(a, b)"
544+
end
545+
end
546+
547+
def test_clear_handler
548+
progress_calls = []
549+
@db.progress_handler do
550+
progress_calls << nil
551+
true
552+
end
553+
@db.progress_handler(nil)
554+
555+
@db.execute "create table test1(a, b)"
556+
557+
assert_equal 0, progress_calls.size
558+
end
507559
end

0 commit comments

Comments
 (0)