Skip to content

Commit bf1f0e6

Browse files
committed
Implement that statement_timeout= in pure C
1 parent 870f3b9 commit bf1f0e6

File tree

4 files changed

+32
-51
lines changed

4 files changed

+32
-51
lines changed

ext/sqlite3/database.c

Lines changed: 28 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ database_mark(void *ctx)
1717
{
1818
sqlite3RubyPtr c = (sqlite3RubyPtr)ctx;
1919
rb_gc_mark(c->busy_handler);
20+
rb_gc_mark(c->stmt_timeout);
21+
rb_gc_mark(c->stmt_deadline);
2022
}
2123

2224
static void
@@ -260,50 +262,44 @@ busy_handler(int argc, VALUE *argv, VALUE self)
260262
}
261263

262264
static int
263-
rb_sqlite3_progress_handler(void *ctx)
265+
rb_sqlite3_statement_timeout(void *context)
264266
{
265-
VALUE self = (VALUE)(ctx);
266-
VALUE handle = rb_iv_get(self, "@progress_handler");
267-
VALUE result = rb_funcall(handle, rb_intern("call"), 0);
267+
sqlite3RubyPtr ctx = (sqlite3RubyPtr)context;
268+
struct timespec currentTime;
269+
clock_gettime(CLOCK_MONOTONIC, &currentTime);
270+
271+
if (ctx->stmt_deadline == 0) {
272+
ctx->stmt_deadline = currentTime.tv_sec * 1e9 + currentTime.tv_nsec + (long long)ctx->stmt_timeout * 1e6;
273+
} else {
274+
long long timeDiff = ctx->stmt_deadline - (currentTime.tv_sec * 1e9 + currentTime.tv_nsec);
268275

269-
if (Qfalse == result) return 1;
276+
if (timeDiff < 0) { return 1; }
277+
}
270278

271-
return 0;
279+
return 0;
272280
}
273281

274-
/* call-seq:
275-
* progress_handler([n]) { ... }
276-
* progress_handler([n,] Class.new { def call; end }.new)
282+
/* call-seq: db.statement_timeout = ms
277283
*
278-
* Register a progress handler with this database instance.
279-
* This handler will be invoked periodically during a long-running query or operation.
280-
* If the handler returns +false+, the operation will be interrupted; otherwise, it continues.
281-
* The parameter 'n' specifies the number of SQLite virtual machine instructions between invocations.
282-
* If 'n' is not provided, the default value is 1.
284+
* Indicates that if a query lasts longer than the indicated number of
285+
* milliseconds, SQLite should interrupt that query and return an error.
286+
* By default, SQLite does not interrupt queries. To restore the default
287+
* behavior, send 0 as the +ms+ parameter.
283288
*/
284289
static VALUE
285-
progress_handler(int argc, VALUE *argv, VALUE self)
290+
set_statement_timeout(VALUE self, VALUE milliseconds)
286291
{
287-
sqlite3RubyPtr ctx;
288-
VALUE block, n_value;
289-
290-
TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx);
291-
REQUIRE_OPEN_DB(ctx);
292-
293-
rb_scan_args(argc, argv, "02", &n_value, &block);
294-
295-
int n = NIL_P(n_value) ? 1 : NUM2INT(n_value);
296-
if(NIL_P(block) && rb_block_given_p()) block = rb_block_proc();
292+
sqlite3RubyPtr ctx;
293+
TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx);
297294

298-
rb_iv_set(self, "@progress_handler", block);
295+
ctx->stmt_timeout = NUM2INT(milliseconds);
296+
int n = NUM2INT(milliseconds) == 0 ? -1 : 1000;
299297

300-
sqlite3_progress_handler(
301-
ctx->db, n, NIL_P(block) ? NULL : rb_sqlite3_progress_handler, (void *)self);
298+
sqlite3_progress_handler(ctx->db, n, rb_sqlite3_statement_timeout, (void *)ctx);
302299

303-
return self;
300+
return self;
304301
}
305302

306-
307303
/* call-seq: last_insert_row_id
308304
*
309305
* Obtains the unique row ID of the last row to be inserted by this Database
@@ -919,10 +915,10 @@ init_sqlite3_database(void)
919915
rb_define_method(cSqlite3Database, "changes", changes, 0);
920916
rb_define_method(cSqlite3Database, "authorizer=", set_authorizer, 1);
921917
rb_define_method(cSqlite3Database, "busy_handler", busy_handler, -1);
918+
rb_define_method(cSqlite3Database, "busy_timeout=", set_busy_timeout, 1);
922919
#ifndef SQLITE_OMIT_PROGRESS_CALLBACK
923-
rb_define_method(cSqlite3Database, "progress_handler", progress_handler, -1);
920+
rb_define_method(cSqlite3Database, "statement_timeout=", set_statement_timeout, 1);
924921
#endif
925-
rb_define_method(cSqlite3Database, "busy_timeout=", set_busy_timeout, 1);
926922
rb_define_method(cSqlite3Database, "extended_result_codes=", set_extended_result_codes, 1);
927923
rb_define_method(cSqlite3Database, "transaction_active?", transaction_active_p, 0);
928924
rb_define_private_method(cSqlite3Database, "exec_batch", exec_batch, 2);

ext/sqlite3/database.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
struct _sqlite3Ruby {
77
sqlite3 *db;
88
VALUE busy_handler;
9+
int stmt_timeout;
10+
long long stmt_deadline;
911
};
1012

1113
typedef struct _sqlite3Ruby sqlite3Ruby;

lib/sqlite3/database.rb

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -692,23 +692,6 @@ def busy_handler_timeout=(milliseconds)
692692
end
693693
end
694694

695-
def statement_timeout=( milliseconds )
696-
progress_handler(nil) and return if milliseconds.nil?
697-
698-
timeout_seconds = milliseconds.fdiv(1000)
699-
700-
progress_handler do
701-
now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
702-
if @statement_timeout_deadline.nil?
703-
@statement_timeout_deadline = now + timeout_seconds
704-
elsif now > @statement_timeout_deadline
705-
next false
706-
else
707-
true
708-
end
709-
end
710-
end
711-
712695
# A helper class for dealing with custom functions (see #create_function,
713696
# #create_aggregate, and #create_aggregate_handler). It encapsulates the
714697
# opaque function object that represents the current invocation. It also

test/test_integration_statement.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,11 +200,11 @@ def test_long_running_statements_get_interrupted_when_statement_timeout_set
200200
VALUES(0)
201201
UNION ALL
202202
SELECT i FROM r
203-
LIMIT 10000000
203+
LIMIT 1000000
204204
)
205205
SELECT i FROM r ORDER BY i LIMIT 1;
206206
SQL
207207
end
208-
@db.statement_timeout = nil
208+
@db.statement_timeout = 0
209209
end
210210
end

0 commit comments

Comments
 (0)